Phase 2: password reset flow
Adds the Credentials password-reset flow. Entra-only accounts have no local password and are silently ignored. Backend: - Migration 0012_password_reset.sql adds password_reset_token_hash and password_reset_expires_at columns to public.users with a partial index for token lookup. - New backend/src/lib/email.ts wraps Resend. RESEND_API_KEY is required in production; in development the email body is written to the backend log so the operator can complete the flow manually. - New POST /auth/request-password-reset and POST /auth/reset-password routes. The request endpoint always returns 200 regardless of whether the email is registered (prevents enumeration). Tokens are 32 random bytes encoded base64url, hashed SHA-256 before storage (raw token never persisted), valid for one hour, single-use. - Wrong token and expired token return the same 400 message so a brute-force attacker cannot discriminate. Weak password returns a separate 400 with the minimum-length message. - Audit events: user.password-reset.request and user.password-reset.complete. - Disabled accounts cannot reset. Frontend: - New /forgot-password page submits the email and shows a generic "if an account exists" success message. - New /reset-password page reads ?token=, validates password length and confirmation, calls the backend, redirects to /login on success. - "Forgot password?" link added under the Login submit button. - middleware.ts adds /forgot-password and /reset-password to the public allowlist. End-to-end verification against Postgres 16: - Unknown email: 200, no email sent. - Known email: 200, link captured from backend log. - Wrong token: 400 generic. - Weak password: 400 with min-length message. - Valid token + strong password: 200, password updated, reset columns cleared. - Same token re-used: 400 generic (one-shot). - Old password rejected, new password signs in. - audit_events recorded both events. Docs: - docs/developer/07-auth.md: new Password reset section. - backend/.env.example: documents RESEND_API_KEY and RESEND_FROM. - CHANGELOG and Instructions updated.
| Repository | cpatpa/PIP |
|---|---|
| Author | Claude <noreply@anthropic.com> |
| Authored | |
| Parents | d2a8512b |
| Stats | 11 files changed , +682 |
| Part of | Phase 2 - Auth.js v5 + Entra OIDC + password reset |
Capture this commit into my fork
Download a Markdown prompt that tells Claude how to port this
exact commit into your working tree. Run it via
claude -p < capture-commit-9e2123cd.md
from inside the repo you want the change in.