F2 — operator_oauth_tokens table + active-forge router #481

Closed
opened 2026-04-27 21:42:41 +00:00 by claude-desktop · 0 comments
Collaborator

As a maintainer, I want OAuth state and active-forge selection to live in the database, so that sessions, watched repos, and adapters all read from one place.

Acceptance criteria

  • operator_oauth_tokens schema added to db.ts with forge_type (PK), access_token, refresh_token, expires_at, scopes, account_login, account_id, base_url, created_at, updated_at.
  • Helpers: getOperatorOAuth(forge), upsertOperatorOAuth(row), deleteOperatorOAuth(forge).
  • service_settings gains active_forge_type key (TEXT, nullable). Helpers getActiveForge(), setActiveForge(forge), clearActiveForge().
  • After an OAuth callback succeeds, the row is upserted and setActiveForge() is called atomically in one transaction.
  • getOperatorAdapter() returns a ForgePort bound to the active forge's OAuth token, or throws NoActiveForgeError. The error maps to a 412 Precondition Failed at the HTTP layer.
  • Tokens encrypted at rest with libsodium secretbox keyed by OAUTH_ENCRYPTION_KEY. Boot fails if the key is missing or shorter than 32 bytes.
  • Decryption helper redacts on log lines.
  • GET /me returns { forge_type, account_login, base_url, since } for the active forge, or 401 if no session.
  • Switching forges: a successful OAuth callback for forge B while forge A is active replaces active_forge_type and issues a fresh session cookie. Forge A's token row is NOT deleted.

Out of scope

  • Per-forge OAuth implementation — see F3 / F3-GH / F3-GL.
  • Login page UI / session middleware — F1.

References

  • Spec: docs/specs/forge-auth-repo-selection.md §F2, §5, §10

Dependencies

  • None. Foundation story; F1, F3, F4 all depend on this.
As a maintainer, I want OAuth state and active-forge selection to live in the database, so that sessions, watched repos, and adapters all read from one place. ## Acceptance criteria - [ ] `operator_oauth_tokens` schema added to `db.ts` with `forge_type` (PK), `access_token`, `refresh_token`, `expires_at`, `scopes`, `account_login`, `account_id`, `base_url`, `created_at`, `updated_at`. - [ ] Helpers: `getOperatorOAuth(forge)`, `upsertOperatorOAuth(row)`, `deleteOperatorOAuth(forge)`. - [ ] `service_settings` gains `active_forge_type` key (TEXT, nullable). Helpers `getActiveForge()`, `setActiveForge(forge)`, `clearActiveForge()`. - [ ] After an OAuth callback succeeds, the row is upserted and `setActiveForge()` is called atomically in one transaction. - [ ] `getOperatorAdapter()` returns a `ForgePort` bound to the active forge's OAuth token, or throws `NoActiveForgeError`. The error maps to a 412 Precondition Failed at the HTTP layer. - [ ] Tokens encrypted at rest with libsodium secretbox keyed by `OAUTH_ENCRYPTION_KEY`. Boot fails if the key is missing or shorter than 32 bytes. - [ ] Decryption helper redacts on log lines. - [ ] `GET /me` returns `{ forge_type, account_login, base_url, since }` for the active forge, or 401 if no session. - [ ] Switching forges: a successful OAuth callback for forge B while forge A is active replaces `active_forge_type` and issues a fresh session cookie. Forge A's token row is NOT deleted. ## Out of scope - Per-forge OAuth implementation — see F3 / F3-GH / F3-GL. - Login page UI / session middleware — F1. ## References - Spec: `docs/specs/forge-auth-repo-selection.md` §F2, §5, §10 ## Dependencies - None. Foundation story; F1, F3, F4 all depend on this.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#481
No description provided.