From a03ba673be7755700ba3a69e213e8ada24aa93f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=A5=E9=BA=92?= Date: Fri, 17 Jul 2026 15:57:23 +0800 Subject: [PATCH] fix(auth): clear model base URL on full logout --- docs/agents/auth-change.md | 2 +- docs/agents/cli-e2e-tests.md | 3 +- packages/commands/src/commands/auth/logout.ts | 14 ++++---- packages/commands/tests/e2e/auth.e2e.test.ts | 32 +++++++++++++++++++ packages/core/src/auth/store.ts | 11 +++++-- packages/core/tests/config-store.test.ts | 5 +++ skills/bailian-cli/assets/setup.md | 2 +- skills/bailian-cli/reference/auth.md | 12 +++---- skills/bailian-cli/reference/index.md | 2 +- 9 files changed, 64 insertions(+), 19 deletions(-) diff --git a/docs/agents/auth-change.md b/docs/agents/auth-change.md index 4c6dbe8..55f92ba 100644 --- a/docs/agents/auth-change.md +++ b/docs/agents/auth-change.md @@ -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_*` 解析分工: diff --git a/docs/agents/cli-e2e-tests.md b/docs/agents/cli-e2e-tests.md index 2ca45f0..4c1aafb 100644 --- a/docs/agents/cli-e2e-tests.md +++ b/docs/agents/cli-e2e-tests.md @@ -85,7 +85,8 @@ describe.skipIf()("e2e: (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 输出 → 更新对应断言 diff --git a/packages/commands/src/commands/auth/logout.ts b/packages/commands/src/commands/auth/logout.ts index 2a598ab..2a2860a 100644 --- a/packages/commands/src/commands/auth/logout.ts +++ b/packages/commands/src/commands/auth/logout.ts @@ -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"); } }, }); diff --git a/packages/commands/tests/e2e/auth.e2e.test.ts b/packages/commands/tests/e2e/auth.e2e.test.ts index f1461e7..505e082 100644 --- a/packages/commands/tests/e2e/auth.e2e.test.ts +++ b/packages/commands/tests/e2e/auth.e2e.test.ts @@ -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", diff --git a/packages/core/src/auth/store.ts b/packages/core/src/auth/store.ts index c762188..3357210 100644 --- a/packages/core/src/auth/store.ts +++ b/packages/core/src/auth/store.ts @@ -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; - /** 清凭证:console/openapi 只删对应域;all 清全部登录凭证。返回是否有变更。 */ + /** 清凭证:console/openapi 只删对应域;all 清全部登录凭证和 model baseUrl。返回是否有变更。 */ logout(scope: "console" | "openapi" | "all"): Promise; /** 实际写入的 config.json 路径(不受命名配置影响,一直是同一个文件)。 */ path: string; diff --git a/packages/core/tests/config-store.test.ts b/packages/core/tests/config-store.test.ts index 8cc12a0..c266797 100644 --- a/packages/core/tests/config-store.test.ts +++ b/packages/core/tests/config-store.test.ts @@ -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 影响 diff --git a/skills/bailian-cli/assets/setup.md b/skills/bailian-cli/assets/setup.md index 4869de9..dd6a5eb 100644 --- a/skills/bailian-cli/assets/setup.md +++ b/skills/bailian-cli/assets/setup.md @@ -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 ``` diff --git a/skills/bailian-cli/reference/auth.md b/skills/bailian-cli/reference/auth.md index 5002c39..016699a 100644 --- a/skills/bailian-cli/reference/auth.md +++ b/skills/bailian-cli/reference/auth.md @@ -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 diff --git a/skills/bailian-cli/reference/index.md b/skills/bailian-cli/reference/index.md index 132c4a1..b521b03 100644 --- a/skills/bailian-cli/reference/index.md +++ b/skills/bailian-cli/reference/index.md @@ -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) |