# OpenAI OAuth Implementation Summary ## Goal Enable Flynn to use OpenAI models (Codex) via ChatGPT Plus/Pro OAuth tokens instead of API keys. ## Minimal Viable Approach **Device Flow + Token Refresh + Codex Endpoint Routing** Based on OpenCode's proven `CodexAuthPlugin` implementation. --- ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ User: flynn login openai │ └────────────────┬────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 1. Device Flow (src/auth/openai.ts) │ │ - Request device code from auth.openai.com │ │ - Display user_code + URL │ │ - Poll for authorization │ │ - Exchange code for tokens (access, refresh, id) │ │ - Extract account_id from JWT claims │ │ - Store to ~/.config/flynn/auth.json (chmod 600) │ └────────────────┬────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 2. Config Update (config.yaml) │ │ models: │ │ default: │ │ provider: openai │ │ model: gpt-5.2-codex │ │ oauth_enabled: true ← NEW │ └────────────────┬────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 3. Daemon Startup (src/daemon/models.ts) │ │ - createClientFromConfig() detects oauth_enabled │ │ - Passes tokenLoader + tokenSaver callbacks │ │ - OpenAIClient created with OAuth mode │ └────────────────┬────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 4. Chat Request (src/models/openai.ts) │ │ - Custom fetch() interceptor checks token expiry │ │ - Auto-refresh if needed via refreshOpenAIToken() │ │ - Set headers: │ │ * Authorization: Bearer {access_token} │ │ * ChatGPT-Account-Id: {account_id} │ │ * originator: flynn │ │ - Rewrite URL to Codex endpoint for codex/gpt-5.x models │ │ - Execute request │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## File Changes | File | Type | Lines | Purpose | |------|------|-------|---------| | `src/auth/openai.ts` | **NEW** | ~300 | Device flow + token management | | `src/auth/index.ts` | MODIFY | +8 | Export openai.ts functions | | `src/models/openai.ts` | MODIFY | +100 | OAuth support + custom fetch | | `src/config/schema.ts` | MODIFY | +1 | Add `oauth_enabled` field | | `src/daemon/models.ts` | MODIFY | +15 | Wire up OAuth callbacks | | `src/cli/commands/login.ts` | **NEW** | ~80 | `flynn login openai` command | | `src/cli/index.ts` | MODIFY | +2 | Register login command | | `src/auth/openai.test.ts` | **NEW** | ~150 | Unit tests | **Total**: ~650 lines of new code + ~130 lines of modifications. --- ## API Flow ### 1. Login Flow ``` flynn login openai │ ├─► POST https://auth.openai.com/api/accounts/deviceauth/usercode │ Body: { client_id: "app_EMoamEEZ73f0CkXaXp7hrann" } │ Response: { device_auth_id, user_code, interval } │ ├─► Display: "Visit https://auth.openai.com/codex/device" │ "Enter code: ABCD-1234" │ ├─► Poll POST https://auth.openai.com/api/accounts/deviceauth/token │ Body: { device_auth_id, user_code } │ Response (authorized): { authorization_code, code_verifier } │ ├─► POST https://auth.openai.com/oauth/token │ Body: { grant_type: authorization_code, code, code_verifier, ... } │ Response: { id_token, access_token, refresh_token, expires_in } │ ├─► Parse JWT to extract account_id │ └─► Save to ~/.config/flynn/auth.json ``` ### 2. Request Flow ``` User message via Telegram/TUI │ ├─► modelRouter.chat(request) │ ├─► OpenAIClient.chat() │ ├─► Custom fetch() interceptor: │ ├─► Load token from auth.json │ ├─► Check expires_at < Date.now() │ ├─► If expired: POST refresh_token to /oauth/token │ ├─► Set Authorization header │ ├─► Rewrite URL to Codex endpoint │ └─► fetch(codex_url, { headers }) │ └─► Response returned to agent ``` --- ## Key Endpoints | Purpose | Endpoint | |---------|----------| | Device code | `https://auth.openai.com/api/accounts/deviceauth/usercode` | | Poll auth | `https://auth.openai.com/api/accounts/deviceauth/token` | | Token exchange | `https://auth.openai.com/oauth/token` | | Token refresh | `https://auth.openai.com/oauth/token` | | Codex API | `https://chatgpt.com/backend-api/codex/responses` | --- ## Configuration ### Before (API Key) ```yaml models: default: provider: openai model: gpt-4 api_key: sk-proj-... ``` ### After (OAuth) ```yaml models: default: provider: openai model: gpt-5.2-codex # or gpt-5.3-codex, gpt-5.1-codex-max oauth_enabled: true ``` --- ## Testing Strategy ### Unit Tests ```bash pnpm test src/auth/openai.test.ts ``` Tests: - JWT parsing (valid/invalid) - Account ID extraction (multiple claim locations) - Token storage/loading ### Manual Integration Test ```bash # 1. Login flynn login openai # 2. Verify token stored cat ~/.config/flynn/auth.json | jq '.openai' # 3. Create test config cat > /tmp/flynn-oauth-test.yaml <