feat(auth): enforce authentication across the v2 API (#336) - #610
Conversation
- require_auth (JWT or X-API-Key) mounted as a router-level dependency on all 22 v2 REST routers + /test/broadcast; WS routers keep their own ?token= auth; auth/login/register and health endpoints stay public - env-gated via CODEFRAME_AUTH_REQUIRED (default ON, secure by default; read at request time); when disabled require_auth yields a synthetic local-admin principal so local/dev use is unchanged - get_current_user accepts ?token=<JWT> query param fallback when no Authorization header is present (enables browser EventSource/SSE, mirroring the WebSocket pattern) - /auth/register gated to bootstrap-first-user: 403 once a real (login-capable) account exists; seeded !DISABLED! admin doesn't count - test suite runs with CODEFRAME_AUTH_REQUIRED=false via conftest setdefault; new enforcement tests opt back in (111 new tests)
- lib/auth.ts: login (form-encoded /auth/jwt/login), bootstrap register, logout, token storage under the existing auth_token localStorage key, withTokenParam() for EventSource URLs - api.ts: request interceptor attaches Authorization: Bearer; 401 response clears token and redirects to /login (loop-safe) - useTaskStream/useStressTestStream append ?token= to SSE URLs - /login page (Shadcn + Hugeicons): sign-in + first-account creation, renders bare via AppLayout BARE_ROUTES; logout in sidebar footer
Codex P1: /auth/* was not proxied by the Next.js rewrite (only /api/*), so login/register 404'd with the default empty NEXT_PUBLIC_API_URL — added an /auth/:path* rewrite. Codex P2: bootstrap registration had a TOCTOU window (count check and user INSERT in separate transactions) — allow_registration is now a yield dependency holding an in-process asyncio.Lock until the request completes; concurrent-registration test asserts exactly one 201 + one 403 (verified to fail without the lock). Multi-worker deployments retain a narrow race (documented in the code comment). Internal review: enforcement matrix now covers review_v2 and the SSE route under the tasks prefix (22/22 routers), plus an end-to-end X-API-Key test (valid key passes, bogus key 401s).
Codex round-2 P2: the query-param JWT fallback applied to every consumer
of get_current_user, making URL credentials a full-API auth mechanism.
Now only the two browser-EventSource routes accept it
(/api/v2/tasks/{id}/stream, /api/v2/prd/stress-test); everywhere else a
query token yields 401. Test contract updated accordingly: the old
any-route query-token expectation was wrong per this review finding —
tests now assert acceptance on SSE paths and rejection elsewhere.
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
|
Warning Review limit reached
More reviews will be available in 8 minutes and 22 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (26)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code Review — feat(auth): enforce authentication across the v2 API (#336)This is a well-structured, security-conscious implementation. The design choices — router-level enforcement via a shared Findings1. GitGuardian flag — likely a false positive, but confirm GitGuardian triggered on 2. The lock is held from dependency entry until the 3. No server-side logout (JWT stays valid after logout)
4. JWT stored in
5. JWT in SSE query string logged by proxies/servers As documented, the 6.
7. Minor:
8.
What's done well
SummaryNo blocking issues. Findings 1 (GitGuardian confirm), 7 (constant dedup), and 8 ( Overall this is a solid, well-tested auth enforcement implementation. LGTM pending the minor items above. |
…false positive (#336) - auth_app fixture now creates its own initialized database with the test user and resets the auth engine — it previously leaned on the dev machine's ambient DATABASE_PATH (passed locally, failed in CI with 'no such table: users') - auth.test.ts parses the form body instead of substring-matching a 'password=...' literal, which tripped GitGuardian's generic-password detector
|
Follow-up Review Building on my prior review, this pass covers the complete frontend implementation now visible in the diff. The backend work remains solid; the frontend additions are clean and consistent. auth.ts observations:
api.ts 401 handler:
AppLayout: The BARE_ROUTES = new Set(['/login']) pattern is clean and easy to extend without touching the render tree. Minor nit: onClick={() => logout()} in AppSidebar.tsx can just be onClick={logout}. The wrapper lambda is a no-op. Not worth a blocking change. Status of Prior Findings:
The one thing to fix before merge: The GitGuardian alert is a false positive - the flagged string is test mock data, not a real credential. Add a GitGuardian inline suppression on the flagged line in web-ui/src/tests/lib/auth.test.ts (around line 68): The author tried to mitigate by parsing URLSearchParams rather than substring-matching a password=... literal, but GitGuardian still triggers on the asserted value. The inline comment suppresses it cleanly. Overall: Complete, well-tested implementation. Design is correct, coverage is strong (119 tests, parametrized router matrix, concurrent-registration TOCTOU test), and the deferred items are documented. Recommend merging after resolving the GitGuardian flag. |
- server.py startup log + OpenAPI description now describe the env-gated default-on auth mode, public endpoints, and SSE query-token scope - CLAUDE.md current-focus records the shipped #336 architecture
|
Follow-up Review for PR 610 — feat(auth): enforce authentication across the v2 API (#336) Reviewing against my prior comment and the 6 commits on this branch. Prior Findings — Status
New Observation
Summary All prior findings addressed. 22/22 v2 routers covered, TOCTOU-safe bootstrap registration, SSE-only query-token allowlist, full frontend login flow, 119 new tests including the parametrized router enforcement matrix and concurrent-registration race test. LGTM. |
Summary
Implements #336: authentication enforcement across the v2 API — backend enforcement, SSE token auth, JWT login web UI, and test migration.
require_auth(JWT Bearer orX-API-Key) mounted as a router-level dependency on all 22 v2 REST routers +/test/broadcast. Env-gated viaCODEFRAME_AUTH_REQUIRED(default ON, read at request time); when disabled, a synthetic local-admin principal preserves current local behavior. Public:/,/health, OpenAPI docs,/auth/*.?token=<JWT>accepted only on the two EventSource routes (/api/v2/tasks/{id}/stream,/api/v2/prd/stress-test) via a strict path allowlist; rejected everywhere else. WS routers keep their existing?token=auth (untouched)./auth/registeris bootstrap-first-user only — 403 once a real account exists (seeded!DISABLED!admin excluded); check-then-create serialized with an in-process lock (TOCTOU guard)./loginpage (sign-in + first-account bootstrap), axios interceptor attachingAuthorization: Bearerfrom the existingauth_tokenlocalStorage convention, loop-safe 401 →/loginredirect, SSE hooks append the token, logout in sidebar;/auth/*added to the Next.js rewrite proxy.Acceptance Criteria
/api/v2/*route 401s without credentials; passes with valid JWT and with valid API key (parametrized over all 22 routers, end-to-end X-API-Key test)?token=(EventSource-compatible); WS unchanged/,/health, docs,/auth/jwt/login,/auth/registerremain public;/test/broadcastnow requires auth/login/api/projects+ 6 environment-dependent e2e/lifecycle); ruff cleannpm run buildgreenTest Plan
Known Limitations / Intentionally Deferred
JWT_LIFETIME_SECONDS); users re-login on expiry.codeframe serve→ login page → "create first account"). CLI is unaffected. Local opt-out:CODEFRAME_AUTH_REQUIRED=false.Implementation Notes
streaming_v2.pyholds SSE utilities; the actual SSE route lives under the tasks prefix and is covered bytasks_v2router auth.Closes #336