From 79cc4aec8077ad92c1d015b362f047c4de3f53b6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 18 Feb 2026 18:54:40 +0000 Subject: [PATCH] refactor(auth): share oauth result builders and token expiry checks --- src/agents/auth-profiles/oauth.test.ts | 50 ++++++++++++++++++++++++++ src/agents/auth-profiles/oauth.ts | 41 +++++++++++++-------- 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/src/agents/auth-profiles/oauth.test.ts b/src/agents/auth-profiles/oauth.test.ts index 630fbc974a..aeb936d275 100644 --- a/src/agents/auth-profiles/oauth.test.ts +++ b/src/agents/auth-profiles/oauth.test.ts @@ -104,3 +104,53 @@ describe("resolveApiKeyForProfile config compatibility", () => { expect(result).toBeNull(); }); }); + +describe("resolveApiKeyForProfile token expiry handling", () => { + it("returns null for expired token credentials", async () => { + const profileId = "anthropic:token-expired"; + const store: AuthProfileStore = { + version: 1, + profiles: { + [profileId]: { + type: "token", + provider: "anthropic", + token: "tok-expired", + expires: Date.now() - 1_000, + }, + }, + }; + + const result = await resolveApiKeyForProfile({ + cfg: cfgFor(profileId, "anthropic", "token"), + store, + profileId, + }); + expect(result).toBeNull(); + }); + + it("accepts token credentials when expires is 0", async () => { + const profileId = "anthropic:token-no-expiry"; + const store: AuthProfileStore = { + version: 1, + profiles: { + [profileId]: { + type: "token", + provider: "anthropic", + token: "tok-123", + expires: 0, + }, + }, + }; + + const result = await resolveApiKeyForProfile({ + cfg: cfgFor(profileId, "anthropic", "token"), + store, + profileId, + }); + expect(result).toEqual({ + apiKey: "tok-123", + provider: "anthropic", + email: undefined, + }); + }); +}); diff --git a/src/agents/auth-profiles/oauth.ts b/src/agents/auth-profiles/oauth.ts index d0d5dec24c..2e044f27b5 100644 --- a/src/agents/auth-profiles/oauth.ts +++ b/src/agents/auth-profiles/oauth.ts @@ -66,6 +66,24 @@ function buildApiKeyProfileResult(params: { apiKey: string; provider: string; em }; } +function buildOAuthProfileResult(params: { + provider: string; + credentials: OAuthCredentials; + email?: string; +}) { + return buildApiKeyProfileResult({ + apiKey: buildOAuthApiKey(params.provider, params.credentials), + provider: params.provider, + email: params.email, + }); +} + +function isExpiredCredential(expires: number | undefined): boolean { + return ( + typeof expires === "number" && Number.isFinite(expires) && expires > 0 && Date.now() >= expires + ); +} + async function refreshOAuthTokenWithLock(params: { profileId: string; agentDir?: string; @@ -148,9 +166,9 @@ async function tryResolveOAuthProfile(params: { } if (Date.now() < cred.expires) { - return buildApiKeyProfileResult({ - apiKey: buildOAuthApiKey(cred.provider, cred), + return buildOAuthProfileResult({ provider: cred.provider, + credentials: cred, email: cred.email, }); } @@ -205,20 +223,15 @@ export async function resolveApiKeyForProfile(params: { if (!token) { return null; } - if ( - typeof cred.expires === "number" && - Number.isFinite(cred.expires) && - cred.expires > 0 && - Date.now() >= cred.expires - ) { + if (isExpiredCredential(cred.expires)) { return null; } return buildApiKeyProfileResult({ apiKey: token, provider: cred.provider, email: cred.email }); } if (Date.now() < cred.expires) { - return buildApiKeyProfileResult({ - apiKey: buildOAuthApiKey(cred.provider, cred), + return buildOAuthProfileResult({ provider: cred.provider, + credentials: cred, email: cred.email, }); } @@ -240,9 +253,9 @@ export async function resolveApiKeyForProfile(params: { const refreshedStore = ensureAuthProfileStore(params.agentDir); const refreshed = refreshedStore.profiles[profileId]; if (refreshed?.type === "oauth" && Date.now() < refreshed.expires) { - return buildApiKeyProfileResult({ - apiKey: buildOAuthApiKey(refreshed.provider, refreshed), + return buildOAuthProfileResult({ provider: refreshed.provider, + credentials: refreshed, email: refreshed.email ?? cred.email, }); } @@ -282,9 +295,9 @@ export async function resolveApiKeyForProfile(params: { agentDir: params.agentDir, expires: new Date(mainCred.expires).toISOString(), }); - return buildApiKeyProfileResult({ - apiKey: buildOAuthApiKey(mainCred.provider, mainCred), + return buildOAuthProfileResult({ provider: mainCred.provider, + credentials: mainCred, email: mainCred.email, }); }