331 lines
10 KiB
TypeScript
331 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import React, { createContext, useContext, useEffect, useState, useCallback, ReactNode } from 'react';
|
|
import { useKeycloak } from './KeycloakContext';
|
|
import { RoleUtils, BTP_ROLES } from '@/config/keycloak';
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
// Types pour l'authentification
|
|
export interface UserInfo {
|
|
id: string;
|
|
username: string;
|
|
email: string;
|
|
firstName?: string;
|
|
lastName?: string;
|
|
fullName?: string;
|
|
roles: string[];
|
|
permissions: string[];
|
|
highestRole?: string;
|
|
isAdmin: boolean;
|
|
isManager: boolean;
|
|
isEmployee: boolean;
|
|
isClient: boolean;
|
|
}
|
|
|
|
export interface AuthState {
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
user: UserInfo | null;
|
|
token: string | null;
|
|
refreshToken: string | null;
|
|
error: string | null;
|
|
}
|
|
|
|
export interface AuthContextType extends AuthState {
|
|
login: () => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
refreshAuth: () => Promise<void>;
|
|
hasRole: (role: string) => boolean;
|
|
hasAnyRole: (roles: string[]) => boolean;
|
|
hasPermission: (permission: string) => boolean;
|
|
isRoleHigher: (role: string) => boolean;
|
|
updateToken: (minValidity?: number) => Promise<boolean>;
|
|
}
|
|
|
|
// Contexte d'authentification
|
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
|
|
// Hook pour utiliser le contexte d'authentification
|
|
export const useAuth = (): AuthContextType => {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error('useAuth must be used within an AuthProvider');
|
|
}
|
|
return context;
|
|
};
|
|
|
|
// Props du provider
|
|
interface AuthProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
// 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,
|
|
isLoading: true,
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
error: null,
|
|
});
|
|
|
|
// Fonction pour récupérer les informations utilisateur depuis l'API
|
|
const fetchUserInfo = useCallback(async (token: string): Promise<UserInfo | null> => {
|
|
try {
|
|
const response = await fetch('/api/v1/auth/user', {
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.warn('Impossible de récupérer les informations utilisateur depuis l\'API');
|
|
return null;
|
|
}
|
|
|
|
const userData = await response.json();
|
|
|
|
// Convertir les données de l'API vers le format UserInfo
|
|
const userInfo: UserInfo = {
|
|
id: userData.id,
|
|
username: userData.username,
|
|
email: userData.email,
|
|
firstName: userData.firstName,
|
|
lastName: userData.lastName,
|
|
fullName: userData.fullName,
|
|
roles: userData.roles || [],
|
|
permissions: userData.permissions || [],
|
|
highestRole: userData.roles?.[0] || undefined,
|
|
isAdmin: userData.isAdmin || false,
|
|
isManager: userData.isManager || false,
|
|
isEmployee: userData.isEmployee || false,
|
|
isClient: userData.isClient || false,
|
|
};
|
|
|
|
return userInfo;
|
|
} catch (error) {
|
|
console.error('Erreur lors de la récupération des informations utilisateur:', error);
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
// Fonction pour extraire les informations utilisateur (fallback)
|
|
const extractUserInfo = useCallback((keycloakInstance: any): UserInfo | null => {
|
|
if (!keycloakInstance.tokenParsed) return null;
|
|
|
|
const tokenParsed = keycloakInstance.tokenParsed;
|
|
const realmAccess = keycloakInstance.realmAccess || {};
|
|
const resourceAccess = keycloakInstance.resourceAccess || {};
|
|
|
|
// Extraction des rôles
|
|
const realmRoles = realmAccess.roles || [];
|
|
const clientRoles = resourceAccess['btpxpress-frontend']?.roles || [];
|
|
const allRoles = [...realmRoles, ...clientRoles];
|
|
|
|
// Informations utilisateur
|
|
const userInfo: UserInfo = {
|
|
id: tokenParsed.sub,
|
|
username: tokenParsed.preferred_username || tokenParsed.email,
|
|
email: tokenParsed.email,
|
|
firstName: tokenParsed.given_name,
|
|
lastName: tokenParsed.family_name,
|
|
fullName: tokenParsed.name || `${tokenParsed.given_name || ''} ${tokenParsed.family_name || ''}`.trim(),
|
|
roles: allRoles,
|
|
permissions: RoleUtils.getUserPermissions(allRoles),
|
|
highestRole: RoleUtils.getHighestRole(allRoles) || undefined,
|
|
isAdmin: RoleUtils.hasAnyRole(allRoles, [BTP_ROLES.SUPER_ADMIN, BTP_ROLES.ADMIN]),
|
|
isManager: RoleUtils.hasAnyRole(allRoles, [BTP_ROLES.DIRECTEUR, BTP_ROLES.MANAGER, BTP_ROLES.CHEF_CHANTIER]),
|
|
isEmployee: RoleUtils.hasAnyRole(allRoles, [BTP_ROLES.EMPLOYE, BTP_ROLES.OUVRIER, BTP_ROLES.CHEF_EQUIPE]),
|
|
isClient: RoleUtils.hasAnyRole(allRoles, [BTP_ROLES.CLIENT_ENTREPRISE, BTP_ROLES.CLIENT_PARTICULIER]),
|
|
};
|
|
|
|
return userInfo;
|
|
}, []);
|
|
|
|
// Fonction pour mettre à jour l'état d'authentification
|
|
const updateAuthState = useCallback(async (keycloakInstance: any) => {
|
|
if (keycloakInstance && keycloakInstance.authenticated) {
|
|
// Essayer d'abord de récupérer les informations depuis l'API
|
|
let user = await fetchUserInfo(keycloakInstance.token);
|
|
|
|
// Si l'API ne répond pas, utiliser les informations du token JWT
|
|
if (!user) {
|
|
user = extractUserInfo(keycloakInstance);
|
|
}
|
|
|
|
setAuthState({
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
user,
|
|
token: keycloakInstance.token,
|
|
refreshToken: keycloakInstance.refreshToken,
|
|
error: null,
|
|
});
|
|
} else {
|
|
setAuthState({
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
error: null,
|
|
});
|
|
}
|
|
}, [fetchUserInfo, extractUserInfo]);
|
|
|
|
// Fonction de connexion - utilise Keycloak JS SDK
|
|
const login = useCallback(async (): Promise<void> => {
|
|
try {
|
|
keycloakLogin();
|
|
} catch (error) {
|
|
console.error('Erreur lors de la connexion:', error);
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
error: 'Erreur lors de la connexion',
|
|
isLoading: false,
|
|
}));
|
|
}
|
|
}, [keycloakLogin]);
|
|
|
|
// Fonction de déconnexion - utilise Keycloak JS SDK
|
|
const logout = useCallback(async (): Promise<void> => {
|
|
try {
|
|
keycloakLogout();
|
|
} catch (error) {
|
|
console.error('Erreur lors de la déconnexion:', error);
|
|
// Forcer la déconnexion locale même en cas d'erreur
|
|
setAuthState({
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
error: null,
|
|
});
|
|
router.push('/');
|
|
}
|
|
}, [keycloakLogout, router]);
|
|
|
|
// Fonction pour rafraîchir l'authentification - utilise Keycloak SDK
|
|
const refreshAuth = useCallback(async (): Promise<void> => {
|
|
try {
|
|
if (keycloak && authenticated) {
|
|
await keycloakUpdateToken();
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors du rafraîchissement du token:', error);
|
|
await logout();
|
|
}
|
|
}, [keycloak, authenticated, keycloakUpdateToken, logout]);
|
|
|
|
// Fonction pour mettre à jour le token - utilise Keycloak SDK
|
|
const updateToken = useCallback(async (minValidity: number = 30): Promise<boolean> => {
|
|
try {
|
|
if (keycloak && authenticated) {
|
|
return await keycloakUpdateToken();
|
|
}
|
|
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 => {
|
|
return authState.user ? RoleUtils.hasRole(authState.user.roles, role) : false;
|
|
}, [authState.user]);
|
|
|
|
const hasAnyRole = useCallback((roles: string[]): boolean => {
|
|
return authState.user ? RoleUtils.hasAnyRole(authState.user.roles, roles) : false;
|
|
}, [authState.user]);
|
|
|
|
const hasPermission = useCallback((permission: string): boolean => {
|
|
return authState.user ? RoleUtils.hasPermission(authState.user.roles, permission) : false;
|
|
}, [authState.user]);
|
|
|
|
const isRoleHigher = useCallback((role: string): boolean => {
|
|
if (!authState.user?.highestRole) return false;
|
|
return RoleUtils.isRoleHigher(authState.user.highestRole, role);
|
|
}, [authState.user]);
|
|
|
|
// Synchroniser l'état avec KeycloakContext
|
|
useEffect(() => {
|
|
const syncAuthState = async () => {
|
|
if (loading) {
|
|
setAuthState({
|
|
isAuthenticated: false,
|
|
isLoading: true,
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
error: null,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (authenticated && keycloak && token) {
|
|
// Essayer de récupérer les informations utilisateur depuis l'API
|
|
let user = await fetchUserInfo(token);
|
|
|
|
// Si l'API ne répond pas, extraire du token JWT
|
|
if (!user) {
|
|
user = extractUserInfo(keycloak);
|
|
}
|
|
|
|
setAuthState({
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
user,
|
|
token: token,
|
|
refreshToken: keycloak.refreshToken || null,
|
|
error: null,
|
|
});
|
|
} else {
|
|
setAuthState({
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
error: null,
|
|
});
|
|
}
|
|
};
|
|
|
|
syncAuthState();
|
|
}, [authenticated, loading, token, keycloak, fetchUserInfo, extractUserInfo]);
|
|
|
|
// Le rafraîchissement automatique du token est géré par KeycloakContext
|
|
|
|
// Valeur du contexte
|
|
const contextValue: AuthContextType = {
|
|
...authState,
|
|
login,
|
|
logout,
|
|
refreshAuth,
|
|
hasRole,
|
|
hasAnyRole,
|
|
hasPermission,
|
|
isRoleHigher,
|
|
updateToken,
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider value={contextValue}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
};
|
|
|
|
export default AuthProvider;
|