feat: add codex oauth login flow
This commit is contained in:
83
apps/api/src/codex/codex-oauth.spec.ts
Normal file
83
apps/api/src/codex/codex-oauth.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
buildCodexAuthorizeUrl,
|
||||
createPkcePair,
|
||||
extractCodexIdentity,
|
||||
renderCodexOauthCallbackHtml,
|
||||
} from './codex-oauth';
|
||||
|
||||
describe('codex-oauth helpers', () => {
|
||||
it('builds the OpenAI authorize URL with Codex-specific PKCE parameters', () => {
|
||||
const url = new URL(
|
||||
buildCodexAuthorizeUrl({
|
||||
state: 'state-123',
|
||||
codeChallenge: 'challenge-abc',
|
||||
redirectUri: 'http://localhost:1455/auth/callback',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(url.origin + url.pathname).toBe(
|
||||
'https://auth.openai.com/oauth/authorize',
|
||||
);
|
||||
expect(url.searchParams.get('response_type')).toBe('code');
|
||||
expect(url.searchParams.get('client_id')).toBe(
|
||||
'app_EMoamEEZ73f0CkXaXp7hrann',
|
||||
);
|
||||
expect(url.searchParams.get('redirect_uri')).toBe(
|
||||
'http://localhost:1455/auth/callback',
|
||||
);
|
||||
expect(url.searchParams.get('scope')).toBe(
|
||||
'openid profile email offline_access',
|
||||
);
|
||||
expect(url.searchParams.get('code_challenge')).toBe('challenge-abc');
|
||||
expect(url.searchParams.get('code_challenge_method')).toBe('S256');
|
||||
expect(url.searchParams.get('state')).toBe('state-123');
|
||||
expect(url.searchParams.get('originator')).toBe('codex_cli_rs');
|
||||
expect(url.searchParams.get('codex_cli_simplified_flow')).toBe('true');
|
||||
expect(url.searchParams.get('id_token_add_organizations')).toBe('true');
|
||||
});
|
||||
|
||||
it('creates a valid PKCE verifier/challenge pair', () => {
|
||||
const pair = createPkcePair(Buffer.alloc(32, 7));
|
||||
|
||||
expect(pair.verifier).toBe('BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc');
|
||||
expect(pair.challenge).toBe('3Ev4DHdHPRMPoN6GukAY_pi7IUAF5qWJHRK6kURvnoE');
|
||||
});
|
||||
|
||||
it('extracts account identity fields from the id token payload', () => {
|
||||
const header = Buffer.from(JSON.stringify({ alg: 'none' })).toString(
|
||||
'base64url',
|
||||
);
|
||||
const payload = Buffer.from(
|
||||
JSON.stringify({
|
||||
email: 'operator@example.com',
|
||||
exp: 1_800_000_000,
|
||||
'https://api.openai.com/auth': {
|
||||
chatgpt_account_id: 'acct_123',
|
||||
chatgpt_plan_type: 'pro',
|
||||
},
|
||||
}),
|
||||
).toString('base64url');
|
||||
const token = `${header}.${payload}.signature`;
|
||||
|
||||
expect(extractCodexIdentity(token)).toEqual({
|
||||
email: 'operator@example.com',
|
||||
accountId: 'acct_123',
|
||||
planType: 'pro',
|
||||
expiresAt: new Date(1_800_000_000 * 1000),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders callback html that reports completion back to the frontend window', () => {
|
||||
const html = renderCodexOauthCallbackHtml({
|
||||
attemptId: 'attempt_123',
|
||||
status: 'success',
|
||||
frontendOrigin: 'http://localhost:5173',
|
||||
message: 'Connected successfully',
|
||||
});
|
||||
|
||||
expect(html).toContain('codexdash:oauth-complete');
|
||||
expect(html).toContain('attempt_123');
|
||||
expect(html).toContain('Connected successfully');
|
||||
expect(html).toContain('http://localhost:5173');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user