Authentification fonctionnelle via security.lions.dev

This commit is contained in:
DahoudG
2025-11-01 14:16:20 +00:00
parent a5adb84a62
commit 1d68878601
20 changed files with 387 additions and 1067 deletions

View File

@@ -1,7 +1,8 @@
'use client';
import React, { createContext, useContext, useEffect, useState, useCallback, ReactNode } from 'react';
import { initKeycloak, keycloakInitOptions, RoleUtils, BTP_ROLES, KEYCLOAK_TIMEOUTS, KEYCLOAK_REDIRECTS } from '@/config/keycloak';
import { useKeycloak } from './KeycloakContext';
import { RoleUtils, BTP_ROLES } from '@/config/keycloak';
import { useRouter } from 'next/navigation';
// Types pour l'authentification
@@ -61,7 +62,10 @@ interface AuthProviderProps {
// Provider d'authentification
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const router = useRouter();
// Utiliser le contexte Keycloak
const { keycloak, authenticated, loading, token, login: keycloakLogin, logout: keycloakLogout, updateToken: keycloakUpdateToken } = useKeycloak();
// État de l'authentification
const [authState, setAuthState] = useState<AuthState>({
isAuthenticated: false,
@@ -178,11 +182,10 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
}
}, [fetchUserInfo, extractUserInfo]);
// Fonction de connexion
// Fonction de connexion - utilise Keycloak JS SDK
const login = useCallback(async (): Promise<void> => {
try {
// Redirection directe vers l'API d'authentification
window.location.href = '/api/auth/login';
keycloakLogin();
} catch (error) {
console.error('Erreur lors de la connexion:', error);
setAuthState(prev => ({
@@ -191,21 +194,12 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
isLoading: false,
}));
}
}, []);
}, [keycloakLogin]);
// Fonction de déconnexion
// Fonction de déconnexion - utilise Keycloak JS SDK
const logout = useCallback(async (): Promise<void> => {
try {
// Nettoyer les tokens locaux
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('idToken');
// Supprimer le cookie
document.cookie = 'keycloak-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
// Redirection directe vers l'API de déconnexion
window.location.href = '/api/auth/logout';
keycloakLogout();
} catch (error) {
console.error('Erreur lors de la déconnexion:', error);
// Forcer la déconnexion locale même en cas d'erreur
@@ -219,45 +213,32 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
});
router.push('/');
}
}, [router]);
}, [keycloakLogout, router]);
// Fonction pour rafraîchir l'authentification
// Fonction pour rafraîchir l'authentification - utilise Keycloak SDK
const refreshAuth = useCallback(async (): Promise<void> => {
try {
// Vérifier les tokens stockés
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
await logout();
return;
if (keycloak && authenticated) {
await keycloakUpdateToken();
}
// TODO: Implémenter le rafraîchissement via API si nécessaire
console.log('Rafraîchissement des tokens...');
} catch (error) {
console.error('Erreur lors du rafraîchissement du token:', error);
await logout();
}
}, [logout]);
}, [keycloak, authenticated, keycloakUpdateToken, logout]);
// Fonction pour mettre à jour le token
// Fonction pour mettre à jour le token - utilise Keycloak SDK
const updateToken = useCallback(async (minValidity: number = 30): Promise<boolean> => {
try {
// Vérifier si le token est encore valide
const accessToken = localStorage.getItem('accessToken');
if (!accessToken) {
return false;
if (keycloak && authenticated) {
return await keycloakUpdateToken();
}
// TODO: Vérifier l'expiration du token et rafraîchir si nécessaire
return true;
return false;
} catch (error) {
console.error('Erreur lors de la mise à jour du token:', error);
return false;
}
}, []);
}, [keycloak, authenticated, keycloakUpdateToken]);
// Fonctions utilitaires pour les rôles et permissions
const hasRole = useCallback((role: string): boolean => {
@@ -277,65 +258,39 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
return RoleUtils.isRoleHigher(authState.user.highestRole, role);
}, [authState.user]);
// Initialisation de Keycloak - Désactivée pour utiliser l'authentification manuelle
// Synchroniser l'état avec KeycloakContext
useEffect(() => {
const initializeKeycloak = async () => {
try {
// Vérifier s'il y a des tokens stockés localement
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
const idToken = localStorage.getItem('idToken');
const syncAuthState = async () => {
if (loading) {
setAuthState({
isAuthenticated: false,
isLoading: true,
user: null,
token: null,
refreshToken: null,
error: null,
});
return;
}
if (accessToken) {
// Stocker aussi dans un cookie pour le middleware
document.cookie = `keycloak-token=${accessToken}; path=/; max-age=3600; SameSite=Lax`;
if (authenticated && keycloak && token) {
// Essayer de récupérer les informations utilisateur depuis l'API
let user = await fetchUserInfo(token);
// Récupérer les informations utilisateur depuis l'API
try {
const user = await fetchUserInfo(accessToken);
if (user) {
setAuthState({
isAuthenticated: true,
isLoading: false,
user,
token: accessToken,
refreshToken: refreshToken,
error: null,
});
return;
}
} catch (error) {
console.warn('Impossible de récupérer les informations utilisateur depuis l\'API, utilisation des données par défaut');
}
// Fallback avec des données par défaut si l'API ne répond pas
setAuthState({
isAuthenticated: true,
isLoading: false,
user: {
id: 'dev-user-001',
username: 'admin.btpxpress',
email: 'admin@btpxpress.com',
firstName: 'Jean-Michel',
lastName: 'Martineau',
fullName: 'Jean-Michel Martineau',
roles: [BTP_ROLES.SUPER_ADMIN, BTP_ROLES.ADMIN, BTP_ROLES.DIRECTEUR],
permissions: RoleUtils.getUserPermissions([BTP_ROLES.SUPER_ADMIN]),
highestRole: BTP_ROLES.SUPER_ADMIN,
isAdmin: true,
isManager: true,
isEmployee: false,
isClient: false,
},
token: accessToken,
refreshToken: refreshToken,
error: null,
});
return;
// Si l'API ne répond pas, extraire du token JWT
if (!user) {
user = extractUserInfo(keycloak);
}
// Pas de tokens, rester non authentifié
setAuthState({
isAuthenticated: true,
isLoading: false,
user,
token: token,
refreshToken: keycloak.refreshToken || null,
error: null,
});
} else {
setAuthState({
isAuthenticated: false,
isLoading: false,
@@ -344,33 +299,13 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
refreshToken: null,
error: null,
});
} catch (error) {
console.error('Erreur lors de l\'initialisation de l\'authentification:', error);
setAuthState({
isAuthenticated: false,
isLoading: false,
user: null,
token: null,
refreshToken: null,
error: 'Erreur lors de l\'initialisation de l\'authentification',
});
}
};
initializeKeycloak();
}, [updateAuthState, refreshAuth, logout]);
syncAuthState();
}, [authenticated, loading, token, keycloak, fetchUserInfo, extractUserInfo]);
// Rafraîchissement automatique du token
useEffect(() => {
if (!authState.isAuthenticated) return;
const interval = setInterval(() => {
updateToken();
}, KEYCLOAK_TIMEOUTS.SESSION_CHECK_INTERVAL * 1000);
return () => clearInterval(interval);
}, [authState.isAuthenticated, updateToken]);
// Le rafraîchissement automatique du token est géré par KeycloakContext
// Valeur du contexte
const contextValue: AuthContextType = {