Files
pad/cmd
xarmian 31053086ee feat(server): RequireVerifiedEmail enforcement across all mutation perimeters (TASK-1937) (#807)
PLAN-1933 DR-4 (Wave 3a). Enforce, on Pad Cloud only, that an
authenticated-but-unverified user cannot mutate content or mint
credentials. Cloud-only + unauthenticated + verified are all no-ops, so
this is inert in production until Wave 3b starts creating unverified
users; the tests drive the state directly via the store's explicit
UserCreate.Unverified control.

Core rule: block only when cloudMode && currentUser != nil &&
!IsEmailVerified(), on mutating methods (POST/PATCH/PUT/DELETE). Returns
403 email_not_verified. The middleware does NOT inherit CSRFProtect's /
RequireAuth's blanket /api/v1/auth/* exemption — it decides for itself.

Perimeters gated (systematic DR-4 audit — one test each):
- /api/v1 core writes (session AND PAT) — RequireVerifiedEmail method
  gate mounted after RequireAuth (server.go).
- Authenticated /auth/* mutations — token create/rotate/delete, PATCH
  /me, 2FA setup/disable, OAuth link/unlink, and cli-session approve —
  all fall through the method gate (no auth exemption); logout,
  verify-email, resend-verification, delete-account allowlisted.
- Collab WS GET-upgrade — authorizeCollabAccess (a GET the method gate
  can't catch; it persists Yjs edits).
- Remote MCP write path — dispatcher RequireVerifiedEmail hook fired in
  buildAuthedRequest (the single chokepoint every synthesized write
  passes through), wired in cmd/pad/main.go.
- OAuth-provider authorize + authorize/decide — emailUnverifiedBlocked
  checks (mounted outside /api/v1; decide mints the auth code).
- POST /api/v1/import/url — SSRF/abuse surface, method gate.

Carve-outs: POST /api/v1/invitations/{code}/accept stays open for
unverified invitees (DR-1); legacy no-user workspace PATs are
intentionally ungated (currentUser==nil). Self-host (!cloudMode) is a
full no-op.

Claude-Session: https://claude.ai/code/session_01HxBkAMiFBtCRJ2tKSCt3ST
2026-07-04 02:06:58 -04:00
..