Skip to content

security: replace open routes with authenticated, validated actions (PR 1.4) - #12

Merged
Jeston10 merged 2 commits into
milestone/p1-securityfrom
security/protect-endpoints
Sep 20, 2026
Merged

Jeston10 merged 2 commits into
milestone/p1-securityfrom
security/protect-endpoints

Conversation

@Jeston10

Copy link
Copy Markdown
Owner

Summary

PR 1.4 of docs/MILESTONES.md (Phase 1). Closes R2 — every remaining unauthenticated entry point is gone or gated.

Before After
POST /api/auth/signout (open) features/auth/actions.ts#signOut → destroySession (revokes tokens)
GET /api/daily-login/{check,streak}?userId= (enumerable) Deleted. server/services/streak.service.ts, recorded on the first authenticated request from the layout; widget takes props from the session user
POST /api/profile/update (cookie read by hand) features/profile/actions.ts#updateProfile — requireUserChecked + zod (description ≤ 500, image whitelist/size cap)
createFeedback(userId, feedbackId) trusted the client features/feedback/actions.ts — user from session, interview existence checked, transcript bounded, existing feedback resolved server-side; generation in server/services/feedback.service.ts with the transcript delimited as data (§2.4)
signUp(uid, …) trusted a client uid Verifies the Firebase ID token; uid/email from claims. Password ≥ 8
POST /api/vapi/generate (open) Server-to-server from Vapi ⇒ shared secret x-vapi-secret (constant-time), zod body, target user must exist; 503 unless VAPI_WEBHOOK_SECRET is set

Also: server/db/types.ts (canonical transcript/id schemas — features compose them, server never imports features), scripts/revoke-all-sessions.ts for the one-off R1 step, a latent bug fixed (prompt category names didn't match the zod literals).

Type

  • security

Verification

  • pnpm lint ✔ · typecheck ✔ · knip ✔ · vitest 44/44 — unauthenticated, identity-from-session, malformed input, no-error-leak, webhook 503/401/400/404, streak date logic across time zones · next build ✔
  • Runtime on a production build with a throwaway user: legacy routes → 404, webhook → 503, authenticated dashboard → 200 with lastLoginDate/loginStreak written by the layout; user cleaned up

Server checklist

  • requireUser()/requireUserChecked() (or secret check) first in every action/route
  • zod with limits on every input; enums for roles
  • Identity from session/token, never from input (tested)
  • Rate limits — P1.5
  • No process.env outside config/env.ts; no PII in logs (opLogger, redaction)
  • Tests for unauthenticated and wrong-input cases

Risk watch-list

  • Rows checked: R2 (closed), R12 (Vapi retained, hardened), R1 (script added; run after Phase 1 release)

Docs

  • docs/SECURITY.md §4 (webhook listed as signed), docs/RISKS.md, .env.example (VAPI_WEBHOOK_SECRET)

Config note: set VAPI_WEBHOOK_SECRET in Vercel and the same value as an x-vapi-secret header in the Vapi workflow's API Request node, or the generate route stays disabled.

🤖 Generated with Claude Code

PR 1.4 of docs/MILESTONES.md. Closes RISKS R2.

- Deleted /api/auth/signout, /api/daily-login/*, /api/profile/update.
  Replaced by server actions in features/{auth,profile,feedback}:
  session first (requireUser / requireUserChecked), zod second,
  identity never taken from the client.
- signUp/signIn now verify the Firebase ID token server-side and take
  uid/email from its claims (the old signUp trusted a client uid).
  Password policy raised to 8 characters (shared zod schema).
- createFeedback: user from session, interview existence checked,
  transcript bounded (500 turns x 4000 chars, role enum), existing
  feedback resolved server-side (no client feedbackId); generation moved
  to server/services/feedback.service.ts with the transcript delimited
  as data (SECURITY §2.4). Also fixes a latent mismatch between the
  prompt's category names and the zod literals.
- /api/vapi/generate (server-to-server from Vapi): shared secret in
  x-vapi-secret with constant-time compare, zod body, target user must
  exist, disabled (503) unless VAPI_WEBHOOK_SECRET is set.
- server/services/streak.service.ts: pure, timezone-aware streak logic
  recorded on the first authenticated request per day from the layout;
  the streak widget takes props from the session user (no polling).
- server/db/types.ts: canonical transcript/id schemas (features compose
  them; server never imports features).
- scripts/revoke-all-sessions.ts for the one-off R1 step post-release.

Verified: pnpm lint, typecheck, knip, vitest 44/44 (unauthenticated,
identity-from-session, malformed input, error-leak, webhook 503/401/
400/404 cases), next build; runtime on next start: legacy routes 404,
webhook 503, authenticated dashboard 200 with lastLoginDate/streak
written by the layout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 20, 2026 10:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vercel

vercel Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
just-prep-ai-interview-z3cv Error Error Sep 20, 2026 10:14am UTC

@Jeston10
Jeston10 merged commit 1cb21fe into milestone/p1-security Sep 20, 2026
6 of 7 checks passed
@Jeston10
Jeston10 deleted the security/protect-endpoints branch September 20, 2026 10:38

This branch had an error being deployed

1 failed deployment
Preview — 5e08da4e Deployed Sep 20, 2026 by vercel[bot]
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