Skip to content

feat(auth): restore password recovery (#96) - #99

Merged
homarone00 merged 1 commit into
mainfrom
feat/password_forgot
Sep 29, 2026
Merged

homarone00 merged 1 commit into
mainfrom
feat/password_forgot

Conversation

@homarone00

Copy link
Copy Markdown
Collaborator

Fixes #96.

Problem

The "Forgot your password?" flow added in 2.x (1b8ce69) was wired up in toothfairy/urls.py. When the project became yggdrasil in 3.0, its URLs, templates, forms and the login link were all lost, so nobody could reset a password.

What's back

  • A "Forgot your password?" link on the login page.
  • Django's token-based reset at /password-reset/, in four pages: request → check your email → choose a new password → done. A used or expired link gets a "This link no longer works" page with a button to send a new one. The pages use the same auth-card markup as sign-in and register, and only CSS classes already in the committed tailwind.css.
  • The branded email the invitations send: text + HTML, logo inline. The inline-logo code moves from maxillo/views/auth.py to common/emails.py, so both emails share it.

The flow lives in common/password_reset.py rather than beside the rest of auth in maxillo/, because maxillo/README.md calls that address legacy and says new cross-domain code belongs in common/.

Additions beyond the 2.x version

  • The demo guest can't be reset. Its account is created by hand in production, so nothing guarantees it lacks an email and a usable password, and resetting it would take over the session every phone visitor is logged into.
  • At most 3 emails per address per hour, so the form can't flood an inbox. Over the limit, and for unknown addresses, the page still says "sent", so the form never reveals which addresses have accounts. A mail failure is logged rather than turned into a 500 (which would reveal the same thing).
  • A completed reset clears the django-axes lockout for that username. Someone who forgot their password has often just locked themselves out guessing it.
  • Phones get the desktop-only card, as for sign-in, with its own wording for someone opening a reset link.

Notes

  • Needs working SMTP settings (EMAIL_*), which the invitations already rely on.
  • Links expire after Django's default PASSWORD_RESET_TIMEOUT (3 days); the email states the lifetime.
  • Accounts without an email address can't use the flow; the "check your email" page points to yggdrasil@unimore.it.
  • VERSION and CHANGELOG are left to the release PR.

Tests

common/tests_password_reset.py covers the login link, the branded email (HTML part and inline logo), same answer and no email for an unknown address, the demo guest refused, the per-address limit, a link that sets a new password exactly once, a forged link refused, a reset lifting an axes lockout, and phones getting the desktop-only card and sending no email.

Full suite on MySQL 8 (dev stack): Ran 1499 tests ... OK. ruff check . and lint-imports are clean.

🤖 Generated with Claude Code

The "Forgot your password?" flow added in 2.x lived in toothfairy/urls.py and
was lost with the rename to yggdrasil in 3.0, so nobody could reset a password.

It comes back in common/ (auth in maxillo/ is legacy, not a pattern) as
Django's token-based reset, with:

- the branded text + HTML email the invitations send; the inline-logo code
  moves to common/emails.py so both share it;
- the shared demo guest excluded, since its hand-made account may have an
  address and whoever reset it would own every phone visitor's session;
- at most three emails per address per hour, answered with the same "sent"
  page as an unknown address so the form reveals nothing;
- a completed reset clearing the django-axes lockout for that username;
- phones getting the desktop-only card, with its own wording for a reset link.

Fixes #96.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@homarone00
homarone00 merged commit b18d5bf into main Sep 29, 2026
3 checks passed
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.

No password recovery after v3.0

1 participant