Skip to content

feat(auth): add a forgot-password flow - #302

Merged
KaliCZ merged 1 commit into
mainfrom
claude/forgot-password-flow-a8eceb
Oct 9, 2026
Merged

KaliCZ merged 1 commit into
mainfrom
claude/forgot-password-flow-a8eceb

Conversation

@KaliCZ

@KaliCZ KaliCZ commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Why

The sign-in dialog had no way to recover a forgotten password, so email/password users who lost theirs were locked out for good.

What

  • Dialog reset mode. "Forgot password?" under the password field switches AuthDialog to a third mode (signin | signup | reset). It asks only for an email and sends resetPasswordForEmail with the same fresh Turnstile token as sign-in/sign-up, since GoTrue captcha-checks /recover too. The confirmation is deliberately neutral ("if an account exists…"), matching Supabase, which returns 200 for unknown emails.
  • Link routing. The emailed link redirects to /auth/callback?next=/reset-password?next=<page you were on>. That keeps the existing rule that /auth/callback is the only page accepting session tokens in the URL; Layout still treats a token anywhere else as a stale link.
  • /reset-password page (en + cs, noindex, not in the sitemap). The new-password form is offered only to sessions opened from an emailed link (amr of otp / magiclink / recovery). That's exactly the set GoTrue's Session.IsRecovery() exempts from the current-password check. A password or Google session, or no session at all, gets "this link is invalid or has expired" with a button that reopens the dialog in reset mode. After saving, the user returns to the page where they asked for the link, signed in, with a success snackbar.
  • Open-redirect fix in /auth/callback. next was validated with startsWith("/") && !startsWith("//"), which lets /\evil.com through, and browsers resolve that to https://evil.com/. Both the callback and the reset page now use a shared sameOriginPath() that parses the URL and compares origins.

Before this works in production

Supabase sends reset emails itself, not the backend. If the project still uses Supabase's built-in mailer, it only delivers to project team members. A custom SMTP server must be set under Authentication → Emails → SMTP Settings; docs/SETUP.md now says so. Redirect URLs need no change, since https://www.kalandra.tech/** already covers /auth/callback?next=….

Testing

  • New E2E password-reset-flow.spec.ts against local Supabase + Mailpit. It requests a link from the dialog, opens the real emailed link, checks password-mismatch validation, sets a new password, and asserts the redirect back to /about. Then the old password fails and the new one signs in. A second case spends a link first and checks that reopening it lands on the "send a new link" state.
  • New page tests: dialog switches into reset mode and back, the signed-out reset page offers a new link, the Czech page renders, noindex, and the sitemap excludes the page.
  • Checked manually in the browser:
    • the Czech flow end to end, returning to /cs/blog
    • a password-session visit to /reset-password gets the invalid-link state
    • light and dark mode

🤖 Generated with Claude Code

The sign-in dialog had no way to recover a forgotten password, so
email/password users who lost theirs were locked out for good.

- "Forgot password?" in the sign-in dialog switches it to a reset mode
  that emails a Supabase recovery link (Turnstile-protected like
  sign-in/sign-up).
- The link lands on /auth/callback, which forwards to a new localized
  /reset-password page. It offers the new-password form only to sessions
  opened from an emailed link, then returns the user to the page they
  started from.
- /auth/callback now validates `next` by resolved origin: the old prefix
  check let `/\evil.com` through as an open redirect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KaliCZ
KaliCZ merged commit 8ff1a11 into main Oct 9, 2026
7 checks passed
@KaliCZ
KaliCZ deleted the claude/forgot-password-flow-a8eceb branch October 9, 2026 08: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