Files
diegosouzapw--omniroute/tests/unit/codex-auth-import-expiry.test.ts
2026-07-13 13:39:12 +08:00

88 lines
3.7 KiB
TypeScript

import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { parseAndValidateCodexAuth } from "@/lib/oauth/utils/codexAuthImport";
/**
* Guards fix for issue #6075:
* Importing a Codex auth.json where id_token.exp is already expired (but
* access_token.exp is still valid) created a broken connection because
* extractExpiresAt() used id_token.exp — triggering an immediate refresh
* that could invalidate the entire token family.
*
* Fix: extractExpiresAt now prefers access_token.exp over id_token.exp.
*/
// Helpers to build minimal signed-looking JWTs for testing.
// We only need valid base64url-encoded payloads; signature doesn't matter for parsing.
function buildFakeJwt(payload: Record<string, unknown>): string {
const encoded = Buffer.from(JSON.stringify(payload)).toString("base64url");
return `header.${encoded}.sig`;
}
const FUTURE = Math.floor(Date.now() / 1000) + 86400; // now + 1 day
const PAST = Math.floor(Date.now() / 1000) - 86400; // now - 1 day
const BASE_ACCOUNT_ID = "acc_test123";
const BASE_PAYLOAD_AUTH = { "https://api.openai.com/auth": { account_id: BASE_ACCOUNT_ID } };
function makeTokens(opts: { accessExp?: number | null; idExp?: number | null }) {
const accessPayload = { ...(opts.accessExp !== undefined ? { exp: opts.accessExp } : {}) };
const idPayload = {
email: "user@example.com",
...(opts.idExp !== undefined ? { exp: opts.idExp } : {}),
...BASE_PAYLOAD_AUTH,
};
return {
id_token: buildFakeJwt(idPayload),
access_token: buildFakeJwt(accessPayload),
refresh_token: "rt_valid_token",
};
}
// tolerance in seconds when comparing derived expiry to the source exp claim
const TOLERANCE_S = 100;
describe("parseAndValidateCodexAuth — expiresAt derivation", () => {
it("uses access_token.exp when it is valid even if id_token.exp is expired", () => {
const tokens = makeTokens({ accessExp: FUTURE, idExp: PAST });
const result = parseAndValidateCodexAuth({ tokens });
// expiresAt should reflect the access token's future expiry, not the past id_token expiry
assert.ok(result.expiresAt, "expected a non-null expiresAt");
const parsed = new Date(result.expiresAt!).getTime() / 1000;
assert.ok(Math.abs(parsed - FUTURE) < TOLERANCE_S, `expected ~${FUTURE}, got ${parsed}`);
});
it("falls back to id_token.exp when access_token has no exp claim", () => {
// access_token has no exp field, id_token has a future exp
const tokens = makeTokens({ accessExp: null, idExp: FUTURE });
const result = parseAndValidateCodexAuth({ tokens });
assert.ok(result.expiresAt, "expected a non-null expiresAt");
const parsed = new Date(result.expiresAt!).getTime() / 1000;
assert.ok(Math.abs(parsed - FUTURE) < TOLERANCE_S, `expected ~${FUTURE}, got ${parsed}`);
});
it("returns null expiresAt when neither token has an exp claim", () => {
const tokens = makeTokens({ accessExp: null, idExp: null });
const result = parseAndValidateCodexAuth({ tokens });
assert.equal(result.expiresAt, null);
});
it("uses access_token.exp when both tokens have future expiries", () => {
const accessExp = FUTURE + 3600;
const idExp = FUTURE;
const tokens = makeTokens({ accessExp, idExp });
const result = parseAndValidateCodexAuth({ tokens });
const parsed = new Date(result.expiresAt!).getTime() / 1000;
assert.ok(Math.abs(parsed - accessExp) < TOLERANCE_S, `expected ~${accessExp}, got ${parsed}`);
});
it("preserves refresh_token from the auth.json (does not null it out)", () => {
const tokens = makeTokens({ accessExp: FUTURE, idExp: PAST });
const result = parseAndValidateCodexAuth({ tokens });
assert.equal(result.refreshToken, "rt_valid_token");
});
});