Skip to content

feat: cookie-based auth with admin/viewer access control#20

Merged
Rio517 merged 16 commits intomainfrom
auth
Mar 13, 2026
Merged

feat: cookie-based auth with admin/viewer access control#20
Rio517 merged 16 commits intomainfrom
auth

Conversation

@Rio517
Copy link
Copy Markdown
Contributor

@Rio517 Rio517 commented Mar 13, 2026

Summary

  • Add src/auth.ts with HMAC cookie-based authentication, adminOnly() and viewerRequired() middleware wrappers, auto key generation (DERBY_ADMIN_KEY=auto)
  • Wrap all 17 mutation endpoints with adminOnly(), all GET API endpoints with viewerRequired() (active only when DERBY_VIEWER_KEY is set)
  • Public mode (no key configured) keeps everything open — existing behavior unchanged
  • Auth routes: POST/GET /admin/login, POST /admin/logout, POST /viewer/login, GET /admin/status
  • Frontend context plumbed with isAdmin/isPublicMode/isPrivateMode (no UI gating yet)
  • Docs restructured, deployment + hosted service plans added

Test plan

  • 17 new unit tests (tests/auth.test.ts) — HMAC, cookie parsing, key management
  • 16 new integration tests (tests/auth.integration.ts) — login/logout, protected mutations, protected reads, status endpoint
  • 21 existing API integration tests pass in public mode (no auth)
  • 38 E2E Playwright tests pass in public mode
  • Sorting E2E test sped up 1.9s → 0.6s via API-based racer creation

🤖 Generated with Claude Code

Rio517 and others added 9 commits March 13, 2026 03:13
Move all plan docs into docs/plans/ with consistent naming. Add new
plans for cloud deployment (Fly.io + SQLite + Litestream), auth revised
for cloud, hosted service (managed Pi-to-cloud), and race day edge
cases. Rename cloud-sync to remote-access, certificate to
achievement-certificate. Flesh out display-pubsub with event ideas.
Reorganize vision.md into near/mid/long-term tiers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add DERBY_VIEWER_KEY env var for privacy-focused families. When set,
all read-only content requires a viewer password — pack leaders share
it privately via email/GroupMe. Two-tier cookie system: derby_admin
(full access) and derby_viewer (read-only). Admin cookie implicitly
satisfies viewer checks.

Updated deployment.md and hosted-service.md to reference the new
viewer password feature without duplicating auth details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend auth module (src/auth.ts) with HMAC cookie validation,
adminOnly/viewerRequired middleware wrappers, and auto key generation.
All 17 mutation endpoints wrapped with adminOnly(), all GET API
endpoints wrapped with viewerRequired() (active only when
DERBY_VIEWER_KEY is set). Public mode (no key) keeps everything open.

Auth routes: POST/GET /admin/login, POST /admin/logout,
POST /viewer/login, GET /admin/status.

Frontend context plumbed with isAdmin/isPublicMode/isPrivateMode
from /admin/status endpoint (no UI gating yet).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace UI form interactions (click add, fill, submit, wait modal,
close × 2) with direct fetch() calls to create racers. The test
verifies sorting toggle behavior, not racer creation. 1.9s → 0.6s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Stop logging admin key in plaintext; show masked form (first4..last4)
- Protect /ws upgrade with viewer cookie check in private mode
- Token login accepts HMAC so URL needn't contain raw secret
- Safe fallback: publicMode=false on /admin/status fetch failure
- Extract respondJson to shared export from auth.ts (remove duplicate)
- Cache admin/viewer keys at module load (no per-request env reads)
- Add /viewer/logout endpoint for symmetry with /admin/logout
- Mark frontend auth context fields with "Phase 2" comment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Export ADMIN_PURPOSE/VIEWER_PURPOSE/ADMIN_COOKIE/VIEWER_COOKIE from
  auth.ts so integration tests import them instead of hardcoding
- Add adminOnly/viewerRequired unit tests (public-mode pass-through)
- Auth-enforcement paths covered by integration tests
Gate admin-only pages (Registration, Race Control) behind full-page walls.
Hide mutation buttons (New Event, delete, generate heats) from viewers.
Add AdminBanner component, filter nav items by role, and add 6 Playwright
tests verifying viewer-mode behavior on a dedicated auth server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Admin Login button in nav when auth is configured (not public mode)
- Login opens dialog with password field, refreshes auth on success
- Add Logout button when authenticated as admin
- Fix: viewers selecting non-complete events now navigate to /heats
  instead of /register (which showed an admin wall dead-end)
