Files
bedolaga-cabinet/src/components/auth/PermissionRoute.tsx
c0mrade 62eaef2f53 fix(permission-route): guard against /admin → /admin redirect loop
PermissionRoute unconditionally sent permission-failing users to /admin.
The current router wraps /admin in AdminRoute (no permission check), so
this never triggered. But if /admin ever picks up a PermissionRoute
guard — even by accident in a future change — the user would loop on
itself with replace history (no back-button escape).

Detect that case explicitly: if we're already on /admin and still failing
the permission check, fall back to / instead. Costs nothing today,
prevents a hard-to-diagnose lock-out later.
2026-05-26 15:18:21 +03:00

71 lines
2.4 KiB
TypeScript

import { Navigate, useLocation } from 'react-router';
import { useAuthStore } from '../../store/auth';
import { usePermissionStore } from '../../store/permissions';
import { saveReturnUrl } from '../../utils/token';
import PageLoader from '../common/PageLoader';
import Layout from '../layout/Layout';
interface PermissionRouteProps {
children: React.ReactNode;
/** Single permission to check */
permission?: string;
/** Multiple permissions (OR by default, AND if requireAll is true) */
permissions?: string[];
/** When true, ALL listed permissions are required instead of ANY */
requireAll?: boolean;
}
export function PermissionRoute({
children,
permission,
permissions,
requireAll = false,
}: PermissionRouteProps) {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const isLoading = useAuthStore((state) => state.isLoading);
const isAdmin = useAuthStore((state) => state.isAdmin);
const location = useLocation();
const permissionsLoaded = usePermissionStore((state) => state.isLoaded);
const hasPermission = usePermissionStore((state) => state.hasPermission);
const hasAnyPermission = usePermissionStore((state) => state.hasAnyPermission);
const hasAllPermissions = usePermissionStore((state) => state.hasAllPermissions);
// Still loading auth state, or admin but permissions not fetched yet
if (isLoading || (isAdmin && !permissionsLoaded)) {
return <PageLoader variant="light" />;
}
if (!isAuthenticated) {
saveReturnUrl();
return <Navigate to="/login" replace state={{ from: location.pathname }} />;
}
if (!isAdmin) {
return <Navigate to="/" replace />;
}
// Check permissions if specified
const needsPermissionCheck = permission || (permissions && permissions.length > 0);
if (needsPermissionCheck) {
let hasAccess = false;
if (permission) {
hasAccess = hasPermission(permission);
} else if (permissions && permissions.length > 0) {
hasAccess = requireAll ? hasAllPermissions(...permissions) : hasAnyPermission(...permissions);
}
if (!hasAccess) {
// Redirect back to the admin landing — unless we're already there
// (would loop) or the landing itself is what we lack permission for.
// Fall back to the user dashboard in that case.
const target = location.pathname === '/admin' ? '/' : '/admin';
return <Navigate to={target} replace />;
}
}
return <Layout>{children}</Layout>;
}