Skip to content

Add account recovery — email verification + password reset (#167)#181

Merged
gregwinn merged 1 commit into
developfrom
feature/auth-account-recovery
Jun 13, 2026
Merged

Add account recovery — email verification + password reset (#167)#181
gregwinn merged 1 commit into
developfrom
feature/auth-account-recovery

Conversation

@gregwinn

Copy link
Copy Markdown
Owner

Summary

Adds email verification and password reset to Auth — sub-issue F (#167), the last runtime feature of the auth epic (#161). Builds on Auth (#163) + Mailer (#166); branched off develop.

Auth.verify_email(token)                  # consume token -> mark verified
Auth.request_password_reset(email)        # always :ok (no enumeration); emails a link
Auth.reset_password(token, new_password)  # set new password with a valid token

Design

  • Single-use, hashed, expiring tokens reuse the existing auth_tokens table with a new purpose column (refresh / verify_email / reset_password). Only the SHA-256 hash is stored.
  • refresh now sets + checks purpose, so a verification/reset link cannot be redeemed at /auth/refresh (and a refresh token can't verify/reset). Tested.
  • No user enumeration: request_password_reset always returns :ok and only emails if the address exists.
  • register/2 emails a verification link when auth.verify_email is on (off by default — no Mailer dependency for apps that don't want it).
  • New config: auth.verify_email, auth.verify_url / auth.reset_url, auth.verify_token_ttl (24h) / auth.reset_token_ttl (1h). The auth_token schema + migration gain purpose.

Testing

9 new tests: verify-email (flip + single-use + expiry + bad token + opt-in default-off), password reset (full flow: old pw dead / new pw works + single-use + expiry), no-enumeration, and the cross-purpose rejection (a verify token is refused by refresh). Fake repo gained update/1. 29 auth tests; full suite 766, only the pre-existing winn_sqlite_tests esqlite-NIF failure.

Docs

docs/modules.md: purpose in the schema/migration, the four recovery functions, recovery routes/handlers in the router example, and config. CHANGELOG updated.

Part of #161. Closes #167 once merged. Completes Phase 3 — only #168 (the winn create auth scaffold + guide) remains.

🤖 Generated with Claude Code

Phase 3 of the auth epic (#161), building on Auth (#163) + Mailer (#166).
Adds Auth.verify_email/1, request_email_verification/1, request_password_reset/1,
reset_password/2.

- Single-use, hashed, expiring tokens stored in the existing auth_tokens table
  with a new `purpose` column (refresh | verify_email | reset_password). Refresh
  now sets and checks purpose, so a recovery link can't be redeemed at
  /auth/refresh (and vice versa).
- register/2 emails a verification link when auth.verify_email is set.
- request_password_reset always returns :ok (no user enumeration); emails a link
  only if the address exists. reset/verify tokens are single-use and expiry-checked.
- New config: auth.verify_email, auth.verify_url / auth.reset_url,
  auth.verify_token_ttl (24h) / auth.reset_token_ttl (1h). auth_token schema +
  migration gain a `purpose` field.
- Fake repo gains update/1. 9 new tests (verify flip + single-use + expiry + bad
  token, reset flow + single-use + expiry, no-enumeration, cross-purpose refresh
  rejection). 29 auth tests; full suite 766 (only the pre-existing esqlite-NIF
  failure). docs/modules.md + CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gregwinn gregwinn added this to the v1.0.0 — The Winn Platform milestone Jun 13, 2026
@gregwinn gregwinn added enhancement New feature or request area/runtime Runtime modules (winn_runtime.erl, stdlib) labels Jun 13, 2026
@gregwinn gregwinn merged commit 377bc6b into develop Jun 13, 2026
2 checks passed
@gregwinn gregwinn deleted the feature/auth-account-recovery branch June 13, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/runtime Runtime modules (winn_runtime.erl, stdlib) enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant