mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
fix(auth): clear model base URL on full logout
This commit is contained in:
@@ -38,7 +38,7 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
- `bl auth login --open-api ...` 只更新 `access_key_id` / `access_key_secret`
|
||||
- `bl auth logout --console` 只清 `access_token`
|
||||
- `bl auth logout --open-api` 只清 `access_key_id` / `access_key_secret` / `security_token`
|
||||
- `bl auth logout` 清 `api_key` + `access_token` + `access_key_*`
|
||||
- `bl auth logout` 清 `api_key` + `base_url` + `access_token` + `access_key_*`
|
||||
|
||||
解析分工:
|
||||
|
||||
|
||||
@@ -85,7 +85,8 @@ describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
|
||||
|
||||
## 安全与例外
|
||||
|
||||
- **禁止真实破坏性操作**:`auth logout` 只用 `--dry-run`;`config set` 只用 `--dry-run`
|
||||
- **禁止破坏真实用户配置**:`auth logout` 默认只用 `--dry-run`;需要验证实际落盘时,必须通过
|
||||
`BAILIAN_CONFIG_DIR` 指向隔离 fixture;`config set` 只用 `--dry-run`
|
||||
- **不加 dry-run**:`dryRun` 在 `resolveFileUrl` / `resolveCredential` / 上传**之后**的命令(如 `image edit`、`speech recognize` 带 `--url`)
|
||||
- **`--list-voices` 等旁路**:先于 `--text` 校验的 flag,缺参用例勿带该 flag
|
||||
- 新增 required option → 至少一条缺参用例;改 dry-run 输出 → 更新对应断言
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineCommand } from "bailian-cli-core";
|
||||
import { emitBare } from "bailian-cli-runtime";
|
||||
|
||||
export default defineCommand({
|
||||
description: "Clear stored credentials",
|
||||
description: "Clear stored credentials; full logout also clears the model Base URL",
|
||||
auth: "none",
|
||||
usageArgs: "[--console | --open-api] [--dry-run]",
|
||||
flags: {
|
||||
@@ -68,24 +68,24 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
const hasKey = stored.apiKey || stored.console || stored.openapi;
|
||||
const hasStoredAuth = stored.apiKey || stored.console || stored.openapi || !!stored.baseUrl;
|
||||
|
||||
if (settings.dryRun) {
|
||||
if (hasKey)
|
||||
if (hasStoredAuth)
|
||||
emitBare(
|
||||
`Would clear api_key / access_token / access_key_id / access_key_secret / security_token from ${store.path}`,
|
||||
`Would clear api_key / base_url / access_token / access_key_id / access_key_secret / security_token from ${store.path}`,
|
||||
);
|
||||
else emitBare("No credentials to clear.");
|
||||
else emitBare("No credentials or model Base URL to clear.");
|
||||
emitBare("No changes made.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (await store.logout("all")) {
|
||||
process.stderr.write(
|
||||
`Cleared api_key / access_token / access_key_id / access_key_secret / security_token from ${store.path}\n`,
|
||||
`Cleared api_key / base_url / access_token / access_key_id / access_key_secret / security_token from ${store.path}\n`,
|
||||
);
|
||||
} else {
|
||||
process.stderr.write("No credentials to clear.\n");
|
||||
process.stderr.write("No credentials or model Base URL to clear.\n");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -478,6 +478,38 @@ describe("e2e: auth", () => {
|
||||
expect(stderr).not.toContain("Cleared api_key");
|
||||
});
|
||||
|
||||
test("auth logout 清除当前 Config 的全部凭证和 Base URL,保留普通配置", async () => {
|
||||
const configDir = makeE2eOutputDir("auth-logout-all");
|
||||
writeFileSync(
|
||||
join(configDir, "config.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
api_key: "sk-e2e-placeholder",
|
||||
base_url: "https://model.example.com",
|
||||
access_token: "console-token-placeholder",
|
||||
access_key_id: "LTAI-e2e-placeholder",
|
||||
access_key_secret: "secret-e2e-placeholder",
|
||||
security_token: "sts-e2e-placeholder",
|
||||
output: "json",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + "\n",
|
||||
);
|
||||
|
||||
const { stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, ["auth", "logout"], {
|
||||
BAILIAN_CONFIG_DIR: configDir,
|
||||
});
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("api_key / base_url / access_token");
|
||||
|
||||
const config = JSON.parse(readFileSync(join(configDir, "config.json"), "utf8")) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
expect(config).toEqual({ output: "json" });
|
||||
});
|
||||
|
||||
test.skipIf(!isDashScopeE2EReady())("auth status 文本输出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
|
||||
@@ -9,7 +9,14 @@ import { describeAuthState, resolveModelBaseUrl } from "./resolver.ts";
|
||||
const LOGOUT_KEYS = {
|
||||
console: ["access_token"],
|
||||
openapi: ["access_key_id", "access_key_secret", "security_token"],
|
||||
all: ["api_key", "access_token", "access_key_id", "access_key_secret", "security_token"],
|
||||
all: [
|
||||
"api_key",
|
||||
"base_url",
|
||||
"access_token",
|
||||
"access_key_id",
|
||||
"access_key_secret",
|
||||
"security_token",
|
||||
],
|
||||
} as const;
|
||||
|
||||
/** 登录允许落盘的键:凭证本体 + 登录回调携带的连接/作用域字段。 */
|
||||
@@ -42,7 +49,7 @@ export interface AuthStore {
|
||||
resolveBaseUrl(fallback?: string): string;
|
||||
/** 登录落盘:合并写入,undefined 键忽略;显式 --config 成功后同时激活目标 Profile。 */
|
||||
login(patch: AuthPersistPatch): Promise<void>;
|
||||
/** 清凭证:console/openapi 只删对应域;all 清全部登录凭证。返回是否有变更。 */
|
||||
/** 清凭证:console/openapi 只删对应域;all 清全部登录凭证和 model baseUrl。返回是否有变更。 */
|
||||
logout(scope: "console" | "openapi" | "all"): Promise<boolean>;
|
||||
/** 实际写入的 config.json 路径(不受命名配置影响,一直是同一个文件)。 */
|
||||
path: string;
|
||||
|
||||
@@ -73,6 +73,7 @@ test("AuthStore:login 合并落盘,logout 按域清理并报告变更", async ()
|
||||
const store = makeAuthStore({ flags: {}, file: {}, env: {} });
|
||||
await store.login({
|
||||
api_key: "sk-1",
|
||||
base_url: "https://model.example.com/compatible-mode/v1",
|
||||
access_token: "tok-1",
|
||||
access_key_id: "ak-1",
|
||||
access_key_secret: "secret-1",
|
||||
@@ -82,6 +83,7 @@ test("AuthStore:login 合并落盘,logout 按域清理并报告变更", async ()
|
||||
});
|
||||
expect(makeConfigStore().read()).toMatchObject({
|
||||
api_key: "sk-1",
|
||||
base_url: "https://model.example.com",
|
||||
access_token: "tok-1",
|
||||
workspace_id: "ws-1",
|
||||
console_site: "international",
|
||||
@@ -90,15 +92,18 @@ test("AuthStore:login 合并落盘,logout 按域清理并报告变更", async ()
|
||||
expect(await store.logout("console")).toBe(true);
|
||||
expect(makeConfigStore().read().access_token).toBeUndefined();
|
||||
expect(makeConfigStore().read().api_key).toBe("sk-1");
|
||||
expect(makeConfigStore().read().base_url).toBe("https://model.example.com");
|
||||
|
||||
expect(await store.logout("openapi")).toBe(true);
|
||||
expect(makeConfigStore().read()).toMatchObject({ api_key: "sk-1" });
|
||||
expect(makeConfigStore().read().base_url).toBe("https://model.example.com");
|
||||
expect(makeConfigStore().read().access_key_id).toBeUndefined();
|
||||
expect(makeConfigStore().read().access_key_secret).toBeUndefined();
|
||||
expect(makeConfigStore().read().security_token).toBeUndefined();
|
||||
|
||||
expect(await store.logout("all")).toBe(true);
|
||||
expect(makeConfigStore().read().api_key).toBeUndefined();
|
||||
expect(makeConfigStore().read().base_url).toBeUndefined();
|
||||
expect(await store.logout("all")).toBe(false);
|
||||
|
||||
// 非凭证键不受 logout 影响
|
||||
|
||||
@@ -30,7 +30,7 @@ Verify: `bl --version` (prints `bl X.Y.Z`).
|
||||
|
||||
```bash
|
||||
bl auth status # check current auth
|
||||
bl auth logout # clear credentials
|
||||
bl auth logout # clear credentials and the model Base URL
|
||||
bl auth logout --console # clear console token only
|
||||
bl auth logout --open-api # clear OpenAPI AK/SK only
|
||||
```
|
||||
|
||||
@@ -11,7 +11,7 @@ Index: [index.md](index.md)
|
||||
| ------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| `bl auth generate-access-token` | Generate a CLI access token using OpenAPI AK/SK |
|
||||
| `bl auth login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) |
|
||||
| `bl auth logout` | Clear stored credentials |
|
||||
| `bl auth logout` | Clear stored credentials; full logout also clears the model Base URL |
|
||||
| `bl auth status` | Show current authentication state |
|
||||
|
||||
## Command details
|
||||
@@ -78,11 +78,11 @@ bl auth login --open-api --access-key-id LTAIxxxxx --access-key-secret xxxxx
|
||||
|
||||
### `bl auth logout`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------ |
|
||||
| **Name** | `auth logout` |
|
||||
| **Description** | Clear stored credentials |
|
||||
| **Usage** | `bl auth logout [--console \| --open-api] [--dry-run]` |
|
||||
| Field | Value |
|
||||
| --------------- | -------------------------------------------------------------------- |
|
||||
| **Name** | `auth logout` |
|
||||
| **Description** | Clear stored credentials; full logout also clears the model Base URL |
|
||||
| **Usage** | `bl auth logout [--console \| --open-api] [--dry-run]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Use this index for the full quick index and global flags.
|
||||
| `bl app list` | List Bailian applications | [app.md](app.md) |
|
||||
| `bl auth generate-access-token` | Generate a CLI access token using OpenAPI AK/SK | [auth.md](auth.md) |
|
||||
| `bl auth login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) |
|
||||
| `bl auth logout` | Clear stored credentials | [auth.md](auth.md) |
|
||||
| `bl auth logout` | Clear stored credentials; full logout also clears the model Base URL | [auth.md](auth.md) |
|
||||
| `bl auth status` | Show current authentication state | [auth.md](auth.md) |
|
||||
| `bl config list` | List config profiles and show the active profile | [config.md](config.md) |
|
||||
| `bl config set` | Set a config value | [config.md](config.md) |
|
||||
|
||||
Reference in New Issue
Block a user