mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
Fix Codex edit dialog masking live OAuth during proxy takeover
The reported "OAuth access token disappears when enabling Codex proxy takeover" was a display artifact, not data loss: auth.json on disk kept the OAuth login the whole time. During takeover the edit dialog falls back to the stored provider config (so it does not surface the proxy placeholder), which for a third-party provider shows that provider's own key instead of the live auth.json, making the OAuth token look gone. Thread an isProxyTakeover flag from App through ProviderForm into the Codex editor and show an explicit notice plus storage-aware auth/config hints clarifying that the form displays the stored provider config while the live config is temporarily managed by the proxy. Drop the proxy-running condition so the notice shows whenever takeover is active, even with the proxy stopped. Add a regression test asserting the dialog does not read live settings during takeover and renders the database config. i18n synced across en/zh/ja/zh-TW.
This commit is contained in:
+1
-1
@@ -1533,7 +1533,7 @@ function App() {
|
||||
}}
|
||||
onSubmit={handleEditProvider}
|
||||
appId={activeApp}
|
||||
isProxyTakeover={isProxyRunning && isCurrentAppTakeoverActive}
|
||||
isProxyTakeover={isCurrentAppTakeoverActive}
|
||||
/>
|
||||
|
||||
{effectiveUsageProvider && (
|
||||
|
||||
@@ -247,6 +247,7 @@ export function EditProviderDialog({
|
||||
onSubmittingChange={setIsFormSubmitting}
|
||||
initialData={initialData}
|
||||
showButtons={false}
|
||||
isProxyTakeover={isProxyTakeover}
|
||||
/>
|
||||
</FullScreenPanel>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Info } from "lucide-react";
|
||||
import { CodexAuthSection, CodexConfigSection } from "./CodexConfigSections";
|
||||
import { CodexCommonConfigModal } from "./CodexCommonConfigModal";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
||||
interface CodexConfigEditorProps {
|
||||
authValue: string;
|
||||
@@ -11,6 +14,8 @@ interface CodexConfigEditorProps {
|
||||
|
||||
showRemoteCompaction?: boolean;
|
||||
|
||||
isProxyTakeover?: boolean;
|
||||
|
||||
onAuthChange: (value: string) => void;
|
||||
|
||||
onConfigChange: (value: string) => void;
|
||||
@@ -43,6 +48,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
||||
configValue,
|
||||
providerName,
|
||||
showRemoteCompaction,
|
||||
isProxyTakeover = false,
|
||||
onAuthChange,
|
||||
onConfigChange,
|
||||
onAuthBlur,
|
||||
@@ -57,6 +63,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
||||
onExtract,
|
||||
isExtracting,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
||||
|
||||
const handleCloseCommonConfigModal = () => {
|
||||
@@ -66,12 +73,22 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{isProxyTakeover && (
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
{t("codexConfig.proxyTakeoverStorageNotice")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Auth JSON Section */}
|
||||
<CodexAuthSection
|
||||
value={authValue}
|
||||
onChange={onAuthChange}
|
||||
onBlur={onAuthBlur}
|
||||
error={authError}
|
||||
isProxyTakeover={isProxyTakeover}
|
||||
/>
|
||||
|
||||
{/* Config TOML Section */}
|
||||
@@ -85,6 +102,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
||||
onEditCommonConfig={() => setIsCommonConfigModalOpen(true)}
|
||||
commonConfigError={commonConfigError}
|
||||
configError={configError}
|
||||
isProxyTakeover={isProxyTakeover}
|
||||
/>
|
||||
|
||||
{/* Common Config Modal */}
|
||||
|
||||
@@ -29,6 +29,7 @@ interface CodexAuthSectionProps {
|
||||
onChange: (value: string) => void;
|
||||
onBlur?: () => void;
|
||||
error?: string;
|
||||
isProxyTakeover?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,6 +40,7 @@ export const CodexAuthSection: React.FC<CodexAuthSectionProps> = ({
|
||||
onChange,
|
||||
onBlur,
|
||||
error,
|
||||
isProxyTakeover = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
@@ -90,7 +92,11 @@ export const CodexAuthSection: React.FC<CodexAuthSectionProps> = ({
|
||||
|
||||
{!error && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("codexConfig.authJsonHint")}
|
||||
{t(
|
||||
isProxyTakeover
|
||||
? "codexConfig.authJsonStorageHint"
|
||||
: "codexConfig.authJsonHint",
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -107,6 +113,7 @@ interface CodexConfigSectionProps {
|
||||
onEditCommonConfig: () => void;
|
||||
commonConfigError?: string;
|
||||
configError?: string;
|
||||
isProxyTakeover?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,6 +129,7 @@ export const CodexConfigSection: React.FC<CodexConfigSectionProps> = ({
|
||||
onEditCommonConfig,
|
||||
commonConfigError,
|
||||
configError,
|
||||
isProxyTakeover = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
@@ -367,7 +375,11 @@ export const CodexConfigSection: React.FC<CodexConfigSectionProps> = ({
|
||||
|
||||
{!configError && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("codexConfig.configTomlHint")}
|
||||
{t(
|
||||
isProxyTakeover
|
||||
? "codexConfig.configTomlStorageHint"
|
||||
: "codexConfig.configTomlHint",
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -230,6 +230,7 @@ export interface ProviderFormProps {
|
||||
iconColor?: string;
|
||||
};
|
||||
showButtons?: boolean;
|
||||
isProxyTakeover?: boolean;
|
||||
}
|
||||
|
||||
export function ProviderForm(props: ProviderFormProps) {
|
||||
@@ -251,6 +252,7 @@ function ProviderFormFull({
|
||||
onSubmittingChange,
|
||||
initialData,
|
||||
showButtons = true,
|
||||
isProxyTakeover = false,
|
||||
}: ProviderFormProps) {
|
||||
if (appId === "claude-desktop") {
|
||||
throw new Error("ProviderFormFull should not receive claude-desktop");
|
||||
@@ -2165,6 +2167,7 @@ function ProviderFormFull({
|
||||
configValue={codexConfig}
|
||||
providerName={form.watch("name")}
|
||||
showRemoteCompaction={category !== "official"}
|
||||
isProxyTakeover={isProxyTakeover}
|
||||
onAuthChange={setCodexAuth}
|
||||
onConfigChange={handleCodexConfigChange}
|
||||
useCommonConfig={useCodexCommonConfigFlag}
|
||||
|
||||
@@ -1104,8 +1104,11 @@
|
||||
"authJson": "auth.json (JSON) *",
|
||||
"authJsonPlaceholder": "{\n \"OPENAI_API_KEY\": \"sk-your-api-key-here\"\n}",
|
||||
"authJsonHint": "Codex auth.json configuration content",
|
||||
"authJsonStorageHint": "Proxy takeover is showing provider-stored auth, not the live ~/.codex/auth.json; OAuth access tokens remain in the live auth.json.",
|
||||
"configToml": "config.toml (TOML)",
|
||||
"configTomlHint": "Codex config.toml configuration content",
|
||||
"configTomlStorageHint": "Proxy takeover is showing the provider upstream config; the live config.toml is temporarily managed with the local proxy URL and PROXY_MANAGED.",
|
||||
"proxyTakeoverStorageNotice": "This app is under proxy takeover. You are editing the provider's stored config; the live config is temporarily managed by the proxy, so this form does not need to show 127.0.0.1 or PROXY_MANAGED.",
|
||||
"writeCommonConfig": "Write Common Config",
|
||||
"editCommonConfig": "Edit Common Config",
|
||||
"editCommonConfigTitle": "Edit Codex Common Config Snippet",
|
||||
|
||||
@@ -1104,8 +1104,11 @@
|
||||
"authJson": "auth.json (JSON) *",
|
||||
"authJsonPlaceholder": "{\n \"OPENAI_API_KEY\": \"sk-your-api-key-here\"\n}",
|
||||
"authJsonHint": "Codex の auth.json 設定内容",
|
||||
"authJsonStorageHint": "プロキシ引き継ぎ中は、live の ~/.codex/auth.json ではなくプロバイダー保存 auth を表示しています。OAuth アクセストークンは live auth.json に保持されます。",
|
||||
"configToml": "config.toml (TOML)",
|
||||
"configTomlHint": "Codex の config.toml 設定内容",
|
||||
"configTomlStorageHint": "プロキシ引き継ぎ中はプロバイダーの upstream config を表示しています。live config.toml は一時的にローカルプロキシ URL と PROXY_MANAGED で管理されます。",
|
||||
"proxyTakeoverStorageNotice": "このアプリはプロキシ引き継ぎ中です。ここで編集しているのはプロバイダーの保存設定で、実際の live 設定は一時的にプロキシが管理します。このフォームに 127.0.0.1 や PROXY_MANAGED が表示される必要はありません。",
|
||||
"writeCommonConfig": "共通設定を書き込む",
|
||||
"editCommonConfig": "共通設定を編集",
|
||||
"editCommonConfigTitle": "Codex 共通設定スニペットを編集",
|
||||
|
||||
@@ -1076,8 +1076,11 @@
|
||||
"authJson": "auth.json (JSON) *",
|
||||
"authJsonPlaceholder": "{\n \"OPENAI_API_KEY\": \"sk-your-api-key-here\"\n}",
|
||||
"authJsonHint": "Codex auth.json 設定內容",
|
||||
"authJsonStorageHint": "代理接管中顯示的是供應商儲存 auth,不是即時 ~/.codex/auth.json;OAuth Access Token 會保留在 live auth.json 中。",
|
||||
"configToml": "config.toml (TOML)",
|
||||
"configTomlHint": "Codex config.toml 設定內容",
|
||||
"configTomlStorageHint": "代理接管中顯示的是供應商上游 config;即時 config.toml 會由代理暫時寫為本機位址和 PROXY_MANAGED。",
|
||||
"proxyTakeoverStorageNotice": "目前應用處於代理接管狀態。這裡編輯的是供應商儲存配置,真實 live 配置會由代理暫時接管;儲存不需要這裡顯示 127.0.0.1 或 PROXY_MANAGED。",
|
||||
"writeCommonConfig": "寫入通用設定",
|
||||
"editCommonConfig": "編輯通用設定",
|
||||
"editCommonConfigTitle": "編輯 Codex 通用設定片段",
|
||||
|
||||
@@ -1104,8 +1104,11 @@
|
||||
"authJson": "auth.json (JSON) *",
|
||||
"authJsonPlaceholder": "{\n \"OPENAI_API_KEY\": \"sk-your-api-key-here\"\n}",
|
||||
"authJsonHint": "Codex auth.json 配置内容",
|
||||
"authJsonStorageHint": "代理接管中显示的是供应商存储 auth,不是实时 ~/.codex/auth.json;OAuth Access Token 会保留在 live auth.json 中。",
|
||||
"configToml": "config.toml (TOML)",
|
||||
"configTomlHint": "Codex config.toml 配置内容",
|
||||
"configTomlStorageHint": "代理接管中显示的是供应商上游 config;实时 config.toml 会由代理临时写为本地地址和 PROXY_MANAGED。",
|
||||
"proxyTakeoverStorageNotice": "当前应用处于代理接管状态。这里编辑的是供应商存储配置,真实 live 配置会由代理临时接管;保存不会要求这里显示 127.0.0.1 或 PROXY_MANAGED。",
|
||||
"writeCommonConfig": "写入通用配置",
|
||||
"editCommonConfig": "编辑通用配置",
|
||||
"editCommonConfigTitle": "编辑 Codex 通用配置片段",
|
||||
|
||||
@@ -42,6 +42,7 @@ vi.mock("@/components/providers/forms/ProviderForm", () => ({
|
||||
ProviderForm: ({
|
||||
initialData,
|
||||
onSubmit,
|
||||
isProxyTakeover,
|
||||
}: {
|
||||
initialData: {
|
||||
name?: string;
|
||||
@@ -61,6 +62,7 @@ vi.mock("@/components/providers/forms/ProviderForm", () => ({
|
||||
icon?: string;
|
||||
iconColor?: string;
|
||||
}) => void;
|
||||
isProxyTakeover?: boolean;
|
||||
}) => (
|
||||
<form
|
||||
id="provider-form"
|
||||
@@ -80,6 +82,9 @@ vi.mock("@/components/providers/forms/ProviderForm", () => ({
|
||||
<output data-testid="settings-config">
|
||||
{JSON.stringify(initialData.settingsConfig ?? {})}
|
||||
</output>
|
||||
<output data-testid="is-proxy-takeover">
|
||||
{isProxyTakeover ? "true" : "false"}
|
||||
</output>
|
||||
</form>
|
||||
),
|
||||
}));
|
||||
@@ -153,4 +158,48 @@ describe("EditProviderDialog", () => {
|
||||
modelCatalog: dbModelCatalog,
|
||||
});
|
||||
});
|
||||
|
||||
it("代理接管中编辑 Codex 供应商时展示数据库配置而不是读取 live 代理配置", async () => {
|
||||
const provider: Provider = {
|
||||
id: "deepseek",
|
||||
name: "DeepSeek",
|
||||
category: "custom",
|
||||
settingsConfig: {
|
||||
auth: {
|
||||
OPENAI_API_KEY: "db-key",
|
||||
},
|
||||
config:
|
||||
'model_provider = "custom"\n[model_providers.custom]\nbase_url = "https://api.deepseek.com/v1"\n',
|
||||
},
|
||||
};
|
||||
|
||||
apiMocks.getCurrent.mockResolvedValue(provider.id);
|
||||
apiMocks.getLiveProviderSettings.mockResolvedValue({
|
||||
auth: {
|
||||
OPENAI_API_KEY: "PROXY_MANAGED",
|
||||
},
|
||||
config:
|
||||
'model_provider = "custom"\n[model_providers.custom]\nbase_url = "http://127.0.0.1:15721/v1"\nexperimental_bearer_token = "PROXY_MANAGED"\n',
|
||||
});
|
||||
|
||||
render(
|
||||
<EditProviderDialog
|
||||
open
|
||||
provider={provider}
|
||||
onOpenChange={vi.fn()}
|
||||
onSubmit={vi.fn()}
|
||||
appId="codex"
|
||||
isProxyTakeover
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("is-proxy-takeover").textContent).toBe("true");
|
||||
});
|
||||
|
||||
expect(apiMocks.getLiveProviderSettings).not.toHaveBeenCalled();
|
||||
expect(
|
||||
JSON.parse(screen.getByTestId("settings-config").textContent ?? "{}"),
|
||||
).toEqual(provider.settingsConfig);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user