From d926a15079f9a9fdcbad5e88ef0a97d515152694 Mon Sep 17 00:00:00 2001 From: anserwaseem Date: Fri, 19 Jun 2026 15:25:01 +0500 Subject: [PATCH] fix: improve token expiration logic to prefer JWT expiry over stale config --- src/auth/token.ts | 4 +++- tests/auth/session.test.ts | 32 ++++++++++++++++++++++++++++++++ tests/auth/token.test.ts | 9 ++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/auth/token.ts b/src/auth/token.ts index 45b57af..abee63d 100644 --- a/src/auth/token.ts +++ b/src/auth/token.ts @@ -66,7 +66,9 @@ export function normalizeExpiresAt(expiresAt: unknown): number | undefined { } export function isTokenExpired(idToken: string, expiresAt?: number, bufferSeconds = 60): boolean { - const expiry = normalizeExpiresAt(expiresAt) ?? getIdTokenExpiryMs(idToken); + const jwtExpiry = getIdTokenExpiryMs(idToken); + const configExpiry = normalizeExpiresAt(expiresAt); + const expiry = jwtExpiry ?? configExpiry; if (expiry === undefined) return true; return expiry <= Date.now() + bufferSeconds * 1000; } diff --git a/tests/auth/session.test.ts b/tests/auth/session.test.ts index b800664..cddf69e 100644 --- a/tests/auth/session.test.ts +++ b/tests/auth/session.test.ts @@ -182,6 +182,38 @@ describe('getValidAuthSession', () => { } }); + it('returns ok without refresh when jwt is valid but config expiresAt is stale', async () => { + const token = makeJwt({ + userId: 'u1', + email: 'a@b.com', + exp: Math.floor(Date.now() / 1000) + 3600, + }); + vi.mocked(globalConfig.readGlobalConfig).mockResolvedValue({ + user: { + uid: 'u1', + email: 'a@b.com', + idToken: token, + refreshToken: 'refresh-123', + expiresAt: Date.now() - 3600_000, + }, + }); + + const fetchMock = vi.fn(); + const originalFetch = globalThis.fetch; + globalThis.fetch = fetchMock; + + const result = await getValidAuthSession(); + + globalThis.fetch = originalFetch; + + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.idToken).toBe(token); + expect(result.refreshed).toBe(false); + } + expect(fetchMock).not.toHaveBeenCalled(); + }); + it('refreshes token when expired and refresh token exists', async () => { const oldToken = makeJwt({ userId: 'u1', diff --git a/tests/auth/token.test.ts b/tests/auth/token.test.ts index 21e6cb8..ec03063 100644 --- a/tests/auth/token.test.ts +++ b/tests/auth/token.test.ts @@ -146,10 +146,17 @@ describe('isTokenExpired', () => { expect(isTokenExpired(token, undefined, 0)).toBe(false); }); - it('uses expiresAt when provided', () => { + it('uses expiresAt when jwt has no exp claim', () => { vi.setSystemTime(new Date('2025-01-01T12:00:00Z')); const token = makeJwt({ userId: 'u1' }); const futureMs = new Date('2025-06-01').getTime(); expect(isTokenExpired(token, futureMs)).toBe(false); }); + + it('prefers jwt exp over stale config expiresAt', () => { + vi.setSystemTime(new Date('2025-01-01T12:00:00Z')); + const token = makeJwt({ userId: 'u1', exp: 1735736400 }); + const stalePastMs = new Date('2024-01-01').getTime(); + expect(isTokenExpired(token, stalePastMs)).toBe(false); + }); });