mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 14:43:06 +08:00
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 <julien.fontanet@isonoe.net>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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()}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user