fix(frontend): pass through Supabase error params in password reset callback (#12384)

When Supabase rejects a password reset token (expired, already used,
etc.), it redirects to the callback URL with `error`, `error_code`, and
`error_description` params instead of a `code`. Previously, the callback
only checked for `!code` and returned a generic "Missing verification
code" error, swallowing the actual Supabase error.

This meant the `ExpiredLinkMessage` UX (added in SECRT-1369 / #12123)
was never triggered for these cases — users just saw the email input
form again with no explanation.

Now the callback reads Supabase's error params and forwards them to
`/reset-password`, where the existing expired link detection picks them
up correctly.

**Note:** This doesn't fix the root cause of Pwuts's token expiry issue
(likely link preview/prefetch consuming the OTP), but it ensures users
see the proper "link expired" message with a "Request new link" button
instead of a confusing silent redirect.

---
Co-authored-by: Reinier van der Leer (@Pwuts) <pwuts@agpt.co>
This commit is contained in:
Otto
2026-03-12 13:51:15 +00:00
committed by GitHub
parent ef446e4fe9
commit 83e49f71cd
2 changed files with 27 additions and 0 deletions

View File

@@ -42,6 +42,14 @@ function ResetPasswordContent() {
if (isExpiredOrUsed) {
setShowExpiredMessage(true);
// Also show a toast with the Supabase error detail for debugging
if (errorDescription) {
toast({
title: "Link Expired",
description: errorDescription,
variant: "destructive",
});
}
} else {
// Show toast for other errors
const errorMessage =

View File

@@ -9,6 +9,25 @@ export async function GET(request: NextRequest) {
process.env.NEXT_PUBLIC_FRONTEND_BASE_URL || "http://localhost:3000";
if (!code) {
// Supabase may redirect here with error params instead of a code
// (e.g. when the OTP token is expired or already used)
const error = searchParams.get("error");
const errorCode = searchParams.get("error_code");
const errorDescription = searchParams.get("error_description");
if (error || errorCode || errorDescription) {
// Forward raw Supabase error params to the reset-password page,
// which already handles classification (expired vs other errors)
const params = new URLSearchParams();
if (error) params.set("error", error);
if (errorCode) params.set("error_code", errorCode);
if (errorDescription) params.set("error_description", errorDescription);
return NextResponse.redirect(
`${origin}/reset-password?${params.toString()}`,
);
}
return NextResponse.redirect(
`${origin}/reset-password?error=${encodeURIComponent("Missing verification code")}`,
);