Skip to content

feat(auth): password-reset via email-token + SPA forgot/reset flow#120

Merged
themightychris merged 3 commits into
mainfrom
feat/login-migration-phase-c
May 31, 2026
Merged

feat(auth): password-reset via email-token + SPA forgot/reset flow#120
themightychris merged 3 commits into
mainfrom
feat/login-migration-phase-c

Conversation

@themightychris
Copy link
Copy Markdown
Member

Summary

Phase C of the login-migration impl track (phase A #118, phase B #119). Implements password-reset via email-token, per specs/api/auth.md + specs/behaviors/account-migration.md.

API

  • PasswordToken private record — keyed by SHA-256 of the plaintext; plaintext leaves the system only via the email send.
  • POST /api/auth/password-reset/request — always 202 (anti-enumeration); silently no-ops when target has no email or no legacy credential. Fire-and-forget notifier send.
  • POST /api/auth/password-reset/confirm — single-use token, uniform 401 invalid_token on every rejection path, rehashes to argon2id, rotates credential, mints session with loginMethod: 'password_reset'.
  • EmailNotifier.notifyPasswordReset (Resend) + LoggingNotifier impl that deliberately redacts the token from logs even in dev. Email template (text + HTML) lives in notify/templates.ts.

SPA

  • "Forgot your password?" link on the legacy-password form.
  • /login/forgot request page — mirrors the always-confirm UX.
  • /login/reset?token=... confirm page — validates client-side + handles 401/422/429 inline.

Tests — 13 new API tests + 5 new web tests. Full sweep: api 382/382, web 63/63, shared 75/75. Lint + type-check clean.

Plan: plans/login-migration-impl-phase-c.md — flips to done in a closeout commit.

Test plan

  • npm run -w apps/api test -- tests/auth-password-reset.test.ts — 13/13
  • npm run -w apps/web test -- tests/PasswordReset.test.tsx — 5/5
  • Full sweep: api 382/382 + web 63/63 + shared 75/75
  • npm run type-check && npm run lint clean

🤖 Generated with Claude Code

themightychris and others added 3 commits May 31, 2026 15:47
Implements POST /api/auth/password-reset/{request,confirm} per
specs/api/auth.md and specs/behaviors/account-migration.md.

PasswordToken is a new private-store record keyed by SHA-256 of the
plaintext; the plaintext only leaves the system via the email send.
Request always returns 202 regardless of whether the address resolved
(anti-enumeration), and silently no-ops when the target has no legacy
credential — spec invariant that password-reset never *creates* a
credential. Confirm collapses every rejection path to a uniform 401
invalid_token, rotates the credential to argon2id at current params,
marks the token used, and mints a session with loginMethod
'password_reset'.

SPA adds a "Forgot your password?" link on the legacy-password form,
a /login/forgot request page (mirrors the always-confirm UX), and a
/login/reset?token=… confirm page that validates client-side and
handles 401/422/429 inline.

EmailNotifier gets a Resend-backed notifyPasswordReset; LoggingNotifier
deliberately redacts the token from logs even in dev — plaintext tokens
only appear over the email channel.

13 API tests + 5 web tests; full sweep (api 382, web 63, shared 75)
green; lint + type-check clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds plan file for the password-reset phase of the login-migration
track. Status flips to done in a closeout commit once the PR is
opened.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All validation items ticked, PR opened. Status: in-progress → done.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@themightychris themightychris merged commit ef4068e into main May 31, 2026
1 check passed
@themightychris themightychris deleted the feat/login-migration-phase-c branch May 31, 2026 20:04
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.

1 participant