mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-25 17:22:27 +08:00
fix(codex): reconcile edit form key with live bearer token (#6534)
* fix(codex): keep provider's own key on switch-away backfill When switching away from a Codex provider, cc-switch backfills the outgoing provider's stored config from the live ~/.codex files so that in-app changes are captured. restore_codex_provider_token_for_backfill decides what to persist as the provider's auth.OPENAI_API_KEY: - When the live config.toml carries a per-provider experimental_bearer_token, that token is provider-scoped and is lifted back into the stored auth. Correct. - When it does NOT (the default preserve_codex_official_auth_on_switch = false mode keeps the active key in the shared auth.json), the function returned early and the caller adopted the live auth.json wholesale as the provider's stored auth. The problem: auth.json is a single-slot shared file with no provider identity; it always holds the most-recently-activated provider's key. Any time it holds ANOTHER provider's key (after a proxy-takeover backup/restore cycle, an in-app ChatGPT login overwriting auth.json, a cloud-sync current/live divergence, or any current-vs-live mismatch), the switch-away backfill overwrote the outgoing provider's stored key with another's. Repeated switches made keys silently converge across providers that share a base URL, surfacing as "model xxx not found" because the wrong key reached a model it was not entitled to. Fix: in the no-bearer branch, preserve the provider's own DB-stored auth instead of adopting the shared live auth.json. Live config.toml changes (model/base_url/mcp/env) are still captured by the caller; only the credential slot is no longer taken from the shared file. This mirrors the #6277 restore-side rule of never letting the shared live credential clobber per-provider storage. Adds two regression tests: - backfill_keeps_provider_own_key_when_live_auth_holds_another_key (the failing case under the old code) - backfill_restores_key_from_live_bearer_token_when_present (positive control for the already-correct bearer path) Fixes #6414. * Revert "fix(codex): keep provider's own key on switch-away backfill" This reverts commit 4ddf304eca458a1eadcf93f5851579c7dde94828. * fix(codex): reconcile edit form key with live bearer token In bearer-token mode (preserveCodexOfficialAuthOnSwitch enabled — used to keep a ChatGPT login while routing third-party providers), switching to a provider writes its key to config.toml's experimental_bearer_token and preserves the shared ~/.codex/auth.json. auth.json is a single shared slot with no provider identity, so it may hold ANOTHER provider's stale key (left over from a previous default-mode switch or a different provider). When the user then opened the edit dialog for the current provider, EditProviderDialog loaded liveSettings as the form base, and useCodexConfigState initialized codexAuth from the stale auth.json while pickCodexApiKey preferred auth.OPENAI_API_KEY over the bearer token. The form therefore displayed — and on save persisted — the stale key back into the provider's DB record. Repeated edits made keys silently converge across providers that share a base URL, surfacing as "model xxx not found" once the wrong key reached a model it was not entitled to (#6414). Fix: when loading Codex config into the form, lift the config's experimental_bearer_token into auth.OPENAI_API_KEY (when present and differing) so both the displayed key and the saved auth carry the correct per-provider key. This mirrors the backend restore_codex_provider_token_for_backfill lift and is a no-op when the config has no bearer (default mode keeps auth.json as the active key slot, and manual live auth edits are preserved exactly — covered by existing tests). Adds 4 regression tests in tests/hooks/useCodexConfigState.bearer.test.ts. Fixes #6414. * style: prettier-format useCodexConfigState.ts Fixes the frontend CI "Check formatting" failure on the previous commit. Pure formatting (collapses a multi-line call to one line); no behavior change. * fix(codex): scope bearer reconciliation to live edits
This commit is contained in:
1 parent
bd15ea1193
commit
bbe8bb93ab
4 files changed
+289
-9
No files matched your search
@@ -0,0 +1,75 @@
|
||||
import { act, renderHook } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { useCodexConfigState } from "@/components/providers/forms/hooks/useCodexConfigState";
|
||||
|
||||
// The hook is also used for stored providers and presets. Those inputs keep
|
||||
// auth.OPENAI_API_KEY as their canonical credential; only EditProviderDialog's
|
||||
// current-live boundary may lift a bearer into auth (#6414).
|
||||
describe("useCodexConfigState bearer-token precedence", () => {
|
||||
it("keeps stored auth authoritative when config also has a bearer", () => {
|
||||
const initialData = {
|
||||
settingsConfig: {
|
||||
auth: { OPENAI_API_KEY: "sk-db-key" },
|
||||
config:
|
||||
'model_provider = "custom"\nmodel = "model-A"\nexperimental_bearer_token = "sk-leftover-live-key"\n',
|
||||
},
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCodexConfigState({ initialData }));
|
||||
|
||||
expect(result.current.codexApiKey).toBe("sk-db-key");
|
||||
const savedAuth = JSON.parse(result.current.codexAuth);
|
||||
expect(savedAuth.OPENAI_API_KEY).toBe("sk-db-key");
|
||||
});
|
||||
|
||||
it("falls back to the bearer for display without mutating an auth object that has no key", () => {
|
||||
const initialData = {
|
||||
settingsConfig: {
|
||||
auth: { tokens: { account_id: "acc" } },
|
||||
config:
|
||||
'model_provider = "custom"\nmodel = "model-A"\nexperimental_bearer_token = "sk-real-key-A"\n',
|
||||
},
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCodexConfigState({ initialData }));
|
||||
|
||||
expect(result.current.codexApiKey).toBe("sk-real-key-A");
|
||||
const savedAuth = JSON.parse(result.current.codexAuth);
|
||||
expect(savedAuth.OPENAI_API_KEY).toBeUndefined();
|
||||
expect(savedAuth.tokens).toEqual({ account_id: "acc" });
|
||||
});
|
||||
|
||||
it("does not reconcile when the config has no bearer (default mode / manual live edits)", () => {
|
||||
// Default mode keeps the active key in auth.json; the config has no bearer.
|
||||
// A user's manual live edit (auth.json = "live-key") must be preserved
|
||||
// exactly — this is the intentional backfill/capture behavior, and the
|
||||
// reconciliation must not touch it.
|
||||
const initialData = {
|
||||
settingsConfig: {
|
||||
auth: { OPENAI_API_KEY: "live-key" },
|
||||
config: 'model_provider = "custom"\nmodel = "model-A"\n',
|
||||
},
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCodexConfigState({ initialData }));
|
||||
|
||||
expect(result.current.codexApiKey).toBe("live-key");
|
||||
const savedAuth = JSON.parse(result.current.codexAuth);
|
||||
expect(savedAuth.OPENAI_API_KEY).toBe("live-key");
|
||||
});
|
||||
|
||||
it("keeps preset auth authoritative when reset config contains a bearer", () => {
|
||||
const { result } = renderHook(() => useCodexConfigState({}));
|
||||
|
||||
act(() => {
|
||||
result.current.resetCodexConfig(
|
||||
{ OPENAI_API_KEY: "sk-preset-key" },
|
||||
'model_provider = "custom"\nexperimental_bearer_token = "sk-leftover-key"\n',
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.codexApiKey).toBe("sk-preset-key");
|
||||
const savedAuth = JSON.parse(result.current.codexAuth);
|
||||
expect(savedAuth.OPENAI_API_KEY).toBe("sk-preset-key");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user