Files
ccs/tests/unit/codex-auth/decode-id-token.test.ts
T

137 lines
4.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
// Lazy import so tests can run before implementation
let decodeIdToken: (idToken: string) => { email?: string; plan_type?: string; account_id?: string };
let hasStructurallyValidIdToken: (idToken: string) => boolean;
// Fixture: real-shape JWT with nested claims
// Payload (base64url): {"email":"user@example.com","https://api.openai.com/auth":{"chatgpt_plan_type":"pro","chatgpt_account_id":"4b0448c0-e4a2-4cc0-a70d-77065d613553"}}
const VALID_NESTED_TOKEN = buildToken({
email: 'user@example.com',
'https://api.openai.com/auth': {
chatgpt_plan_type: 'pro',
chatgpt_account_id: '4b0448c0-e4a2-4cc0-a70d-77065d613553',
},
});
// Fixture: email only via profile fallback path
const PROFILE_EMAIL_TOKEN = buildToken({
'https://api.openai.com/profile': { email: 'profile@example.com' },
'https://api.openai.com/auth': { chatgpt_plan_type: 'plus', chatgpt_account_id: 'acct-xyz' },
});
// Fixture: neither top-level email nor profile email — both absent
const NO_EMAIL_TOKEN = buildToken({
sub: 'user-abc',
'https://api.openai.com/auth': {
chatgpt_plan_type: 'free',
chatgpt_account_id: 'acct-123',
},
});
// Fixture: missing plan_type in auth claim
const MISSING_PLAN_TOKEN = buildToken({
email: 'user@example.com',
'https://api.openai.com/auth': {
chatgpt_account_id: 'acct-no-plan',
},
});
// Fixture: missing account_id in auth claim
const MISSING_ACCOUNT_ID_TOKEN = buildToken({
email: 'user@example.com',
'https://api.openai.com/auth': {
chatgpt_plan_type: 'pro',
},
});
// Fixture: no https://api.openai.com/auth claim at all
const NO_AUTH_CLAIM_TOKEN = buildToken({
email: 'user@example.com',
sub: 'user-abc',
});
function buildToken(payload: Record<string, unknown>): string {
const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url');
const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
return `${header}.${body}.fakesig`;
}
beforeEach(async () => {
const mod = await import('../../../src/codex-auth/decode-id-token');
decodeIdToken = mod.decodeIdToken;
hasStructurallyValidIdToken = mod.hasStructurallyValidIdToken;
});
describe('decodeIdToken', () => {
it('extracts email, plan_type, account_id from standard nested JWT', () => {
const result = decodeIdToken(VALID_NESTED_TOKEN);
expect(result.email).toBe('user@example.com');
expect(result.plan_type).toBe('pro');
expect(result.account_id).toBe('4b0448c0-e4a2-4cc0-a70d-77065d613553');
});
it('falls back to profile email when top-level email is absent', () => {
const result = decodeIdToken(PROFILE_EMAIL_TOKEN);
expect(result.email).toBe('profile@example.com');
expect(result.plan_type).toBe('plus');
expect(result.account_id).toBe('acct-xyz');
});
it('returns {} for token with only 2 segments (malformed)', () => {
const result = decodeIdToken('header.payload');
expect(result).toEqual({});
});
it('returns {} for non-base64 garbage input', () => {
const result = decodeIdToken('!!!.%%%.$$$');
expect(result).toEqual({});
});
it('returns empty object when email is absent in both paths', () => {
const result = decodeIdToken(NO_EMAIL_TOKEN);
expect(result.email).toBeUndefined();
expect(result.plan_type).toBe('free');
expect(result.account_id).toBe('acct-123');
});
it('returns undefined plan_type when chatgpt_plan_type is absent', () => {
const result = decodeIdToken(MISSING_PLAN_TOKEN);
expect(result.email).toBe('user@example.com');
expect(result.plan_type).toBeUndefined();
expect(result.account_id).toBe('acct-no-plan');
});
it('returns undefined account_id when chatgpt_account_id is absent', () => {
const result = decodeIdToken(MISSING_ACCOUNT_ID_TOKEN);
expect(result.email).toBe('user@example.com');
expect(result.plan_type).toBe('pro');
expect(result.account_id).toBeUndefined();
});
it('returns partial result when https://api.openai.com/auth claim is absent', () => {
const result = decodeIdToken(NO_AUTH_CLAIM_TOKEN);
expect(result.email).toBe('user@example.com');
expect(result.plan_type).toBeUndefined();
expect(result.account_id).toBeUndefined();
});
it('does not throw on empty string input', () => {
expect(() => decodeIdToken('')).not.toThrow();
expect(decodeIdToken('')).toEqual({});
});
it('reports valid sparse JWT payloads as structurally valid', () => {
expect(hasStructurallyValidIdToken(buildToken({}))).toBe(true);
});
it('rejects JWT segments with invalid base64url characters', () => {
const [header, payload, signature] = buildToken({}).split('.');
expect(hasStructurallyValidIdToken(`${header}.${payload}$.${signature}`)).toBe(false);
expect(hasStructurallyValidIdToken(`${header}=.${payload}.${signature}`)).toBe(false);
});
});