From 9007920695b3ec232959e05b51126f7475eb8ebe Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 13 Jan 2026 07:42:41 +0000 Subject: [PATCH] fix(auth): drop invalid auth profiles from order --- src/agents/auth-profiles.test.ts | 128 +++++++++++++++++++++++++++++++ src/agents/auth-profiles.ts | 33 +++++++- 2 files changed, 159 insertions(+), 2 deletions(-) diff --git a/src/agents/auth-profiles.test.ts b/src/agents/auth-profiles.test.ts index a330f8a61..1f81f2e1e 100644 --- a/src/agents/auth-profiles.test.ts +++ b/src/agents/auth-profiles.test.ts @@ -59,6 +59,134 @@ describe("resolveAuthProfileOrder", () => { expect(order).toContain("anthropic:default"); }); + it("drops explicit order entries that are missing from the store", () => { + const order = resolveAuthProfileOrder({ + cfg: { + auth: { + order: { + minimax: ["minimax:default", "minimax:prod"], + }, + }, + }, + store: { + version: 1, + profiles: { + "minimax:prod": { + type: "api_key", + provider: "minimax", + key: "sk-prod", + }, + }, + }, + provider: "minimax", + }); + expect(order).toEqual(["minimax:prod"]); + }); + + it("drops explicit order entries that belong to another provider", () => { + const order = resolveAuthProfileOrder({ + cfg: { + auth: { + order: { + minimax: ["openai:default", "minimax:prod"], + }, + }, + }, + store: { + version: 1, + profiles: { + "openai:default": { + type: "api_key", + provider: "openai", + key: "sk-openai", + }, + "minimax:prod": { + type: "api_key", + provider: "minimax", + key: "sk-mini", + }, + }, + }, + provider: "minimax", + }); + expect(order).toEqual(["minimax:prod"]); + }); + + it("drops token profiles with empty credentials", () => { + const order = resolveAuthProfileOrder({ + cfg: { + auth: { + order: { + minimax: ["minimax:default"], + }, + }, + }, + store: { + version: 1, + profiles: { + "minimax:default": { + type: "token", + provider: "minimax", + token: " ", + }, + }, + }, + provider: "minimax", + }); + expect(order).toEqual([]); + }); + + it("drops token profiles that are already expired", () => { + const order = resolveAuthProfileOrder({ + cfg: { + auth: { + order: { + minimax: ["minimax:default"], + }, + }, + }, + store: { + version: 1, + profiles: { + "minimax:default": { + type: "token", + provider: "minimax", + token: "sk-minimax", + expires: Date.now() - 1000, + }, + }, + }, + provider: "minimax", + }); + expect(order).toEqual([]); + }); + + it("keeps oauth profiles that can refresh", () => { + const order = resolveAuthProfileOrder({ + cfg: { + auth: { + order: { + anthropic: ["anthropic:oauth"], + }, + }, + }, + store: { + version: 1, + profiles: { + "anthropic:oauth": { + type: "oauth", + provider: "anthropic", + access: "", + refresh: "refresh-token", + expires: Date.now() - 1000, + }, + }, + }, + provider: "anthropic", + }); + expect(order).toEqual(["anthropic:oauth"]); + }); + it("does not prioritize lastGood over round-robin ordering", () => { const order = resolveAuthProfileOrder({ cfg, diff --git a/src/agents/auth-profiles.ts b/src/agents/auth-profiles.ts index 6306132c9..9c3d4b200 100644 --- a/src/agents/auth-profiles.ts +++ b/src/agents/auth-profiles.ts @@ -1042,6 +1042,7 @@ export function resolveAuthProfileOrder(params: { }): string[] { const { cfg, store, provider, preferredProfile } = params; const providerKey = normalizeProviderId(provider); + const now = Date.now(); const storedOrder = (() => { const order = store.order; if (!order) return undefined; @@ -1076,7 +1077,36 @@ export function resolveAuthProfileOrder(params: { const filtered = baseOrder.filter((profileId) => { const cred = store.profiles[profileId]; - return cred ? normalizeProviderId(cred.provider) === providerKey : true; + if (!cred) return false; + if (normalizeProviderId(cred.provider) !== providerKey) return false; + const profileConfig = cfg?.auth?.profiles?.[profileId]; + if (profileConfig) { + if (normalizeProviderId(profileConfig.provider) !== providerKey) { + return false; + } + if (profileConfig.mode !== cred.type) { + const oauthCompatible = + profileConfig.mode === "oauth" && cred.type === "token"; + if (!oauthCompatible) return false; + } + } + if (cred.type === "api_key") return Boolean(cred.key?.trim()); + if (cred.type === "token") { + if (!cred.token?.trim()) return false; + if ( + typeof cred.expires === "number" && + Number.isFinite(cred.expires) && + cred.expires > 0 && + now >= cred.expires + ) { + return false; + } + return true; + } + if (cred.type === "oauth") { + return Boolean(cred.access?.trim() || cred.refresh?.trim()); + } + return false; }); const deduped: string[] = []; for (const entry of filtered) { @@ -1089,7 +1119,6 @@ export function resolveAuthProfileOrder(params: { if (explicitOrder && explicitOrder.length > 0) { // ...but still respect cooldown tracking to avoid repeatedly selecting a // known-bad/rate-limited key as the first candidate. - const now = Date.now(); const available: string[] = []; const inCooldown: Array<{ profileId: string; cooldownUntil: number }> = [];