diff --git a/autogpt_platform/frontend/src/app/(platform)/reset-password/page.tsx b/autogpt_platform/frontend/src/app/(platform)/reset-password/page.tsx
index 1aa69c9b24..152e9037a5 100644
--- a/autogpt_platform/frontend/src/app/(platform)/reset-password/page.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/reset-password/page.tsx
@@ -23,7 +23,6 @@ function ResetPasswordContent() {
const [isLoading, setIsLoading] = useState(false);
const [disabled, setDisabled] = useState(false);
const [showExpiredMessage, setShowExpiredMessage] = useState(false);
- const [linkSent, setLinkSent] = useState(false);
useEffect(() => {
const error = searchParams.get("error");
@@ -32,14 +31,14 @@ function ResetPasswordContent() {
if (error || errorCode) {
// Check if this is an expired/used link error
+ // Avoid broad checks like "invalid" which can match unrelated errors (e.g., PKCE errors)
const descLower = errorDescription?.toLowerCase() || "";
const isExpiredOrUsed =
error === "link_expired" ||
errorCode === "otp_expired" ||
descLower.includes("expired") ||
- descLower.includes("invalid") ||
- // access_denied alone is too broad - only treat as expired when combined with otp indicators
- (error === "access_denied" && errorCode === "otp_expired");
+ descLower.includes("already") ||
+ descLower.includes("used");
if (isExpiredOrUsed) {
setShowExpiredMessage(true);
@@ -98,7 +97,6 @@ function ResetPasswordContent() {
return;
}
setDisabled(true);
- setLinkSent(true);
toast({
title: "Email Sent",
description:
@@ -109,7 +107,7 @@ function ResetPasswordContent() {
[sendEmailForm, toast],
);
- function handleSendNewLink() {
+ function handleShowEmailForm() {
setShowExpiredMessage(false);
}
@@ -158,11 +156,7 @@ function ResetPasswordContent() {
return (
);
diff --git a/autogpt_platform/frontend/src/app/api/auth/callback/reset-password/route.ts b/autogpt_platform/frontend/src/app/api/auth/callback/reset-password/route.ts
index 0216f4bc99..9947b5e888 100644
--- a/autogpt_platform/frontend/src/app/api/auth/callback/reset-password/route.ts
+++ b/autogpt_platform/frontend/src/app/api/auth/callback/reset-password/route.ts
@@ -27,10 +27,10 @@ export async function GET(request: NextRequest) {
if (!result.success) {
// Check for expired or used link errors
+ // Avoid broad checks like "invalid" which can match unrelated errors (e.g., PKCE errors)
const errorMessage = result.error?.toLowerCase() || "";
const isExpiredOrUsed =
errorMessage.includes("expired") ||
- errorMessage.includes("invalid") ||
errorMessage.includes("otp_expired") ||
errorMessage.includes("already") ||
errorMessage.includes("used");
@@ -48,7 +48,7 @@ export async function GET(request: NextRequest) {
} catch (error) {
console.error("Password reset callback error:", error);
return NextResponse.redirect(
- `${origin}/reset-password?error=Password reset failed`,
+ `${origin}/reset-password?error=${encodeURIComponent("Password reset failed")}`,
);
}
}
diff --git a/autogpt_platform/frontend/src/components/auth/ExpiredLinkMessage.tsx b/autogpt_platform/frontend/src/components/auth/ExpiredLinkMessage.tsx
index 4d08f44397..0e297bbf7e 100644
--- a/autogpt_platform/frontend/src/components/auth/ExpiredLinkMessage.tsx
+++ b/autogpt_platform/frontend/src/components/auth/ExpiredLinkMessage.tsx
@@ -3,16 +3,10 @@ import { Link } from "../atoms/Link/Link";
import { Text } from "../atoms/Text/Text";
interface Props {
- onSendNewLink: () => void;
- isLoading?: boolean;
- linkSent?: boolean;
+ onRequestNewLink: () => void;
}
-export function ExpiredLinkMessage({
- onSendNewLink,
- isLoading = false,
- linkSent = false,
-}: Props) {
+export function ExpiredLinkMessage({ onRequestNewLink }: Props) {
return (
@@ -21,14 +15,8 @@ export function ExpiredLinkMessage({
Enter your email below to request a new password reset link.
-