mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 05:24:11 +03:00
fix redirect race condition
Coordinate 401 redirect logic between ApiProvider and ProtectedRoute using a shared flag to prevent multiple simultaneous redirects that caused UI flashing. Ensure both auth error paths check and set the redirect flag before navigating to login, eliminating race conditions where both mechanisms could trigger at once
This commit is contained in:
parent
bbe18d5d6b
commit
0902e4bf7e
12
web/src/api/auth-redirect.ts
Normal file
12
web/src/api/auth-redirect.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Module-level flag to prevent multiple simultaneous redirects
|
||||||
|
// (eg, when multiple SWR queries fail with 401 at once, or when
|
||||||
|
// both ApiProvider and ProtectedRoute try to redirect)
|
||||||
|
let _isRedirectingToLogin = false;
|
||||||
|
|
||||||
|
export function isRedirectingToLogin(): boolean {
|
||||||
|
return _isRedirectingToLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setRedirectingToLogin(value: boolean): void {
|
||||||
|
_isRedirectingToLogin = value;
|
||||||
|
}
|
||||||
@ -3,13 +3,10 @@ import { SWRConfig } from "swr";
|
|||||||
import { WsProvider } from "./ws";
|
import { WsProvider } from "./ws";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import { isRedirectingToLogin, setRedirectingToLogin } from "./auth-redirect";
|
||||||
|
|
||||||
axios.defaults.baseURL = `${baseUrl}api/`;
|
axios.defaults.baseURL = `${baseUrl}api/`;
|
||||||
|
|
||||||
// Module-level flag to prevent multiple simultaneous redirects
|
|
||||||
// (eg, when multiple SWR queries fail with 401 at once)
|
|
||||||
let isRedirectingToLogin = false;
|
|
||||||
|
|
||||||
type ApiProviderType = {
|
type ApiProviderType = {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
options?: Record<string, unknown>;
|
options?: Record<string, unknown>;
|
||||||
@ -35,8 +32,8 @@ export function ApiProvider({ children, options }: ApiProviderType) {
|
|||||||
) {
|
) {
|
||||||
// redirect to the login page if not already there
|
// redirect to the login page if not already there
|
||||||
const loginPage = error.response.headers.get("location") ?? "login";
|
const loginPage = error.response.headers.get("location") ?? "login";
|
||||||
if (window.location.href !== loginPage && !isRedirectingToLogin) {
|
if (window.location.href !== loginPage && !isRedirectingToLogin()) {
|
||||||
isRedirectingToLogin = true;
|
setRedirectingToLogin(true);
|
||||||
window.location.href = loginPage;
|
window.location.href = loginPage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { useContext } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import { Navigate, Outlet } from "react-router-dom";
|
import { Navigate, Outlet } from "react-router-dom";
|
||||||
import { AuthContext } from "@/context/auth-context";
|
import { AuthContext } from "@/context/auth-context";
|
||||||
import ActivityIndicator from "../indicators/activity-indicator";
|
import ActivityIndicator from "../indicators/activity-indicator";
|
||||||
|
import {
|
||||||
|
isRedirectingToLogin,
|
||||||
|
setRedirectingToLogin,
|
||||||
|
} from "@/api/auth-redirect";
|
||||||
|
|
||||||
export default function ProtectedRoute({
|
export default function ProtectedRoute({
|
||||||
requiredRoles,
|
requiredRoles,
|
||||||
@ -10,6 +14,20 @@ export default function ProtectedRoute({
|
|||||||
}) {
|
}) {
|
||||||
const { auth } = useContext(AuthContext);
|
const { auth } = useContext(AuthContext);
|
||||||
|
|
||||||
|
// Redirect to login page when not authenticated
|
||||||
|
// don't use <Navigate> because we need a full page load to reset state
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!auth.isLoading &&
|
||||||
|
auth.isAuthenticated &&
|
||||||
|
!auth.user &&
|
||||||
|
!isRedirectingToLogin()
|
||||||
|
) {
|
||||||
|
setRedirectingToLogin(true);
|
||||||
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
}, [auth.isLoading, auth.isAuthenticated, auth.user]);
|
||||||
|
|
||||||
if (auth.isLoading) {
|
if (auth.isLoading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
||||||
@ -23,7 +41,9 @@ export default function ProtectedRoute({
|
|||||||
|
|
||||||
// Authenticated mode (8971): require login
|
// Authenticated mode (8971): require login
|
||||||
if (!auth.user) {
|
if (!auth.user) {
|
||||||
return <Navigate to="/login" replace />;
|
return (
|
||||||
|
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If role is null (shouldn’t happen if isAuthenticated, but type safety), fallback
|
// If role is null (shouldn’t happen if isAuthenticated, but type safety), fallback
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user