diff --git a/apps/studio/components/interfaces/SignIn/ForgotPasswordWizard.tsx b/apps/studio/components/interfaces/SignIn/ForgotPasswordWizard.tsx index 4aa80ad51d..27e8df3b0a 100644 --- a/apps/studio/components/interfaces/SignIn/ForgotPasswordWizard.tsx +++ b/apps/studio/components/interfaces/SignIn/ForgotPasswordWizard.tsx @@ -60,10 +60,13 @@ const ConfirmResetCodeForm = ({ email }: { email: string }) => { if (user?.factors?.length) { await router.push({ pathname: '/forgot-password-mfa', - query: { returnTo: '/reset-password' }, + query: router.query, }) } else { - await router.push('reset-password') + await router.push({ + pathname: '/reset-password', + query: router.query, + }) } } } diff --git a/apps/studio/components/interfaces/SignIn/ResetPasswordForm.tsx b/apps/studio/components/interfaces/SignIn/ResetPasswordForm.tsx index 868b5d9616..48e513fe0e 100644 --- a/apps/studio/components/interfaces/SignIn/ResetPasswordForm.tsx +++ b/apps/studio/components/interfaces/SignIn/ResetPasswordForm.tsx @@ -7,7 +7,7 @@ import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { z } from 'zod' -import { auth } from 'lib/gotrue' +import { auth, getReturnToPath } from 'lib/gotrue' import { Button, Form_Shadcn_, @@ -67,7 +67,7 @@ const ResetPasswordForm = () => { // logout all other sessions after changing password await auth.signOut({ scope: 'others' }) - await router.push('/organizations') + await router.push(getReturnToPath('/organizations')) } else { toast.error(`Failed to save password: ${error.message}`, { id: toastId }) if (!WHITELIST_ERRORS.some((e) => error.message.includes(e))) { diff --git a/apps/studio/components/interfaces/SignIn/SignInForm.tsx b/apps/studio/components/interfaces/SignIn/SignInForm.tsx index b25214fa34..9460c7c2eb 100644 --- a/apps/studio/components/interfaces/SignIn/SignInForm.tsx +++ b/apps/studio/components/interfaces/SignIn/SignInForm.tsx @@ -4,7 +4,7 @@ import type { AuthError } from '@supabase/supabase-js' import { useQueryClient } from '@tanstack/react-query' import Link from 'next/link' import { useRouter } from 'next/router' -import { useRef, useState } from 'react' +import { useRef, useState, useEffect } from 'react' import { toast } from 'sonner' import { object, string } from 'yup' @@ -28,10 +28,22 @@ const SignInForm = () => { const [captchaToken, setCaptchaToken] = useState(null) const captchaRef = useRef(null) + const [returnTo, setReturnTo] = useState(null) + + useEffect(() => { + // Only call getReturnToPath after component mounts client-side + setReturnTo(getReturnToPath()) + }, []) const { mutate: sendEvent } = useSendEventMutation() const { mutate: addLoginEvent } = useAddLoginEvent() + let forgotPasswordUrl = `/forgot-password` + + if (returnTo && !returnTo.includes('/forgot-password')) { + forgotPasswordUrl = `${forgotPasswordUrl}?returnTo=${encodeURIComponent(returnTo)}` + } + const onSignIn = async ({ email, password }: { email: string; password: string }) => { const toastId = toast.loading('Signing in...') @@ -68,9 +80,12 @@ const SignInForm = () => { addLoginEvent({}) await queryClient.resetQueries() - const returnTo = getReturnToPath() // since we're already on the /sign-in page, prevent redirect loops - router.push(returnTo === '/sign-in' ? '/organizations' : returnTo) + let redirectPath = '/organizations' + if (returnTo && returnTo !== '/sign-in') { + redirectPath = returnTo + } + router.push(redirectPath) } catch (error: any) { toast.error(`Failed to sign in: ${(error as AuthError).message}`, { id: toastId }) Sentry.captureMessage('[CRITICAL] Failed to sign in via EP: ' + error.message) @@ -124,7 +139,7 @@ const SignInForm = () => { {/* positioned using absolute instead of labelOptional prop so tabbing between inputs works smoothly */} Forgot Password? diff --git a/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx b/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx index 7543a1a411..f0ec3eb138 100644 --- a/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx +++ b/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx @@ -18,7 +18,11 @@ const signInSchema = object({ code: string().required('MFA Code is required'), }) -const SignInMfaForm = () => { +interface SignInMfaFormProps { + context?: 'forgot-password' | 'sign-in' +} + +const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => { const router = useRouter() const signOut = useSignOut() const queryClient = useQueryClient() @@ -38,7 +42,15 @@ const SignInMfaForm = () => { } = useMfaChallengeAndVerifyMutation({ onSuccess: async () => { await queryClient.resetQueries() - router.push(getReturnToPath()) + + if (context === 'forgot-password') { + router.push({ + pathname: '/reset-password', + query: router.query, + }) + } else { + router.push(getReturnToPath()) + } }, }) diff --git a/apps/studio/lib/gotrue.ts b/apps/studio/lib/gotrue.ts index 0c96b31a04..42c65673ae 100644 --- a/apps/studio/lib/gotrue.ts +++ b/apps/studio/lib/gotrue.ts @@ -73,6 +73,11 @@ export const buildPathWithParams = (pathname: string) => { } export const getReturnToPath = (fallback = DEFAULT_FALLBACK_PATH) => { + // If we're in a server environment, return the fallback + if (typeof location === 'undefined') { + return fallback + } + const searchParams = new URLSearchParams(location.search) let returnTo = searchParams.get('returnTo') ?? fallback diff --git a/apps/studio/pages/forgot-password-mfa.tsx b/apps/studio/pages/forgot-password-mfa.tsx index 7061c9c84a..3d838cb3c6 100644 --- a/apps/studio/pages/forgot-password-mfa.tsx +++ b/apps/studio/pages/forgot-password-mfa.tsx @@ -86,7 +86,7 @@ const ForgotPasswordMfa: NextPageWithLayout = () => { heading="Complete two-factor authentication" subheading="Enter the authentication code from your two-factor authentication app before changing your password" > - + ) }