feat(auth): F1 — login page + auth gate + session middleware #502
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!502
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "boss/480"
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?
Wires an opt-in session-cookie gate in front of every operator-facing route, plus the SPA login surface and the cookie + DB plumbing F3 will issue cookies into. OAuth providers themselves arrive in F2 / F3.
Test plan
bun x turbo run typecheckcleanbun x turbo run test— 2341 server tests pass (the 2 pre-existingagent-runnerfailures are unrelated, present onmain)apps/webvitest — 591 pass (incl. 4 newlogin.test.tsxcases)bun x @biomejs/biome check .exits 0session-gate.test.tscovers the AC end-to-end (/board302/401 split, valid-cookie 200, exempt routes,/logoutcookie clearing,/whoamisession echo)webhook-config.test.tscoverspublic_base_urlHTTPS-only validation +auth.session_gate_enabledoverrideCloses #480
Adds an opt-in session-cookie gate that requires sign-in before any operator-facing route is reachable. The actual OAuth providers (F2 / F3) land separately; this commit wires the gate, the SPA login surface, and the cookie + DB plumbing F3 will issue cookies into. - `operator_sessions` SQLite table + helpers (`createOperatorSession`, `getOperatorSession`, `touchOperatorSession`, `deleteOperatorSession`, `sweepExpiredOperatorSessions`). 32-byte CSPRNG hex ids, 30 d sliding TTL, daily background sweep. - `http/session-middleware.ts` — cookie parser, exempt-path predicate (webhooks, OAuth flow, /login, /logout, SPA login + assets, /health, PWA), middleware factory; rejects with 302→/login on `Accept: text/html` and 401 JSON `{ error: "unauthenticated" }` otherwise. - `GET /login` 302s to `/app/login`; `POST /logout` deletes the row, clears the `claude-hooks-session` cookie (HttpOnly, Secure, SameSite=Lax), and 302s to `/login`. Exempt from the gate. - `apps/web/src/routes/login.tsx` — three anchors to `/oauth/<forge>/init?return=<here>` for Forgejo / GitHub / GitLab. - `public_base_url` config field + boot-time validation (must be `https://`); presence enables the gate by default. Operators can override via `auth.session_gate_enabled`. - `/whoami` now returns `session.{forge_type, account_login}` so the SPA can render the signed-in identity without a second round-trip. Closes #480 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>c36648c8a6c05bdb867dAll ACs from #480 verified against the diff. CI green (run #2398, 9m10s).
Nit (non-blocking): the comment in
clientWantsHtmlsays*/*"both qualify" but the regex only matchestext/html— the code is correct per the AC, just the prose is slightly misleading.c05bdb867d1d0bf8ad671d0bf8ad677f4991c148