| 1 | # RFC: Unified provider login (`codewhale auth login`) |
| 2 | |
| 3 | **Status:** Future RFC — approved direction (maintainer, 2026-07-12); |
| 4 | implementation is deferred beyond v0.9.0. |
| 5 | **Owner seams verified against:** v0.9.0 candidate tree. |
| 6 | |
| 7 | ## Decision |
| 8 | |
| 9 | One login surface, one grammar, for every provider that offers an |
| 10 | interactive auth flow: |
| 11 | |
| 12 | ``` |
| 13 | codewhale auth login --provider <anthropic|openai-codex|xai|...> |
| 14 | /auth <provider> |
| 15 | ``` |
| 16 | |
| 17 | Today the tree has one interactive flow — xAI device-code OAuth |
| 18 | (`crates/tui/src/xai_oauth.rs`, `codewhale auth xai-device`, `/auth |
| 19 | xai-device`, #4257) — shipped as a provider-specific command. That shape |
| 20 | does not scale: Anthropic (Claude Pro/Max browser OAuth) and OpenAI Codex |
| 21 | OAuth are next, and each provider growing its own verb produces a different |
| 22 | command per provider for the same user intent. |
| 23 | |
| 24 | `auth login` becomes the canonical entry; `auth xai-device` stays as a |
| 25 | compatibility alias for at least one release. |
| 26 | |
| 27 | ## Shared contract (per provider adapter) |
| 28 | |
| 29 | Every login adapter implements the same lifecycle, so the CLI, `/auth`, the |
| 30 | provider picker row, `auth status`, and logout behave identically: |
| 31 | |
| 32 | 1. **Initiate** — browser OAuth (authorization-code + PKCE, loopback |
| 33 | callback, random state) or device-code flow, chosen by the provider |
| 34 | adapter. Manual code-paste fallback for SSH/headless. |
| 35 | 2. **Store** — structured credential (`access_token`, `refresh_token`, |
| 36 | `expires_at`, `auth_mode`) in a dedicated auth store (0600, atomic |
| 37 | writes) or keyring entry. Never written into `config.toml` as `api_key` |
| 38 | — the static-key storage and logout paths are not designed for |
| 39 | refreshable tokens. |
| 40 | 3. **Resolve** — provider/auth resolution recognizes `auth_mode = "oauth"` |
| 41 | as valid auth without an API key, classifies the source distinctly |
| 42 | (not `missing`, not `config`), and refreshes before use. Seams: |
| 43 | `crates/tui/src/config.rs` auth resolution, `crates/config/src/ |
| 44 | provider.rs`, the shared runtime resolver in `crates/config/src/lib.rs`. |
| 45 | 4. **Send** — per-provider header mode. Anthropic OAuth uses |
| 46 | `Authorization: Bearer` plus the required beta/identity headers and must |
| 47 | not also send `x-api-key` (`crates/tui/src/client.rs` header |
| 48 | construction). Clients capture credentials at construction today, so |
| 49 | long-running sessions need request-time refresh or a rebuild path. |
| 50 | 5. **Status / logout** — `auth status` shows mode, source, and expiry |
| 51 | without exposing tokens; `logout` removes OAuth credentials and the auth |
| 52 | mode, tells the user how to log in again after failed refresh, and never |
| 53 | unsets shell-managed environment variables. |
| 54 | 6. **Tests** — PKCE/state rejection, callback success/cancel/timeout, |
| 55 | mock-HTTP token exchange and refresh, file permissions/atomicity, |
| 56 | API-key-vs-OAuth header selection, picker readiness, logout/status, |
| 57 | 401-recovery. No real credentials in CI. |
| 58 | |
| 59 | ## Hard gate before Anthropic implementation |
| 60 | |
| 61 | Do not copy OAuth constants, client IDs, scopes, or Claude-Code-specific |
| 62 | headers from reference implementations (e.g. Pi's |
| 63 | `packages/ai/src/utils/oauth/anthropic.ts`) without first verifying that |
| 64 | CodeWhale is permitted to use that flow. Those details may be |
| 65 | client-specific or governed by Anthropic compatibility policy. This |
| 66 | verification is an explicit maintainer action and blocks the Anthropic |
| 67 | adapter, not the shared `auth login` scaffolding. |
| 68 | |
| 69 | ## Out of scope |
| 70 | |
| 71 | - Changing existing API-key authentication (unchanged, first-class). |
| 72 | - Hosted/remote token brokering. |
| 73 | - Any billing/usage-limit interpretation beyond surfacing the provider's |
| 74 | own messaging that subscription OAuth may differ from API billing. |
| 75 |