- Correction des erreurs TypeScript dans userService.ts et workflowTester.ts - Ajout des propriétés manquantes aux objets User mockés - Conversion des dates de string vers objets Date - Correction des appels asynchrones et des types incompatibles - Ajout de dynamic rendering pour résoudre les erreurs useSearchParams - Enveloppement de useSearchParams dans Suspense boundary - Configuration de force-dynamic au niveau du layout principal Build réussi: 126 pages générées avec succès 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
335 lines
8.3 KiB
TypeScript
335 lines
8.3 KiB
TypeScript
/**
|
|
* Configuration Keycloak pour BTP Xpress
|
|
* Intégration complète avec security.lions.dev
|
|
*/
|
|
|
|
import Keycloak from 'keycloak-js';
|
|
import { KEYCLOAK_CONFIG, KEYCLOAK_ROLES, KEYCLOAK_SCOPES } from './api';
|
|
|
|
// Configuration de l'instance Keycloak
|
|
export const keycloakConfig = {
|
|
url: KEYCLOAK_CONFIG.url,
|
|
realm: KEYCLOAK_CONFIG.realm,
|
|
clientId: KEYCLOAK_CONFIG.clientId,
|
|
};
|
|
|
|
// Instance Keycloak globale avec gestion SSR et développement
|
|
let keycloak: any = null;
|
|
|
|
// Fonction pour initialiser Keycloak côté client uniquement
|
|
export const initKeycloak = () => {
|
|
if (typeof window === 'undefined') {
|
|
// Côté serveur - retourner un mock
|
|
return {
|
|
init: () => Promise.resolve(false),
|
|
login: () => console.log('Mock Keycloak login'),
|
|
logout: () => console.log('Mock Keycloak logout'),
|
|
updateToken: () => Promise.resolve(false),
|
|
authenticated: false,
|
|
token: null,
|
|
refreshToken: null,
|
|
tokenParsed: null,
|
|
onTokenExpired: null,
|
|
onAuthRefreshSuccess: null,
|
|
onAuthRefreshError: null,
|
|
onAuthLogout: null,
|
|
};
|
|
}
|
|
|
|
// Côté client - initialiser Keycloak réel
|
|
if (!keycloak) {
|
|
try {
|
|
keycloak = new Keycloak(keycloakConfig);
|
|
} catch (error) {
|
|
console.warn('Keycloak non disponible en mode développement:', error);
|
|
// Mock Keycloak pour le développement local
|
|
keycloak = {
|
|
init: () => Promise.resolve(false),
|
|
login: () => console.log('Mock Keycloak login'),
|
|
logout: () => console.log('Mock Keycloak logout'),
|
|
updateToken: () => Promise.resolve(false),
|
|
authenticated: false,
|
|
token: null,
|
|
refreshToken: null,
|
|
tokenParsed: null,
|
|
onTokenExpired: null,
|
|
onAuthRefreshSuccess: null,
|
|
onAuthRefreshError: null,
|
|
onAuthLogout: null,
|
|
};
|
|
}
|
|
}
|
|
|
|
return keycloak;
|
|
};
|
|
|
|
// Export pour compatibilité
|
|
export { keycloak };
|
|
|
|
// Options d'initialisation Keycloak
|
|
export const keycloakInitOptions = {
|
|
onLoad: 'login-required' as const,
|
|
checkLoginIframe: false,
|
|
pkceMethod: 'S256' as const,
|
|
scope: KEYCLOAK_SCOPES.join(' '),
|
|
responseMode: 'fragment' as const,
|
|
flow: 'standard' as const,
|
|
enableLogging: process.env.NODE_ENV === 'development',
|
|
};
|
|
|
|
// Configuration des rôles BTP Xpress
|
|
export const BTP_ROLES = {
|
|
// Rôles administratifs
|
|
SUPER_ADMIN: 'super_admin',
|
|
ADMIN: 'admin',
|
|
|
|
// Rôles de gestion
|
|
DIRECTEUR: 'directeur',
|
|
MANAGER: 'manager',
|
|
CHEF_CHANTIER: 'chef_chantier',
|
|
|
|
// Rôles opérationnels
|
|
CONDUCTEUR_TRAVAUX: 'conducteur_travaux',
|
|
CHEF_EQUIPE: 'chef_equipe',
|
|
EMPLOYE: 'employe',
|
|
OUVRIER: 'ouvrier',
|
|
|
|
// Rôles clients
|
|
CLIENT_ENTREPRISE: 'client_entreprise',
|
|
CLIENT_PARTICULIER: 'client_particulier',
|
|
|
|
// Rôles techniques
|
|
COMPTABLE: 'comptable',
|
|
COMMERCIAL: 'commercial',
|
|
LOGISTICIEN: 'logisticien',
|
|
|
|
// Rôles de consultation
|
|
VIEWER: 'viewer',
|
|
GUEST: 'guest',
|
|
} as const;
|
|
|
|
// Hiérarchie des rôles (du plus élevé au plus bas)
|
|
export const ROLE_HIERARCHY = [
|
|
BTP_ROLES.SUPER_ADMIN,
|
|
BTP_ROLES.ADMIN,
|
|
BTP_ROLES.DIRECTEUR,
|
|
BTP_ROLES.MANAGER,
|
|
BTP_ROLES.CHEF_CHANTIER,
|
|
BTP_ROLES.CONDUCTEUR_TRAVAUX,
|
|
BTP_ROLES.CHEF_EQUIPE,
|
|
BTP_ROLES.COMMERCIAL,
|
|
BTP_ROLES.COMPTABLE,
|
|
BTP_ROLES.LOGISTICIEN,
|
|
BTP_ROLES.EMPLOYE,
|
|
BTP_ROLES.OUVRIER,
|
|
BTP_ROLES.CLIENT_ENTREPRISE,
|
|
BTP_ROLES.CLIENT_PARTICULIER,
|
|
BTP_ROLES.VIEWER,
|
|
BTP_ROLES.GUEST,
|
|
] as const;
|
|
|
|
// Permissions par rôle
|
|
export const ROLE_PERMISSIONS = {
|
|
[BTP_ROLES.SUPER_ADMIN]: ['*'], // Toutes les permissions
|
|
[BTP_ROLES.ADMIN]: [
|
|
'users.manage',
|
|
'chantiers.manage',
|
|
'materiels.manage',
|
|
'employes.manage',
|
|
'clients.manage',
|
|
'devis.manage',
|
|
'factures.manage',
|
|
'reports.view',
|
|
'dashboard.admin',
|
|
],
|
|
[BTP_ROLES.DIRECTEUR]: [
|
|
'chantiers.manage',
|
|
'employes.manage',
|
|
'clients.manage',
|
|
'devis.manage',
|
|
'factures.manage',
|
|
'reports.view',
|
|
'dashboard.management',
|
|
],
|
|
[BTP_ROLES.MANAGER]: [
|
|
'chantiers.view',
|
|
'chantiers.edit',
|
|
'employes.view',
|
|
'employes.edit',
|
|
'materiels.view',
|
|
'materiels.edit',
|
|
'devis.view',
|
|
'devis.edit',
|
|
'factures.view',
|
|
'reports.view',
|
|
'dashboard.management',
|
|
],
|
|
[BTP_ROLES.CHEF_CHANTIER]: [
|
|
'chantiers.view',
|
|
'chantiers.edit',
|
|
'employes.view',
|
|
'materiels.view',
|
|
'materiels.reserve',
|
|
'planning.view',
|
|
'planning.edit',
|
|
'dashboard.chantier',
|
|
],
|
|
[BTP_ROLES.CONDUCTEUR_TRAVAUX]: [
|
|
'chantiers.view',
|
|
'employes.view',
|
|
'materiels.view',
|
|
'planning.view',
|
|
'dashboard.chantier',
|
|
],
|
|
[BTP_ROLES.CHEF_EQUIPE]: [
|
|
'chantiers.view',
|
|
'employes.view',
|
|
'materiels.view',
|
|
'planning.view',
|
|
'dashboard.equipe',
|
|
],
|
|
[BTP_ROLES.COMMERCIAL]: [
|
|
'clients.manage',
|
|
'devis.manage',
|
|
'factures.view',
|
|
'chantiers.view',
|
|
'dashboard.commercial',
|
|
],
|
|
[BTP_ROLES.COMPTABLE]: [
|
|
'factures.manage',
|
|
'devis.view',
|
|
'clients.view',
|
|
'reports.financial',
|
|
'dashboard.financial',
|
|
],
|
|
[BTP_ROLES.LOGISTICIEN]: [
|
|
'materiels.manage',
|
|
'chantiers.view',
|
|
'planning.view',
|
|
'dashboard.logistique',
|
|
],
|
|
[BTP_ROLES.EMPLOYE]: [
|
|
'chantiers.view',
|
|
'planning.view',
|
|
'profile.edit',
|
|
'dashboard.employe',
|
|
],
|
|
[BTP_ROLES.OUVRIER]: [
|
|
'chantiers.view',
|
|
'planning.view',
|
|
'profile.edit',
|
|
'dashboard.employe',
|
|
],
|
|
[BTP_ROLES.CLIENT_ENTREPRISE]: [
|
|
'chantiers.view.own',
|
|
'devis.view.own',
|
|
'factures.view.own',
|
|
'profile.edit',
|
|
'dashboard.client',
|
|
],
|
|
[BTP_ROLES.CLIENT_PARTICULIER]: [
|
|
'chantiers.view.own',
|
|
'devis.view.own',
|
|
'factures.view.own',
|
|
'profile.edit',
|
|
'dashboard.client',
|
|
],
|
|
[BTP_ROLES.VIEWER]: [
|
|
'chantiers.view',
|
|
'dashboard.readonly',
|
|
],
|
|
[BTP_ROLES.GUEST]: [
|
|
'dashboard.public',
|
|
],
|
|
} as const;
|
|
|
|
// Utilitaires pour la gestion des rôles
|
|
export const RoleUtils = {
|
|
/**
|
|
* Vérifie si l'utilisateur a un rôle spécifique
|
|
*/
|
|
hasRole: (userRoles: string[], requiredRole: string): boolean => {
|
|
return userRoles.includes(requiredRole);
|
|
},
|
|
|
|
/**
|
|
* Vérifie si l'utilisateur a au moins un des rôles requis
|
|
*/
|
|
hasAnyRole: (userRoles: string[], requiredRoles: string[]): boolean => {
|
|
return requiredRoles.some(role => userRoles.includes(role));
|
|
},
|
|
|
|
/**
|
|
* Vérifie si l'utilisateur a tous les rôles requis
|
|
*/
|
|
hasAllRoles: (userRoles: string[], requiredRoles: string[]): boolean => {
|
|
return requiredRoles.every(role => userRoles.includes(role));
|
|
},
|
|
|
|
/**
|
|
* Obtient le rôle le plus élevé de l'utilisateur
|
|
*/
|
|
getHighestRole: (userRoles: string[]): string | null => {
|
|
for (const role of ROLE_HIERARCHY) {
|
|
if (userRoles.includes(role)) {
|
|
return role;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Vérifie si l'utilisateur a une permission spécifique
|
|
*/
|
|
hasPermission: (userRoles: string[], permission: string): boolean => {
|
|
for (const role of userRoles) {
|
|
const rolePermissions = (ROLE_PERMISSIONS as any)[role];
|
|
if (rolePermissions?.includes('*') || rolePermissions?.includes(permission)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Obtient toutes les permissions de l'utilisateur
|
|
*/
|
|
getUserPermissions: (userRoles: string[]): string[] => {
|
|
const permissions = new Set<string>();
|
|
for (const role of userRoles) {
|
|
const rolePermissions = (ROLE_PERMISSIONS as any)[role];
|
|
if (rolePermissions) {
|
|
rolePermissions.forEach((permission: string) => permissions.add(permission));
|
|
}
|
|
}
|
|
return Array.from(permissions);
|
|
},
|
|
|
|
/**
|
|
* Vérifie si un rôle est supérieur à un autre
|
|
*/
|
|
isRoleHigher: (role1: string, role2: string): boolean => {
|
|
const index1 = ROLE_HIERARCHY.indexOf(role1 as any);
|
|
const index2 = ROLE_HIERARCHY.indexOf(role2 as any);
|
|
return index1 !== -1 && index2 !== -1 && index1 < index2;
|
|
},
|
|
};
|
|
|
|
// Configuration des redirections après authentification
|
|
export const KEYCLOAK_REDIRECTS = {
|
|
LOGIN_SUCCESS: '/dashboard',
|
|
LOGIN_ERROR: '/auth/error',
|
|
LOGOUT_SUCCESS: '/auth/logout',
|
|
UNAUTHORIZED: '/auth/unauthorized',
|
|
FORBIDDEN: '/auth/forbidden',
|
|
} as const;
|
|
|
|
// Configuration des timeouts
|
|
export const KEYCLOAK_TIMEOUTS = {
|
|
TOKEN_REFRESH_BEFORE_EXPIRY: 30, // secondes
|
|
SESSION_CHECK_INTERVAL: 60, // secondes
|
|
SILENT_CHECK_SSO_TIMEOUT: 5000, // millisecondes
|
|
} as const;
|
|
|
|
export default keycloak;
|