p0-c2-email-verification

donetype/backlogpriority/p0severity/criticaltopic/auth

p0 · C2 · Email verification on signup

TL;DR

Shipped in Phase B: emailVerified gate in Credentials authorize(), verification token model (24h, hashed, single-use), signup issues verify email without session, /verify-email route, grandfather script, and Playwright happy-path E2E. Resend UI and token edge-case E2E spun out.

Status: done (2026-05-24) · Source: [[Projects/personal-finance-notion/context/audit-2026-05-17-auth|Auth audit 2026-05-17 §C2]]

Shipped (app repo)

  • emailVerified on userModel (Date | null, Auth.js convention)
  • src/lib/models/emailVerificationTokenModel.ts
  • src/lib/actions/emailVerification.tsissueEmailVerificationToken, verifyEmail, requestEmailVerification
  • Signup: src/lib/actions/auth.tsawaitingVerification only (no session)
  • Login gate: EmailNotVerified in src/auth.ts
  • tests/grandfather-email-verified.mjs
  • tests/e2e/email-verify.spec.ts — signup → blocked login → verify → sign in

Implementation checklist

  • [x] emailVerified field added
  • [x] Verification token model with TTL + one-use enforcement
  • [x] Email template email-verify
  • [x] Resend-verification server action (rate-limited per email)
  • [x] Login refuses unverified users with actionable message
  • [x] Signup does not issue session cookies until verified
  • [x] Playwright E2E: signup → click email link → login

Spun out (not blocking “done”)

  • [[Projects/personal-finance-notion/backlog/p1-email-verification-resend-ui|p1 · Email verification resend UI on login]]
  • [[Projects/personal-finance-notion/backlog/p2-auth-token-flow-edge-e2e|p2 · Auth token flow edge-case E2E]] — expiry, reuse, resend path

Related

  • [[Projects/personal-finance-notion/decisions/adr-2026-05-18-authjs-migration|ADR 2026-05-18]]