fix(ai): trim API keys before authentication

This commit is contained in:
t8y2 committed 2026-07-28 13:19:04 +08:00
1 parent 8d1472f7af
commit 054702e223
4 files changed
+60 -23

No files matched your search

@@ -2378,7 +2378,7 @@ function currentAiEditConfig() {
const reasoningLevel = aiIsClaudeCodeCli.value ? normalizeClaudeCodeReasoningLevel(aiEditReasoningLevel.value, aiSelectedModelInfo.value) : aiEditReasoningLevel.value;
return {
provider: aiEditProvider.value,
apiKey: aiEditApiKey.value,
apiKey: aiEditApiKey.value.trim(),
authMethod: aiEditAuthMethod.value,
endpoint: aiEditEndpoint.value,
model: aiEditModel.value,
@@ -2503,7 +2503,7 @@ async function aiFetchModelList() {
try {
const models = await aiListModels({
provider: aiEditProvider.value,
apiKey: aiEditApiKey.value,
apiKey: aiEditApiKey.value.trim(),
endpoint: aiEditEndpoint.value,
model: aiEditModel.value,
authMethod: aiEditAuthMethod.value,
+26 -1
View File
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { enforceRightSidebarPanelExclusivity, EXECUTE_MODE_CURRENT_DEFAULT_VERSION, normalizeDesktopSettings, normalizeEditorSettings, normalizeMcpGlobalPolicy, transitionRightSidebarPanels, type RightSidebarPanelState } from "@/stores/settingsStore";
import { enforceRightSidebarPanelExclusivity, EXECUTE_MODE_CURRENT_DEFAULT_VERSION, normalizeAiConfig, normalizeDesktopSettings, normalizeEditorSettings, normalizeMcpGlobalPolicy, transitionRightSidebarPanels, type RightSidebarPanelState } from "@/stores/settingsStore";
import { createPinia, setActivePinia } from "pinia";
import { isProxy } from "vue";
import type { AiConfigItem } from "@/types/ai";
@@ -318,6 +318,31 @@ function makeTestConfig(overrides: Partial<AiConfigItem> & { id: string }): AiCo
} as AiConfigItem;
}
describe("settingsStore AI API key normalization", () => {
beforeEach(() => {
vi.resetModules();
setActivePinia(createPinia());
});
it("trims API keys before persisting new configurations", async () => {
const saveAiConfigItem = vi.fn().mockResolvedValue(undefined);
vi.doMock("@/lib/backend/api", () => ({ saveAiConfigItem }));
const { useSettingsStore } = await import("@/stores/settingsStore");
const store = useSettingsStore();
const config = makeTestConfig({ id: "trimmed-key", apiKey: " \tsecret\r\n" });
await store.createAiConfig(config);
expect(saveAiConfigItem).toHaveBeenCalledWith(expect.objectContaining({ apiKey: "secret" }));
expect(store.aiConfigs[0].apiKey).toBe("secret");
});
it("trims API keys when normalizing loaded configurations", () => {
expect(normalizeAiConfig({ provider: "openai", apiKey: " secret " }).apiKey).toBe("secret");
});
});
describe("settingsStore MCP policy persistence", () => {
beforeEach(() => {
vi.resetModules();
+11 -6
View File
@@ -250,9 +250,9 @@ export function normalizeAiConfig(config: Partial<AiConfig> | null | undefined):
const provider = config?.provider && config.provider in AI_PROVIDER_PRESETS ? config.provider : inferAiProviderFromConfig(config);
return {
...defaultConfigs[provider],
apiKey: config?.apiKey ?? "",
...config,
provider,
apiKey: (config?.apiKey ?? "").trim(),
apiStyle: config?.apiStyle ?? defaultConfigs[provider].apiStyle,
authMethod: config?.authMethod ?? defaultConfigs[provider].authMethod,
proxyEnabled: !!config?.proxyEnabled,
@@ -267,6 +267,10 @@ export function normalizeAiConfig(config: Partial<AiConfig> | null | undefined):
};
}
function normalizeAiConfigItem(config: AiConfigItem): AiConfigItem {
return { ...config, ...normalizeAiConfig(config) };
}
function inferAiProviderFromConfig(config: Partial<AiConfig> | null | undefined): AiProvider {
const endpoint = config?.endpoint?.toLowerCase() ?? "";
const model = config?.model?.toLowerCase() ?? "";
@@ -1087,7 +1091,7 @@ export const useSettingsStore = defineStore("settings", () => {
const newConfigs = await api.loadAiConfigs();
if (newConfigs.length > 0) {
aiConfigs.value = newConfigs;
aiConfigs.value = newConfigs.map(normalizeAiConfigItem);
} else {
// 迁移旧格式
await migrateToMultiConfig();
@@ -1146,17 +1150,18 @@ export const useSettingsStore = defineStore("settings", () => {
}
async function createAiConfig(config: AiConfigItem): Promise<void> {
await api.saveAiConfigItem(config);
aiConfigs.value.push(config);
const normalized = normalizeAiConfigItem(config);
await api.saveAiConfigItem(normalized);
aiConfigs.value.push(normalized);
if (aiConfigs.value.length === 1) {
activeModel.value = { configId: config.id, modelId: config.model };
activeModel.value = { configId: normalized.id, modelId: normalized.model };
}
}
async function updateAiConfigItem(id: string, config: Partial<AiConfigItem>): Promise<void> {
const index = aiConfigs.value.findIndex((c) => c.id === id);
if (index !== -1) {
const updated = { ...aiConfigs.value[index], ...config };
const updated = normalizeAiConfigItem({ ...aiConfigs.value[index], ...config });
await api.saveAiConfigItem(updated);
aiConfigs.value[index] = updated;
}