7.7 KiB
7.7 KiB
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)
models:
default:
provider: openai
model: gpt-4
api_key: sk-proj-...
After (OAuth)
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
pnpm test src/auth/openai.test.ts
Tests:
- JWT parsing (valid/invalid)
- Account ID extraction (multiple claim locations)
- Token storage/loading
Manual Integration Test
# 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 <<EOF
models:
default:
provider: openai
model: gpt-5.2-codex
oauth_enabled: true
telegram:
bot_token: \${TELEGRAM_BOT_TOKEN}
allowed_chat_ids: [123456789]