From 53132acb0a9109fd34147be4c1947c9d7cb7580d Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:02:23 +0000 Subject: [PATCH] fix: redirect to original page after re-authentication (#1959) * fix: redirect to original page after re-authentication When a session expires, the current URL is now preserved as a query parameter on the login page. After successful login (including MFA flows), the user is redirected back to their original page instead of always landing on /home. * secure --------- Co-authored-by: Julien Fontanet --- .../src/ee/components/ldap-login-modal.tsx | 8 ++++---- .../src/ee/mfa/components/mfa-challenge.tsx | 4 ++-- .../src/ee/mfa/components/mfa-setup-required.tsx | 4 ++-- .../src/ee/mfa/hooks/use-mfa-page-protection.ts | 12 +++++++----- apps/client/src/features/auth/hooks/use-auth.ts | 8 ++++---- .../auth/hooks/use-redirect-if-authenticated.ts | 4 ++-- apps/client/src/lib/api-client.ts | 6 +++++- apps/client/src/lib/app-route.ts | 16 ++++++++++++++++ 8 files changed, 42 insertions(+), 20 deletions(-) diff --git a/apps/client/src/ee/components/ldap-login-modal.tsx b/apps/client/src/ee/components/ldap-login-modal.tsx index 9360651d..0a456946 100644 --- a/apps/client/src/ee/components/ldap-login-modal.tsx +++ b/apps/client/src/ee/components/ldap-login-modal.tsx @@ -7,7 +7,7 @@ import { notifications } from "@mantine/notifications"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { IAuthProvider } from "@/ee/security/types/security.types"; -import APP_ROUTE from "@/lib/app-route"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route"; import { ldapLogin } from "@/ee/security/services/ldap-auth-service"; const formSchema = z.object({ @@ -59,13 +59,13 @@ export function LdapLoginModal({ // Handle MFA like the regular login if (response?.userHasMfa) { onClose(); - navigate(APP_ROUTE.AUTH.MFA_CHALLENGE); + navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + window.location.search); } else if (response?.requiresMfaSetup) { onClose(); - navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED); + navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + window.location.search); } else { onClose(); - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } } catch (err: any) { setIsLoading(false); diff --git a/apps/client/src/ee/mfa/components/mfa-challenge.tsx b/apps/client/src/ee/mfa/components/mfa-challenge.tsx index 8a9bef53..413494ef 100644 --- a/apps/client/src/ee/mfa/components/mfa-challenge.tsx +++ b/apps/client/src/ee/mfa/components/mfa-challenge.tsx @@ -18,7 +18,7 @@ import { useNavigate } from "react-router-dom"; import { notifications } from "@mantine/notifications"; import classes from "./mfa-challenge.module.css"; import { verifyMfa } from "@/ee/mfa"; -import APP_ROUTE from "@/lib/app-route"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route"; import { useTranslation } from "react-i18next"; import * as z from "zod"; import { MfaBackupCodeInput } from "./mfa-backup-code-input"; @@ -53,7 +53,7 @@ export function MfaChallenge() { setIsLoading(true); try { await verifyMfa(values.code); - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } catch (error: any) { setIsLoading(false); notifications.show({ diff --git a/apps/client/src/ee/mfa/components/mfa-setup-required.tsx b/apps/client/src/ee/mfa/components/mfa-setup-required.tsx index c657abe9..ab327c4d 100644 --- a/apps/client/src/ee/mfa/components/mfa-setup-required.tsx +++ b/apps/client/src/ee/mfa/components/mfa-setup-required.tsx @@ -3,7 +3,7 @@ import { Container, Paper, Title, Text, Alert, Stack } from "@mantine/core"; import { IconAlertCircle } from "@tabler/icons-react"; import { useTranslation } from "react-i18next"; import { MfaSetupModal } from "@/ee/mfa"; -import APP_ROUTE from "@/lib/app-route.ts"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts"; import { useNavigate } from "react-router-dom"; export default function MfaSetupRequired() { @@ -11,7 +11,7 @@ export default function MfaSetupRequired() { const navigate = useNavigate(); const handleSetupComplete = () => { - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); }; return ( diff --git a/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts b/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts index 9200cac7..30b27427 100644 --- a/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts +++ b/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import APP_ROUTE from "@/lib/app-route"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route"; import { validateMfaAccess } from "@/ee/mfa"; export function useMfaPageProtection() { @@ -13,8 +13,10 @@ export function useMfaPageProtection() { const checkAccess = async () => { const result = await validateMfaAccess(); + const search = location.search; + if (!result.valid) { - navigate(APP_ROUTE.AUTH.LOGIN); + navigate(APP_ROUTE.AUTH.LOGIN + search); return; } @@ -26,17 +28,17 @@ export function useMfaPageProtection() { if (result.requiresMfaSetup && !isOnSetupPage) { // User needs to set up MFA but is on challenge page - navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED); + navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + search); } else if ( !result.requiresMfaSetup && result.userHasMfa && !isOnChallengePage ) { // User has MFA and should be on challenge page - navigate(APP_ROUTE.AUTH.MFA_CHALLENGE); + navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + search); } else if (!result.isTransferToken) { // User has a regular auth token, shouldn't be on MFA pages - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } else { setIsValid(true); } diff --git a/apps/client/src/features/auth/hooks/use-auth.ts b/apps/client/src/features/auth/hooks/use-auth.ts index decb393f..6e1b4e34 100644 --- a/apps/client/src/features/auth/hooks/use-auth.ts +++ b/apps/client/src/features/auth/hooks/use-auth.ts @@ -23,7 +23,7 @@ import { acceptInvitation, createWorkspace, } from "@/features/workspace/services/workspace-service.ts"; -import APP_ROUTE from "@/lib/app-route.ts"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts"; import { RESET } from "jotai/utils"; import { useTranslation } from "react-i18next"; import { isCloud } from "@/lib/config.ts"; @@ -44,11 +44,11 @@ export default function useAuth() { // Check if MFA is required if (response?.userHasMfa) { - navigate(APP_ROUTE.AUTH.MFA_CHALLENGE); + navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + window.location.search); } else if (response?.requiresMfaSetup) { - navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED); + navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + window.location.search); } else { - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } } catch (err) { setIsLoading(false); diff --git a/apps/client/src/features/auth/hooks/use-redirect-if-authenticated.ts b/apps/client/src/features/auth/hooks/use-redirect-if-authenticated.ts index 8961ea93..10c76bd3 100644 --- a/apps/client/src/features/auth/hooks/use-redirect-if-authenticated.ts +++ b/apps/client/src/features/auth/hooks/use-redirect-if-authenticated.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; import useCurrentUser from "@/features/user/hooks/use-current-user.ts"; -import APP_ROUTE from "@/lib/app-route.ts"; +import { getPostLoginRedirect } from "@/lib/app-route.ts"; import { useNavigate } from "react-router-dom"; export function useRedirectIfAuthenticated() { @@ -9,7 +9,7 @@ export function useRedirectIfAuthenticated() { useEffect(() => { if (data && data?.user) { - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } }, [isLoading, data]); } diff --git a/apps/client/src/lib/api-client.ts b/apps/client/src/lib/api-client.ts index d4f61b05..632811db 100644 --- a/apps/client/src/lib/api-client.ts +++ b/apps/client/src/lib/api-client.ts @@ -68,10 +68,14 @@ function redirectToLogin() { APP_ROUTE.AUTH.SIGNUP, APP_ROUTE.AUTH.FORGOT_PASSWORD, APP_ROUTE.AUTH.PASSWORD_RESET, + APP_ROUTE.AUTH.MFA_CHALLENGE, + APP_ROUTE.AUTH.MFA_SETUP_REQUIRED, "/invites", ]; if (!exemptPaths.some((path) => window.location.pathname.startsWith(path))) { - window.location.href = APP_ROUTE.AUTH.LOGIN; + const redirectTo = window.location.pathname; + const params = new URLSearchParams({ redirect: redirectTo }); + window.location.href = `${APP_ROUTE.AUTH.LOGIN}?${params.toString()}`; } } diff --git a/apps/client/src/lib/app-route.ts b/apps/client/src/lib/app-route.ts index 0151c856..c4a13093 100644 --- a/apps/client/src/lib/app-route.ts +++ b/apps/client/src/lib/app-route.ts @@ -29,4 +29,20 @@ const APP_ROUTE = { }, }; +export function getPostLoginRedirect(): string { + const params = new URLSearchParams(window.location.search); + const redirect = params.get("redirect"); + if (redirect) { + try { + const resolved = new URL(redirect, window.location.origin); + if (resolved.origin === window.location.origin) { + return resolved.pathname + resolved.search + resolved.hash; + } + } catch { + // malformed URL, fall through to default + } + } + return APP_ROUTE.HOME; +} + export default APP_ROUTE;