fix(openai): honor absolute codex jwt expiry fallback

This commit is contained in:
Vincent Koc
2026-04-20 20:50:23 -07:00
committed by Val Alexander
parent 17c1e13d32
commit a028e63324
2 changed files with 44 additions and 2 deletions

View File

@@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { resolveCodexAccessTokenExpiry } from "./openai-codex-auth-identity.js";
import { loginOpenAICodexDeviceCode } from "./openai-codex-device-code.js";
function createJwt(payload: Record<string, unknown>): string {
@@ -88,6 +89,45 @@ describe("loginOpenAICodexDeviceCode", () => {
expect(credentials.expires).toBeGreaterThan(Date.now());
});
it("treats JWT-derived expiry fallback as an absolute timestamp", async () => {
const accessToken = createJwt({
exp: Math.floor(Date.now() / 1000) + 600,
"https://api.openai.com/auth": {
chatgpt_account_id: "acct_123",
},
});
const expectedExpiry = resolveCodexAccessTokenExpiry(accessToken);
const fetchMock = vi
.fn()
.mockResolvedValueOnce(
createJsonResponse({
device_auth_id: "device-auth-123",
user_code: "CODE-12345",
interval: "0",
}),
)
.mockResolvedValueOnce(
createJsonResponse({
authorization_code: "authorization-code-123",
code_verifier: "code-verifier-123",
}),
)
.mockResolvedValueOnce(
createJsonResponse({
access_token: accessToken,
refresh_token: "refresh-token-123",
}),
);
const credentials = await loginOpenAICodexDeviceCode({
fetchFn: fetchMock as typeof fetch,
onVerification: async () => {},
});
expect(expectedExpiry).toBeDefined();
expect(credentials.expires).toBe(expectedExpiry);
});
it("surfaces user-code request failures", async () => {
const fetchMock = vi.fn().mockResolvedValueOnce(new Response(null, { status: 503 }));

View File

@@ -240,9 +240,11 @@ async function exchangeOpenAICodexDeviceCode(params: {
throw new Error("OpenAI token exchange succeeded but did not return OAuth tokens.");
}
const expiresInMs = normalizeTokenLifetimeMs(body?.expires_in);
const expires =
Date.now() +
(normalizeTokenLifetimeMs(body?.expires_in) ?? resolveCodexAccessTokenExpiry(access) ?? 0);
expiresInMs !== undefined
? Date.now() + expiresInMs
: (resolveCodexAccessTokenExpiry(access) ?? Date.now());
const accountId =
resolveCodexChatgptAccountId(access) ?? (idToken && resolveCodexChatgptAccountId(idToken));