193 lines
7.7 KiB
Markdown
193 lines
7.7 KiB
Markdown
# 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 <<EOF
|
|
models:
|
|
default:
|
|
provider: openai
|
|
model: gpt-5.2-codex
|
|
oauth_enabled: true
|
|
telegram:
|
|
bot_token: \${TELEGRAM_BOT_TOKEN}
|
|
allowed_chat_ids: [123456789]
|