API tokens for programmatic access #23

Open
opened 2026-07-27 13:53:54 +00:00 by mathias · 0 comments
Owner

Problem

Tapir is browser-only. Every entry point sits behind an OIDC session, so a chat session or agent cannot submit a video or read a transcript without a human driving a browser. This blocks the API and MCP work in the follow-up issues.

Scope

Per-user API tokens, resolved to the same user identity as an OIDC session, so downstream handlers keep working against RLS unchanged.

  • New migration 017_api_tokens: id, user_id, name, token_hash, created_at, last_used_at, revoked_at. RLS on, user-scoped.
  • Store only a hash (argon2id or bcrypt). Plaintext shown once at creation, never retrievable.
  • Middleware: Authorization: Bearer <token> resolves to the user identity. Fail-closed — an invalid, revoked, or malformed token is 401 with no user context, never a fallthrough to anonymous.
  • Account page: create, list (name + created + last used, never the token), revoke.
  • Session auth and token auth are alternatives on the same handler chain, not layered.

Non-goals

  • Scopes/permissions. One token = full access for that user. Revisit when a second consumer exists.
  • Machine/service accounts.
  • Token expiry (revocation only for now).

Acceptance

  • caveman: me authenticate API caller as a user, not grant anonymous access
  • Red-first: middleware tests for valid, invalid, revoked, missing, and malformed tokens before implementation
  • RLS tests confirm a token-authenticated request sees exactly the same rows as that user's session
  • Token plaintext appears in exactly one response body and is never logged
  • Account page flows covered
  • docs/use-cases/api_access.feature added

Handoff

Branch from main, one file per commit, squash-merge PR back to main, human review gate.

## Problem Tapir is browser-only. Every entry point sits behind an OIDC session, so a chat session or agent cannot submit a video or read a transcript without a human driving a browser. This blocks the API and MCP work in the follow-up issues. ## Scope Per-user API tokens, resolved to the same user identity as an OIDC session, so downstream handlers keep working against RLS unchanged. - New migration `017_api_tokens`: `id`, `user_id`, `name`, `token_hash`, `created_at`, `last_used_at`, `revoked_at`. RLS on, user-scoped. - Store only a hash (argon2id or bcrypt). Plaintext shown once at creation, never retrievable. - Middleware: `Authorization: Bearer <token>` resolves to the user identity. Fail-closed — an invalid, revoked, or malformed token is 401 with no user context, never a fallthrough to anonymous. - Account page: create, list (name + created + last used, never the token), revoke. - Session auth and token auth are alternatives on the same handler chain, not layered. ## Non-goals - Scopes/permissions. One token = full access for that user. Revisit when a second consumer exists. - Machine/service accounts. - Token expiry (revocation only for now). ## Acceptance - [ ] `caveman: me authenticate API caller as a user, not grant anonymous access` - [ ] Red-first: middleware tests for valid, invalid, revoked, missing, and malformed tokens before implementation - [ ] RLS tests confirm a token-authenticated request sees exactly the same rows as that user's session - [ ] Token plaintext appears in exactly one response body and is never logged - [ ] Account page flows covered - [ ] `docs/use-cases/api_access.feature` added ## Handoff Branch from `main`, one file per commit, squash-merge PR back to `main`, human review gate.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/tapir#23