mirror of
https://github.com/docmost/docmost.git
synced 2026-05-19 07:54:05 +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 { useNavigate } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { IAuthProvider } from "@/ee/security/types/security.types";
|
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";
|
import { ldapLogin } from "@/ee/security/services/ldap-auth-service";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
@@ -59,13 +59,13 @@ export function LdapLoginModal({
|
|||||||
// Handle MFA like the regular login
|
// Handle MFA like the regular login
|
||||||
if (response?.userHasMfa) {
|
if (response?.userHasMfa) {
|
||||||
onClose();
|
onClose();
|
||||||
navigate(APP_ROUTE.AUTH.MFA_CHALLENGE);
|
navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + window.location.search);
|
||||||
} else if (response?.requiresMfaSetup) {
|
} else if (response?.requiresMfaSetup) {
|
||||||
onClose();
|
onClose();
|
||||||
navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED);
|
navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + window.location.search);
|
||||||
} else {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(getPostLoginRedirect());
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import classes from "./mfa-challenge.module.css";
|
import classes from "./mfa-challenge.module.css";
|
||||||
import { verifyMfa } from "@/ee/mfa";
|
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 { useTranslation } from "react-i18next";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import { MfaBackupCodeInput } from "./mfa-backup-code-input";
|
import { MfaBackupCodeInput } from "./mfa-backup-code-input";
|
||||||
@@ -53,7 +53,7 @@ export function MfaChallenge() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await verifyMfa(values.code);
|
await verifyMfa(values.code);
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(getPostLoginRedirect());
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Container, Paper, Title, Text, Alert, Stack } from "@mantine/core";
|
|||||||
import { IconAlertCircle } from "@tabler/icons-react";
|
import { IconAlertCircle } from "@tabler/icons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { MfaSetupModal } from "@/ee/mfa";
|
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";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function MfaSetupRequired() {
|
export default function MfaSetupRequired() {
|
||||||
@@ -11,7 +11,7 @@ export default function MfaSetupRequired() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSetupComplete = () => {
|
const handleSetupComplete = () => {
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(getPostLoginRedirect());
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
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";
|
import { validateMfaAccess } from "@/ee/mfa";
|
||||||
|
|
||||||
export function useMfaPageProtection() {
|
export function useMfaPageProtection() {
|
||||||
@@ -13,8 +13,10 @@ export function useMfaPageProtection() {
|
|||||||
const checkAccess = async () => {
|
const checkAccess = async () => {
|
||||||
const result = await validateMfaAccess();
|
const result = await validateMfaAccess();
|
||||||
|
|
||||||
|
const search = location.search;
|
||||||
|
|
||||||
if (!result.valid) {
|
if (!result.valid) {
|
||||||
navigate(APP_ROUTE.AUTH.LOGIN);
|
navigate(APP_ROUTE.AUTH.LOGIN + search);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,17 +28,17 @@ export function useMfaPageProtection() {
|
|||||||
|
|
||||||
if (result.requiresMfaSetup && !isOnSetupPage) {
|
if (result.requiresMfaSetup && !isOnSetupPage) {
|
||||||
// User needs to set up MFA but is on challenge page
|
// 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 (
|
} else if (
|
||||||
!result.requiresMfaSetup &&
|
!result.requiresMfaSetup &&
|
||||||
result.userHasMfa &&
|
result.userHasMfa &&
|
||||||
!isOnChallengePage
|
!isOnChallengePage
|
||||||
) {
|
) {
|
||||||
// User has MFA and should be on challenge page
|
// 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) {
|
} else if (!result.isTransferToken) {
|
||||||
// User has a regular auth token, shouldn't be on MFA pages
|
// User has a regular auth token, shouldn't be on MFA pages
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(getPostLoginRedirect());
|
||||||
} else {
|
} else {
|
||||||
setIsValid(true);
|
setIsValid(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
acceptInvitation,
|
acceptInvitation,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
} from "@/features/workspace/services/workspace-service.ts";
|
} 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 { RESET } from "jotai/utils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { isCloud } from "@/lib/config.ts";
|
import { isCloud } from "@/lib/config.ts";
|
||||||
@@ -44,11 +44,11 @@ export default function useAuth() {
|
|||||||
|
|
||||||
// Check if MFA is required
|
// Check if MFA is required
|
||||||
if (response?.userHasMfa) {
|
if (response?.userHasMfa) {
|
||||||
navigate(APP_ROUTE.AUTH.MFA_CHALLENGE);
|
navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + window.location.search);
|
||||||
} else if (response?.requiresMfaSetup) {
|
} else if (response?.requiresMfaSetup) {
|
||||||
navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED);
|
navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + window.location.search);
|
||||||
} else {
|
} else {
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(getPostLoginRedirect());
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import useCurrentUser from "@/features/user/hooks/use-current-user.ts";
|
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";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export function useRedirectIfAuthenticated() {
|
export function useRedirectIfAuthenticated() {
|
||||||
@@ -9,7 +9,7 @@ export function useRedirectIfAuthenticated() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data && data?.user) {
|
if (data && data?.user) {
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(getPostLoginRedirect());
|
||||||
}
|
}
|
||||||
}, [isLoading, data]);
|
}, [isLoading, data]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,10 +68,14 @@ function redirectToLogin() {
|
|||||||
APP_ROUTE.AUTH.SIGNUP,
|
APP_ROUTE.AUTH.SIGNUP,
|
||||||
APP_ROUTE.AUTH.FORGOT_PASSWORD,
|
APP_ROUTE.AUTH.FORGOT_PASSWORD,
|
||||||
APP_ROUTE.AUTH.PASSWORD_RESET,
|
APP_ROUTE.AUTH.PASSWORD_RESET,
|
||||||
|
APP_ROUTE.AUTH.MFA_CHALLENGE,
|
||||||
|
APP_ROUTE.AUTH.MFA_SETUP_REQUIRED,
|
||||||
"/invites",
|
"/invites",
|
||||||
];
|
];
|
||||||
if (!exemptPaths.some((path) => window.location.pathname.startsWith(path))) {
|
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;
|
export default APP_ROUTE;
|
||||||
|
|||||||
Reference in New Issue
Block a user