Initial commit
This commit is contained in:
73
config/api.ts
Normal file
73
config/api.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* API Configuration for BTP Xpress Client
|
||||
*/
|
||||
|
||||
export const API_CONFIG = {
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080',
|
||||
timeout: parseInt(process.env.NEXT_PUBLIC_API_TIMEOUT || '10000'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const KEYCLOAK_CONFIG = {
|
||||
url: process.env.NEXT_PUBLIC_KEYCLOAK_URL || 'https://security.lions.dev',
|
||||
realm: process.env.NEXT_PUBLIC_KEYCLOAK_REALM || 'btpxpress',
|
||||
clientId: process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || 'btpxpress-frontend',
|
||||
// Configuration pour l'authentification
|
||||
'ssl-required': 'external',
|
||||
'public-client': true,
|
||||
'confidential-port': 0,
|
||||
'use-resource-role-mappings': true,
|
||||
'cors': true,
|
||||
'enable-cors': true,
|
||||
'cors-max-age': 1000,
|
||||
'cors-allowed-methods': 'POST, PUT, DELETE, GET',
|
||||
'cors-allowed-headers': 'X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers',
|
||||
'bearer-only': false,
|
||||
'autodetect-bearer-only': true,
|
||||
'connection-pool-size': 20,
|
||||
'socket-timeout-millis': 5000,
|
||||
'connection-timeout-millis': 5000,
|
||||
'connection-ttl-millis': 5000,
|
||||
'disable-trust-manager': false,
|
||||
'allow-any-hostname': false,
|
||||
'truststore': '',
|
||||
'truststore-password': '',
|
||||
'client-keystore': '',
|
||||
'client-keystore-password': '',
|
||||
'client-key-password': '',
|
||||
'token-minimum-time-to-live': 10,
|
||||
'min-time-between-jwks-requests': 10,
|
||||
'public-key-cache-ttl': 86400,
|
||||
'redirect-rewrite-rules': {},
|
||||
} as const;
|
||||
|
||||
// Configuration des rôles et permissions
|
||||
export const KEYCLOAK_ROLES = {
|
||||
ADMIN: 'admin',
|
||||
MANAGER: 'manager',
|
||||
USER: 'user',
|
||||
CHEF_CHANTIER: 'chef_chantier',
|
||||
EMPLOYE: 'employe',
|
||||
CLIENT: 'client',
|
||||
} as const;
|
||||
|
||||
// Configuration des scopes OAuth2
|
||||
export const KEYCLOAK_SCOPES = [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
] as const;
|
||||
|
||||
export const APP_CONFIG = {
|
||||
name: process.env.NEXT_PUBLIC_APP_NAME || 'BTP Xpress',
|
||||
version: process.env.NEXT_PUBLIC_APP_VERSION || '1.0.0',
|
||||
description: process.env.NEXT_PUBLIC_APP_DESCRIPTION || 'Plateforme de gestion BTP',
|
||||
} as const;
|
||||
|
||||
export const THEME_CONFIG = {
|
||||
defaultTheme: process.env.NEXT_PUBLIC_DEFAULT_THEME || 'blue',
|
||||
defaultMode: process.env.NEXT_PUBLIC_DEFAULT_THEME_MODE || 'light',
|
||||
} as const;
|
||||
334
config/keycloak.ts
Normal file
334
config/keycloak.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* 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[role as keyof typeof ROLE_PERMISSIONS];
|
||||
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[role as keyof typeof ROLE_PERMISSIONS];
|
||||
if (rolePermissions) {
|
||||
rolePermissions.forEach(permission => 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;
|
||||
Reference in New Issue
Block a user