fix(auth): scanner-proof password reset with button-gated verify and OTP-code fallback (#1100)

Corporate mail scanners (Microsoft Defender SafeLinks) follow links in
auth emails and burn the one-shot recovery token before the user sees
the mail (#1099, first hit: Deepgrid 2026-07-21, verify from an Azure
IP 23s after send).

/reset-password now has three entry modes:
- set-password: recovery session exists (legacy /auth/callback links
  keep working unchanged)
- confirm-link: the email link carries ?token_hash= and verification
  runs ONLY on an explicit button click; scanners render pages but do
  not click buttons, so the token survives scanning
- enter-code: email + 6-digit {{ .Token }} code typed manually, the
  fallback when no link works at all

The Supabase recovery email template switches to
{{ .SiteURL }}/reset-password?token_hash={{ .TokenHash }} + {{ .Token }}
AFTER this deploys (template content in the PR); the link then never
touches gotrue's GET /verify endpoint, leaving nothing to detonate.

Fixes #1099.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-21 16:50:07 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 335d908614
commit 3cb5ae7716
3 changed files with 245 additions and 47 deletions
+15 -1
View File
@@ -850,7 +850,21 @@
"mismatch_title": "Lösenorden matchar inte",
"mismatch_description": "Kontrollera att du skrev samma lösenord i båda fälten.",
"save_failed_title": "Kunde inte spara lösenordet",
"save_failed_description": "Försök igen."
"save_failed_description": "Försök igen.",
"confirm_subtitle": "Bekräfta för att fortsätta till lösenordsåterställningen.",
"confirm_button": "Fortsätt",
"confirm_verifying": "Verifierar...",
"confirm_hint": "Av säkerhetsskäl används länken först när du klickar på knappen.",
"code_subtitle": "Ange din e-post och engångskoden från mailet.",
"email_label": "E-postadress",
"code_label": "Engångskod",
"code_placeholder": "6-siffrig kod",
"code_button": "Verifiera kod",
"code_verifying": "Verifierar...",
"code_hint": "Koden finns i mailet om lösenordsåterställning. Hittar du inget mail kan du begära en ny återställning från inloggningssidan.",
"verify_failed_title": "Verifieringen misslyckades",
"link_invalid_description": "Länken är ogiltig eller har redan använts. Ange engångskoden från mailet i stället, eller begär en ny återställning.",
"code_invalid_description": "Koden är ogiltig eller har gått ut. Kontrollera koden eller begär en ny återställning från inloggningssidan."
},
"customer_detail": {
"back": "Tillbaka till kunder",