fix(api): let native clients manage Personal Access Tokens (#386) #387
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!387
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "386-native-pat-management"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #386
The PAT-management endpoints gated on a cookie session, so native clients (Android, Linux Flatpak) — which authenticate with the bearer access token
cat_…(ADR-0027) and hold no cookie — got 401 on the account → tokens screen. The UI (apps/client/app/(app)/account.tsx) exists and works on web; it was simply locked out natively.Why this is safe
ADR-0021 made token-management session-only to enforce "a stolen PAT can't re-provision itself". That carve-out predates native access tokens, which are the interactive-sign-in equivalent of a cookie session: 15-min TTL, minted only by the
passwordor OAuthauthorization_codegrant onPOST /api/auth/token. The threat model never required a cookie specifically — it required an interactive credential and the exclusion of PATs. Admitting the access token preserves the model verbatim: a stolen PAT still cannot list, create, or revoke tokens.What changed
lib/auth/identity.ts: newgetInteractiveIdentity(req)— acceptssession+access_token, rejectspatand unauthenticated.GET/POST /api/account/tokens,DELETE /api/account/tokens/{id}):getSession→getInteractiveIdentity.accessTokenAuthscheme so the routes advertise cookie or native access token, not a PAT precisely. Regeneratedopenapi.json+@carol/api-client.Amendednote, forward-pointer at the Decision section, full Update section) cross-referencing ADR-0027.Tests (
tests/api/account-tokens.test.ts)Verification
TEST_POSTGRES_URL);openapi:check+openapi:coverage+@carol/api-client checkall green; full CI semgrep pack set (99 rules) 0 findings.🤖 Generated with Claude Code
The PAT-management endpoints (GET/POST /api/account/tokens, DELETE /api/account/tokens/{id}) gated on a cookie session, so native clients (Android, Linux Flatpak) — which authenticate with the bearer access token cat_… (ADR-0027) and hold no cookie — got 401 on the account → tokens screen. The UI exists and works on web; it was simply locked out natively. ADR-0021 made token-management session-only to enforce "a stolen PAT can't re-provision itself". That predates native access tokens, which are the interactive-sign-in equivalent of a cookie session (15-min TTL, minted only by the password / OAuth authorization_code grant). The threat model never required a cookie specifically — it required an interactive credential and the exclusion of PATs. - lib/auth/identity.ts: add getInteractiveIdentity(req) — accepts session + access_token, rejects pat and unauthenticated. - The three token-management handlers: getSession → getInteractiveIdentity. A stolen PAT still can't list/create/revoke tokens (verbatim threat model); native clients regain PWA parity. - OpenAPI: a dedicated accessTokenAuth scheme so the three routes advertise "cookie or native access token, not a PAT" precisely; regenerated openapi.json + @carol/api-client. - ADR-0021: amended with a native-access-token update referencing ADR-0027. - Tests: access token can list/create/revoke; PAT bearer is refused 401 on all three; cross-user revoke via access token still 404s. No client change — the screen works natively once the endpoints accept the access token. Closes #386 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.