238 lines
7.0 KiB
TypeScript
Executable File
238 lines
7.0 KiB
TypeScript
Executable File
'use client';
|
|
|
|
import React, { ReactNode, useEffect } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { useAuth } from '@/contexts/AuthContext';
|
|
import { KEYCLOAK_REDIRECTS } from '@/config/keycloak';
|
|
import LoadingSpinner from '@/components/ui/LoadingSpinner';
|
|
|
|
// Props pour le composant de route protégée
|
|
interface ProtectedRouteProps {
|
|
children: ReactNode;
|
|
requiredRoles?: string[];
|
|
requiredPermissions?: string[];
|
|
requireAnyRole?: boolean; // Si true, l'utilisateur doit avoir au moins un des rôles requis
|
|
requireAllRoles?: boolean; // Si true, l'utilisateur doit avoir tous les rôles requis
|
|
requireAnyPermission?: boolean; // Si true, l'utilisateur doit avoir au moins une des permissions requises
|
|
requireAllPermissions?: boolean; // Si true, l'utilisateur doit avoir toutes les permissions requises
|
|
fallbackComponent?: ReactNode;
|
|
redirectTo?: string;
|
|
showUnauthorized?: boolean;
|
|
}
|
|
|
|
// Composant de route protégée
|
|
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
|
|
children,
|
|
requiredRoles = [],
|
|
requiredPermissions = [],
|
|
requireAnyRole = true,
|
|
requireAllRoles = false,
|
|
requireAnyPermission = true,
|
|
requireAllPermissions = false,
|
|
fallbackComponent,
|
|
redirectTo,
|
|
showUnauthorized = true,
|
|
}) => {
|
|
const router = useRouter();
|
|
const {
|
|
isAuthenticated,
|
|
isLoading,
|
|
user,
|
|
hasRole,
|
|
hasAnyRole,
|
|
hasPermission,
|
|
login
|
|
} = useAuth();
|
|
|
|
// Vérification de l'authentification et des autorisations
|
|
useEffect(() => {
|
|
if (isLoading) return;
|
|
|
|
// Si l'utilisateur n'est pas authentifié
|
|
if (!isAuthenticated) {
|
|
if (redirectTo) {
|
|
router.push(redirectTo);
|
|
} else {
|
|
// Rediriger vers la page de connexion
|
|
login();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Si l'utilisateur est authentifié mais n'a pas les rôles requis
|
|
if (requiredRoles.length > 0 && user) {
|
|
let hasRequiredRoles = false;
|
|
|
|
if (requireAllRoles) {
|
|
// L'utilisateur doit avoir tous les rôles requis
|
|
hasRequiredRoles = requiredRoles.every(role => hasRole(role));
|
|
} else if (requireAnyRole) {
|
|
// L'utilisateur doit avoir au moins un des rôles requis
|
|
hasRequiredRoles = hasAnyRole(requiredRoles);
|
|
}
|
|
|
|
if (!hasRequiredRoles) {
|
|
if (redirectTo) {
|
|
router.push(redirectTo);
|
|
} else {
|
|
router.push(KEYCLOAK_REDIRECTS.FORBIDDEN);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Si l'utilisateur est authentifié mais n'a pas les permissions requises
|
|
if (requiredPermissions.length > 0 && user) {
|
|
let hasRequiredPermissions = false;
|
|
|
|
if (requireAllPermissions) {
|
|
// L'utilisateur doit avoir toutes les permissions requises
|
|
hasRequiredPermissions = requiredPermissions.every(permission => hasPermission(permission));
|
|
} else if (requireAnyPermission) {
|
|
// L'utilisateur doit avoir au moins une des permissions requises
|
|
hasRequiredPermissions = requiredPermissions.some(permission => hasPermission(permission));
|
|
}
|
|
|
|
if (!hasRequiredPermissions) {
|
|
if (redirectTo) {
|
|
router.push(redirectTo);
|
|
} else {
|
|
router.push(KEYCLOAK_REDIRECTS.FORBIDDEN);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}, [
|
|
isAuthenticated,
|
|
isLoading,
|
|
user,
|
|
requiredRoles,
|
|
requiredPermissions,
|
|
requireAnyRole,
|
|
requireAllRoles,
|
|
requireAnyPermission,
|
|
requireAllPermissions,
|
|
redirectTo,
|
|
router,
|
|
hasRole,
|
|
hasAnyRole,
|
|
hasPermission,
|
|
login,
|
|
]);
|
|
|
|
// Affichage pendant le chargement
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<LoadingSpinner size="large" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Si l'utilisateur n'est pas authentifié
|
|
if (!isAuthenticated) {
|
|
if (fallbackComponent) {
|
|
return <>{fallbackComponent}</>;
|
|
}
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<div className="text-center">
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
|
Authentification requise
|
|
</h2>
|
|
<p className="text-gray-600 mb-6">
|
|
Vous devez être connecté pour accéder à cette page.
|
|
</p>
|
|
<button
|
|
onClick={login}
|
|
className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
|
>
|
|
Se connecter
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Vérification des rôles
|
|
if (requiredRoles.length > 0 && user) {
|
|
let hasRequiredRoles = false;
|
|
|
|
if (requireAllRoles) {
|
|
hasRequiredRoles = requiredRoles.every(role => hasRole(role));
|
|
} else if (requireAnyRole) {
|
|
hasRequiredRoles = hasAnyRole(requiredRoles);
|
|
}
|
|
|
|
if (!hasRequiredRoles) {
|
|
if (fallbackComponent) {
|
|
return <>{fallbackComponent}</>;
|
|
}
|
|
if (showUnauthorized) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<div className="text-center">
|
|
<h2 className="text-2xl font-bold text-red-600 mb-4">
|
|
Accès non autorisé
|
|
</h2>
|
|
<p className="text-gray-600 mb-6">
|
|
Vous n'avez pas les permissions nécessaires pour accéder à cette page.
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
Rôles requis: {requiredRoles.join(', ')}
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
Vos rôles: {user.roles.join(', ')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Vérification des permissions
|
|
if (requiredPermissions.length > 0 && user) {
|
|
let hasRequiredPermissions = false;
|
|
|
|
if (requireAllPermissions) {
|
|
hasRequiredPermissions = requiredPermissions.every(permission => hasPermission(permission));
|
|
} else if (requireAnyPermission) {
|
|
hasRequiredPermissions = requiredPermissions.some(permission => hasPermission(permission));
|
|
}
|
|
|
|
if (!hasRequiredPermissions) {
|
|
if (fallbackComponent) {
|
|
return <>{fallbackComponent}</>;
|
|
}
|
|
if (showUnauthorized) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<div className="text-center">
|
|
<h2 className="text-2xl font-bold text-red-600 mb-4">
|
|
Permissions insuffisantes
|
|
</h2>
|
|
<p className="text-gray-600 mb-6">
|
|
Vous n'avez pas les permissions nécessaires pour accéder à cette page.
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
Permissions requises: {requiredPermissions.join(', ')}
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
Vos permissions: {user.permissions.join(', ')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Si toutes les vérifications passent, afficher le contenu
|
|
return <>{children}</>;
|
|
};
|
|
|
|
export default ProtectedRoute;
|