- Remove AdminBanner from HeatsView (unnecessary for read-only page)
- Position auth button outside scrollable nav area
- Add login flow e2e test + viewer navigation redirect test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…enshots

- Add unified /auth/login endpoint (detects admin vs viewer from password)
- Add PrivateLoginGate screen for fully-private mode (both keys set)
- Add PasswordInput component with show/hide toggle
- Hide Print Certificates from viewers in StandingsView
- Add README screenshot gallery (6 screenshots, 2-column grid)
- Refresh screenshots

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a cookie/HMAC-based authentication layer (admin + optional viewer password) and wires it through backend routes and frontend UI gating to support public, admin-protected, and fully private deployments.

Changes:

  • Introduces src/auth.ts (key management, cookie helpers, auth wrappers) and applies adminOnly / viewerRequired across API routes + /ws.
  • Adds frontend auth state (authStatus, canEdit) and updates views/navigation to hide admin-only UI, add login/logout flows, and gate pages.
  • Adds unit/integration/E2E coverage for auth behaviors and updates test scripts/config accordingly; expands docs/README with auth + roadmap updates.

Reviewed changes

Copilot reviewed 32 out of 41 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
tests/auth.test.ts New unit tests for auth helpers (HMAC, cookie parsing, cached-key behaviors, wrapper pass-through).
tests/auth.integration.ts New integration tests covering login/logout/status and admin/viewer access control against a running server.
src/auth.ts New auth module: admin key resolution (incl. auto), HMAC cookie sessions, wrappers, status, startup logging.
src/index.ts Applies auth wrappers to routes, restricts /ws, adds /auth/login, /admin/*, /viewer/*, /admin/status, logs auth config.
src/frontend/api.ts Adds AuthStatus + auth endpoints (getAuthStatus, adminLogin, unified login, adminLogout).
src/frontend/context.tsx Extends app context with auth state and refreshAuth.
src/frontend/main.tsx Fetches auth status on hydrate, adds private-mode login gate, adds admin login dialog + logout, filters nav based on canEdit.
src/frontend/components/AdminBanner.tsx New banner for read-only users indicating admin access is required for mutations.
src/frontend/views/EventsView.tsx Hides “New Event” and destructive actions for non-admins; shows AdminBanner.
src/frontend/views/RegistrationView.tsx Hard-gates registration behind canEdit with an “Admin access required” wall.
src/frontend/views/RaceConsoleView.tsx Hard-gates race control behind canEdit and hides certificate printing for non-admins.
src/frontend/views/HeatsView.tsx Hides mutation controls for non-admins; improves empty-state messaging for viewers.
src/frontend/views/StandingsView.tsx Restricts certificates link visibility to canEdit on completed events.
src/frontend/views/CertificateView.tsx Adds admin gating for batch printing (/certificates).
e2e/ui-sorting.playwright.ts Speeds up setup by creating racers via API instead of UI interactions.
e2e/auth-ui-gating.playwright.ts New Playwright suite validating viewer-mode UI gating and admin login flow.
playwright.config.ts Adds a dedicated auth project for auth-gating UI tests.
package.json Splits integration tests into API vs auth suites; adds test:ui:auth.
README.md Adds screenshots section; documents auth modes/vars and updated test commands.
docs/vision.md Reorganizes roadmap into near/mid/long-term and adds plan index tables.
docs/plans/*.md Adds/updates multiple plan docs (auth/deployment/edge cases/hosted service/remote access/scoring/etc.).
docs/auth-plan.md / docs/certificate-plan.md Removes older plan docs in favor of the new docs/plans/* structure.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Rio517 and others added 2 commits March 13, 2026 09:09
…ivate mode tests

- Remove api.adminLogin(), nav dialog now uses unified api.login()
- Add api.logout() that clears both admin and viewer cookies
- Use crypto.timingSafeEqual for all password comparisons
- Change PrivateLoginGate label to "Event Password"
- Add private mode e2e tests (gate, wrong password, viewer/admin login)
- Add test:ui:auth and test:ui:private to test:all
- Refresh screenshots

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…docs alignment

- Add Secure flag to cookies when request is HTTPS (X-Forwarded-Proto)
- Use timingSafeEqual for cookie HMAC validation (not just login)
- Write auto-generated admin key with mode 0o600
- Remove unused AdminBanner imports from RegistrationView and RaceConsoleView
- Add private mode auth check in CertificateView (single + batch)
- Skip auth unit tests when DERBY_ADMIN_KEY/DERBY_VIEWER_KEY are set
- Fix auth.md: remove unimplemented "none" sentinel and /display token claims
- Fix deployment.md typo: "cloaud" → "cloud"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clear DERBY_ADMIN_KEY/DERBY_VIEWER_KEY before importing the auth module
via cache-busted dynamic import so tests always run in public mode
regardless of ambient environment variables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a cookie-based authentication system (admin + optional viewer) to support public, admin-protected, and fully-private deployments, and updates the UI/API/tests/docs accordingly.

Changes:

  • Introduces src/auth.ts (key management, HMAC cookies, auth wrappers, auth status helpers) and wires it into server routes.
  • Gates API mutations with adminOnly, read endpoints with viewerRequired (only when viewer key is set), and adds login/logout/status endpoints plus WS gating.
  • Updates frontend to track auth status (canEdit), gate admin-only views/actions, and adds integration + Playwright coverage and documentation updates.

Reviewed changes

Copilot reviewed 33 out of 42 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/auth.test.ts Unit tests for auth helpers (HMAC, cookie parsing, cached mode behavior, wrapper pass-through in public mode).
tests/auth.integration.ts End-to-end API auth verification (login/logout/status + protected endpoints).
src/index.ts Wires auth into Bun routes, adds auth endpoints, wraps API routes, gates WS upgrades.
src/auth.ts New auth module: key resolution (incl. auto), HMAC cookie sessions, middleware wrappers, auth status, startup logging.
src/frontend/api.ts Adds auth API surface: getAuthStatus, unified login, logout helpers.
src/frontend/context.tsx Extends app context with auth state and refreshAuth.
src/frontend/main.tsx Fetches auth status on hydrate, private-mode login gate, nav updates, password input toggle.
src/frontend/components/AdminBanner.tsx Viewer-mode banner indicating admin access required for mutations.
src/frontend/views/StandingsView.tsx Hides batch certificate link unless user can edit.
src/frontend/views/RegistrationView.tsx Hard-gates registration UI behind admin access.
src/frontend/views/RaceConsoleView.tsx Hard-gates race control UI behind admin access; hides batch cert link for viewers.
src/frontend/views/HeatsView.tsx Hides mutation controls for viewers; improves empty-state messaging for viewer mode.
src/frontend/views/EventsView.tsx Hides “New Event” and delete controls for viewers; shows admin banner.
src/frontend/views/CertificateView.tsx Adds auth-status checks (viewer required in private mode; batch requires admin).
README.md Adds screenshots, expands test commands, documents auth modes and env vars.
playwright.config.ts Adds dedicated Playwright projects for auth gating and private mode; updates ignores.
package.json Splits integration tests (API vs auth), adds auth/private UI test scripts, updates test:all.
e2e/ui-sorting.playwright.ts Speeds up test by creating racers via API instead of UI interactions.
e2e/screenshots.playwright.ts Improves screenshot logging (new/updated/unchanged).
e2e/auth-ui-gating.playwright.ts New E2E coverage for viewer-mode UI gating + admin login dialog flow.
e2e/auth-private-mode.playwright.ts New E2E coverage for private-mode login gate behavior.
docs/vision.md Restructures vision doc and plan index tables.
docs/plans/standings-scoring.md Adds standings scoring research doc (points-per-place).
docs/plans/remote-access.md Renames/reframes remote access plan (tunnel vs cloud).
docs/plans/race-day.md Updates link to new standings scoring doc.
docs/plans/race-day-2026-analysis.md Updates link to new standings scoring doc.
docs/plans/hosted-service.md Adds hosted service plan doc.
docs/plans/edge-cases.md Adds race-day edge cases plan doc.
docs/plans/display-pubsub.md Expands display pub/sub plan detail and event taxonomy.
docs/plans/deployment.md Adds Fly.io deployment plan doc.
docs/plans/auth.md Adds/updates auth plan doc describing implemented phases.
docs/plans/achievement-certificate.md Adds consolidated certificate plan doc.
docs/certificate-plan.md Removes older certificate plan doc (superseded).
docs/auth-plan.md Removes older auth plan doc (superseded).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Remove raw admin key acceptance from GET /admin/login (HMAC only)
- Export ADMIN_LOGIN_PURPOSE constant, use in index.ts and integration tests
- Remove unused imports from index.ts (parseCookies, isPublicMode, isPrivateMode)
- Add aria-label and remove tabIndex={-1} on password toggle button
- Add private mode auth check to display page
- Fix auth.md: Max-Age 30 days, remove /healthcheck, update display docs
- Update vision.md: auth plan status → Implemented

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a cookie-based authentication system (admin + optional viewer) and applies it across the Bun server routes and React UI to support public, admin-protected, and fully-private deployments.

Changes:

  • Add src/auth.ts and wire backend route protection (adminOnly, viewerRequired) plus login/logout/status endpoints.
  • Update frontend context/UI to respect auth state (hide mutation controls for viewers, add private-mode login gate).
  • Add unit/integration/E2E coverage for auth behaviors and update docs/plans + test scripts.

Reviewed changes

Copilot reviewed 34 out of 43 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/auth.test.ts Adds unit tests for auth helpers and public-mode behavior.
tests/auth.integration.ts Adds integration tests for login flows, cookies, and protected routes.
src/index.ts Wires auth into server startup, protects API routes + websocket upgrade, adds auth endpoints.
src/auth.ts New auth module: key management (incl. auto), HMAC cookies, wrappers, status, logging.
src/frontend/views/StandingsView.tsx Hides certificate-print link unless user can edit.
src/frontend/views/RegistrationView.tsx Adds admin-only wall for registration in viewer mode.
src/frontend/views/RaceConsoleView.tsx Adds admin-only wall for race control; hides certificate-print link for viewers.
src/frontend/views/HeatsView.tsx Hides admin controls for viewers; adjusts empty-state copy for viewers.
src/frontend/views/EventsView.tsx Hides “New Event” + delete controls for viewers; adds AdminBanner.
src/frontend/views/CertificateView.tsx Adds auth checks (private-mode login requirement; batch print admin-only).
src/frontend/main.tsx Adds auth status hydration, private-mode login gate, and nav login/logout dialog.
src/frontend/display.tsx Adds private-mode auth check; shows “Authentication Required” screen when needed.
src/frontend/context.tsx Extends app context with auth status (canEdit, mode flags) and refreshAuth.
src/frontend/components/AdminBanner.tsx New banner shown to viewers indicating admin access required for changes.
src/frontend/api.ts Adds auth APIs (getAuthStatus, login, logout) and AuthStatus type.
README.md Documents auth modes, env vars, and updated test commands; adds screenshots section.
playwright.config.ts Adds dedicated Playwright projects for auth-gating and private-mode tests.
package.json Splits integration + UI tests into auth/private variants; updates scripts.
e2e/ui-sorting.playwright.ts Speeds up setup by creating racers via API instead of UI form flows.
e2e/screenshots.playwright.ts Improves screenshot logging output (new/updated/unchanged).
e2e/auth-ui-gating.playwright.ts New E2E tests verifying viewer-mode UI gating and admin login dialog flow.
e2e/auth-private-mode.playwright.ts New E2E tests verifying private-mode login gate behavior.
docs/vision.md Reorganizes roadmap/plan links and clarifies near/mid/long-term direction.
docs/plans/standings-scoring.md Adds standings scoring research doc (points-per-place).
docs/plans/remote-access.md Updates/renames remote access plan and clarifies tunnel vs cloud.
docs/plans/race-day.md Fixes reference to the new standings scoring plan location.
docs/plans/race-day-2026-analysis.md Updates link to standings scoring plan doc.
docs/plans/hosted-service.md Adds hosted service plan doc.
docs/plans/edge-cases.md Adds race-day edge cases plan doc.
docs/plans/display-pubsub.md Expands display pub/sub plan with event taxonomy and UX ideas.
docs/plans/deployment.md Adds Fly.io deployment plan doc.
docs/plans/auth.md Adds auth plan doc aligned with the implementation.
docs/plans/achievement-certificate.md Adds consolidated certificate plan doc aligned with current implementation.
docs/certificate-plan.md Removes older certificate plan doc (replaced by docs/plans/achievement-certificate.md).
docs/auth-plan.md Removes older auth plan doc (replaced by docs/plans/auth.md).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Precompute HMACs at module load for sync cookie validation
- Add per-IP rate limiting (10 attempts/60s) on all login endpoints
- Remove dead /admin/login POST endpoint
- Show login/logout button regardless of event selection
- Nav logout works for both admin and viewer roles
- Add viewer logout + read-only e2e tests
- Fix test:integration:api glob, .gitignore, display.tsx auth state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a cookie-based authentication system (admin + optional viewer access) to protect mutation APIs and optionally require login for read-only views, with corresponding frontend UI gating and expanded automated test coverage.

Changes:

  • Introduces src/auth.ts (admin key management, HMAC cookies, auth wrappers, rate limiting) and wires it into backend routes (API + /ws + auth endpoints).
  • Updates frontend context + navigation + views to hide/disable admin-only functionality based on auth status and to show private-mode login gating.
  • Adds unit/integration/E2E coverage for auth flows, plus documentation updates describing auth modes and future plans.

Reviewed changes

Copilot reviewed 34 out of 44 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/auth.test.ts Unit tests for auth helpers (HMAC, cookie parsing, mode flags, wrapper pass-through).
tests/auth.integration.ts Integration tests for login/logout, status, route protection, and rate limiting.
src/index.ts Applies adminOnly / viewerRequired, gates /ws, and adds /auth/login, /admin/login, /admin/logout, /viewer/login, /viewer/logout, /admin/status.
src/auth.ts New auth module: key management (DERBY_ADMIN_KEY=auto), HMAC cookies, auth wrappers, rate limiting, startup logging.
src/frontend/views/StandingsView.tsx Hides certificate printing link behind canEdit.
src/frontend/views/RegistrationView.tsx Adds admin-only “wall” for non-edit users.
src/frontend/views/RaceConsoleView.tsx Adds admin-only “wall” and hides certificate printing link behind canEdit.
src/frontend/views/HeatsView.tsx Hides admin controls for viewers and adjusts empty-state messaging for read-only users.
src/frontend/views/EventsView.tsx Hides event creation/deletion actions for viewers and adds AdminBanner.
src/frontend/views/CertificateView.tsx Adds auth checks: requires login in private mode; restricts batch printing to admins.
src/frontend/main.tsx Adds auth status hydration/refresh, private-mode login gate UI, and nav login/logout dialog + role-based nav filtering.
src/frontend/display.tsx Adds private-mode auth check to block display view until authenticated.
src/frontend/context.tsx Extends app context with auth fields (isAdmin, isViewer, public/private, canEdit) and refreshAuth.
src/frontend/components/AdminBanner.tsx New banner shown when user lacks edit/admin privileges.
src/frontend/api.ts Adds /admin/status fetch, unified login (/auth/login), and logout helpers.
playwright.config.ts Adds dedicated Playwright projects for auth-gating and private-mode suites.
package.json Splits integration tests into API vs auth; adds UI auth/private test scripts; updates test:all.
e2e/ui-sorting.playwright.ts Uses API calls to create racers (reduces UI interaction in sorting test).
e2e/screenshots.playwright.ts Improves screenshot runner output formatting.
e2e/auth-ui-gating.playwright.ts New E2E tests validating viewer-mode UI gating + admin login dialog flow.
e2e/auth-private-mode.playwright.ts New E2E tests validating private-mode login gate and viewer/admin behavior.
README.md Adds auth documentation and expands testing commands list.
docs/vision.md Restructures roadmap/vision and references new plan docs.
docs/plans/standings-scoring.md New research doc on points-per-place scoring.
docs/plans/remote-access.md Renames/reframes remote viewing doc around tunneling vs “cloud sync”.
docs/plans/race-day.md Updates reference link to standings scoring research doc.
docs/plans/race-day-2026-analysis.md Updates reference link to standings scoring research doc.
docs/plans/hosted-service.md New hosted-service plan doc.
docs/plans/edge-cases.md New plan doc covering common race-day disruptions and proposed features.
docs/plans/display-pubsub.md Expands WebSocket pub/sub plan with richer display event concepts.
docs/plans/deployment.md New Fly.io deployment plan doc.
docs/plans/auth.md New auth plan doc describing intended design and phases.
docs/plans/achievement-certificate.md New consolidated certificate plan doc (supersedes older docs).
docs/certificate-plan.md Removes older certificate plan doc (replaced by plans/achievement-certificate.md).
docs/auth-plan.md Removes older auth plan doc (replaced by plans/auth.md).
.gitignore Ignores .derby_admin_key auto-generated admin key file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Derive _privateMode from both admin and viewer keys being set
- Pass secure flag to clearAdminCookie/clearViewerCookie in logout
- Update README and auth plan to reflect unified /auth/login endpoint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Rio517 Rio517 merged commit b2a6aa9 into main Mar 13, 2026
3 checks passed
@Rio517 Rio517 deleted the auth branch March 13, 2026 10:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants