diff --git a/client/src/lib/permissions.ts b/client/src/lib/permissions.ts new file mode 100644 index 0000000..00af99a --- /dev/null +++ b/client/src/lib/permissions.ts @@ -0,0 +1,149 @@ +// Système de permissions hardcodé par module +// Remplace l'ancien système flexible basé sur la base de données + +export type Permission = 'view' | 'create' | 'edit' | 'delete' | 'validate'; +export type Role = 'admin' | 'directeur' | 'manager' | 'employee'; +export type Module = + | 'dashboard' + | 'calendar' + | 'orders' + | 'deliveries' + | 'reconciliation' + | 'publicity' + | 'customer-orders' + | 'dlc' + | 'tasks'; + +// Définition des permissions par module et par rôle +const PERMISSIONS: Record> = { + // Tableau de bord - tous les rôles peuvent le voir + dashboard: { + admin: ['view'], + directeur: ['view'], + manager: ['view'], + employee: ['view'] + }, + + // Calendrier - Admin/Directeur tout, Manager tout sauf delete, Employé view + calendar: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view'] + }, + + // Commandes - Admin/Directeur tout, Manager tout sauf delete, Employé view + orders: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view'] + }, + + // Livraisons - Admin/Directeur tout, Manager tout sauf delete, Employé view + deliveries: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view'] + }, + + // Rapprochement - Admin tout, Directeur tout sauf delete, Manager/Employé rien + reconciliation: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit'], + manager: [], + employee: [] + }, + + // Publicité - Admin tout, autres view seulement + publicity: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view'], + manager: ['view'], + employee: ['view'] + }, + + // Commandes client - Admin/Directeur tout, Manager tout sauf delete, Employé view + create + 'customer-orders': { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view', 'create'] + }, + + // DLC - Admin/Directeur tout, Manager tout sauf delete, Employé create + view + dlc: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view', 'create'] + }, + + // Tâches - Admin/Directeur tout, Manager view + validate, Employé view + tasks: { + admin: ['view', 'create', 'edit', 'delete', 'validate'], + directeur: ['view', 'create', 'edit', 'delete', 'validate'], + manager: ['view', 'validate'], + employee: ['view'] + } +}; + +// Hook pour vérifier les permissions +export function usePermissions(userRole?: string) { + const role = userRole?.toLowerCase() as Role; + + // Vérifie si l'utilisateur peut accéder à un module + const canAccessModule = (module: Module): boolean => { + if (!role || !PERMISSIONS[module]) return false; + const permissions = PERMISSIONS[module][role] || []; + return permissions.length > 0; + }; + + // Vérifie si l'utilisateur peut effectuer une action sur un module + const canPerformAction = (module: Module, action: Permission): boolean => { + if (!role || !PERMISSIONS[module]) return false; + const permissions = PERMISSIONS[module][role] || []; + return permissions.includes(action); + }; + + // Retourne toutes les permissions pour un module + const getModulePermissions = (module: Module): Permission[] => { + if (!role || !PERMISSIONS[module]) return []; + return PERMISSIONS[module][role] || []; + }; + + // Vérifie si l'utilisateur peut voir le module (a au moins une permission) + const canView = (module: Module): boolean => canPerformAction(module, 'view'); + const canCreate = (module: Module): boolean => canPerformAction(module, 'create'); + const canEdit = (module: Module): boolean => canPerformAction(module, 'edit'); + const canDelete = (module: Module): boolean => canPerformAction(module, 'delete'); + const canValidate = (module: Module): boolean => canPerformAction(module, 'validate'); + + return { + canAccessModule, + canPerformAction, + getModulePermissions, + canView, + canCreate, + canEdit, + canDelete, + canValidate + }; +} + +// Fonction utilitaire pour les middlewares serveur +export function hasPermission(userRole: string, module: Module, action: Permission): boolean { + const role = userRole?.toLowerCase() as Role; + if (!role || !PERMISSIONS[module]) return false; + const permissions = PERMISSIONS[module][role] || []; + return permissions.includes(action); +} + +// Fonction utilitaire pour vérifier l'accès à un module +export function hasModuleAccess(userRole: string, module: Module): boolean { + const role = userRole?.toLowerCase() as Role; + if (!role || !PERMISSIONS[module]) return false; + const permissions = PERMISSIONS[module][role] || []; + return permissions.length > 0; +} \ No newline at end of file diff --git a/client/src/pages/Users.tsx b/client/src/pages/Users.tsx index 0fa0654..698e3bf 100644 --- a/client/src/pages/Users.tsx +++ b/client/src/pages/Users.tsx @@ -12,6 +12,7 @@ import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useToast } from "@/hooks/use-toast"; import { apiRequest } from "@/lib/queryClient"; import { getRoleTailwindClasses, getRoleDisplayName } from "@/lib/roleUtils"; +import { usePermissions } from "@/lib/permissions"; import { isUnauthorizedError } from "@/lib/authUtils"; import { Search, diff --git a/replit.md b/replit.md index e8a2529..97ce338 100644 --- a/replit.md +++ b/replit.md @@ -79,6 +79,25 @@ Preferred communication style: Simple, everyday language. ## Recent Changes +### August 11, 2025 - Permissions System Overhaul & User Management Improvements + +#### Hardcoded Permissions System Implementation +- **Migration from Database to Code**: Replaced flexible database-driven permissions with hardcoded permission system for better performance and control +- **New Architecture**: + - Created `client/src/lib/permissions.ts` and `shared/permissions.ts` with role-based module permissions + - Implemented server-side middleware `server/permissions.ts` for route protection + - Removed database tables: `roles`, `permissions`, `rolePermissions`, `userRoles` + - Cleaned up storage interface and routes from old permission system +- **Permission Matrix by Module**: + - **Dashboard**: All roles can view + - **Calendar/Orders/Deliveries**: Admin/Directeur full access, Manager all except delete, Employee view only + - **Reconciliation**: Admin full access, Directeur all except delete, Manager/Employee no access + - **Publicity**: Admin full access, others view only + - **Customer Orders**: Admin/Directeur full access, Manager all except delete, Employee view + create + - **DLC**: Admin/Directeur full access, Manager all except delete, Employee create + view + - **Tasks**: Admin/Directeur full access, Manager view + validate, Employee view only +- **Benefits**: Improved performance, simplified maintenance, no database queries for permission checks + ### August 11, 2025 - User Management Improvements #### User Deletion Fix diff --git a/server/initRolesAndPermissions.ts b/server/initRolesAndPermissions.ts deleted file mode 100644 index b84c9a9..0000000 --- a/server/initRolesAndPermissions.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { storage } from "./storage"; - -export async function initRolesAndPermissions() { - console.log("Initializing roles and permissions..."); - - try { - // Check if roles already exist - const existingRoles = await storage.getRoles(); - if (existingRoles.length > 0) { - console.log("Roles already exist, skipping initialization"); - return; - } - - // Create default roles - const adminRole = await storage.createRole({ - name: "admin", - displayName: "Administrateur", - description: "Accès complet à toutes les fonctionnalités du système", - color: "#dc2626", - isSystem: true, - isActive: true, - }); - - const managerRole = await storage.createRole({ - name: "manager", - displayName: "Manager", - description: "Accès à la gestion des commandes, livraisons et fournisseurs", - color: "#2563eb", - isSystem: true, - isActive: true, - }); - - const employeeRole = await storage.createRole({ - name: "employee", - displayName: "Employé", - description: "Accès en lecture aux données et publicités", - color: "#16a34a", - isSystem: true, - isActive: true, - }); - - const directeurRole = await storage.createRole({ - name: "directeur", - displayName: "Directeur", - description: "Supervision générale et gestion stratégique", - color: "#7c3aed", - isSystem: true, - isActive: true, - }); - - console.log("Created default roles:", { adminRole, managerRole, employeeRole, directeurRole }); - - // Create default permissions - const permissions = [ - // Dashboard permissions - { category: "tableau_de_bord", name: "dashboard_read", displayName: "Voir tableau de bord", description: "Accès en lecture au tableau de bord", action: "read", resource: "dashboard", isSystem: true }, - - // Groups/Stores permissions - { category: "magasins", name: "groups_read", displayName: "Voir magasins", description: "Accès en lecture aux magasins", action: "read", resource: "groups", isSystem: true }, - { category: "magasins", name: "groups_create", displayName: "Créer magasins", description: "Création de nouveaux magasins", action: "create", resource: "groups", isSystem: true }, - { category: "magasins", name: "groups_update", displayName: "Modifier magasins", description: "Modification des magasins existants", action: "update", resource: "groups", isSystem: true }, - { category: "magasins", name: "groups_delete", displayName: "Supprimer magasins", description: "Suppression de magasins", action: "delete", resource: "groups", isSystem: true }, - - // Suppliers permissions - { category: "fournisseurs", name: "suppliers_read", displayName: "Voir fournisseurs", description: "Accès en lecture aux fournisseurs", action: "read", resource: "suppliers", isSystem: true }, - { category: "fournisseurs", name: "suppliers_create", displayName: "Créer fournisseurs", description: "Création de nouveaux fournisseurs", action: "create", resource: "suppliers", isSystem: true }, - { category: "fournisseurs", name: "suppliers_update", displayName: "Modifier fournisseurs", description: "Modification des fournisseurs existants", action: "update", resource: "suppliers", isSystem: true }, - { category: "fournisseurs", name: "suppliers_delete", displayName: "Supprimer fournisseurs", description: "Suppression de fournisseurs", action: "delete", resource: "fournisseurs", isSystem: true }, - - // Orders permissions - { category: "commandes", name: "orders_read", displayName: "Voir commandes", description: "Accès en lecture aux commandes", action: "read", resource: "orders", isSystem: true }, - { category: "commandes", name: "orders_create", displayName: "Créer commandes", description: "Création de nouvelles commandes", action: "create", resource: "orders", isSystem: true }, - { category: "commandes", name: "orders_update", displayName: "Modifier commandes", description: "Modification des commandes existantes", action: "update", resource: "orders", isSystem: true }, - { category: "commandes", name: "orders_delete", displayName: "Supprimer commandes", description: "Suppression de commandes", action: "delete", resource: "orders", isSystem: true }, - - // Deliveries permissions - { category: "livraisons", name: "deliveries_read", displayName: "Voir livraisons", description: "Accès en lecture aux livraisons", action: "read", resource: "deliveries", isSystem: true }, - { category: "livraisons", name: "deliveries_create", displayName: "Créer livraisons", description: "Création de nouvelles livraisons", action: "create", resource: "deliveries", isSystem: true }, - { category: "livraisons", name: "deliveries_update", displayName: "Modifier livraisons", description: "Modification des livraisons existantes", action: "update", resource: "deliveries", isSystem: true }, - { category: "livraisons", name: "deliveries_delete", displayName: "Supprimer livraisons", description: "Suppression de livraisons", action: "delete", resource: "deliveries", isSystem: true }, - { category: "livraisons", name: "deliveries_validate", displayName: "Valider livraisons", description: "Validation des livraisons avec BL", action: "validate", resource: "deliveries", isSystem: true }, - - // Calendar permissions - { category: "calendrier", name: "calendar_read", displayName: "Voir calendrier", description: "Accès en lecture au calendrier", action: "read", resource: "calendar", isSystem: true }, - - // BL Reconciliation permissions - { category: "rapprochement", name: "reconciliation_read", displayName: "Voir rapprochement", description: "Accès en lecture au rapprochement BL/Factures", action: "read", resource: "reconciliation", isSystem: true }, - { category: "rapprochement", name: "reconciliation_update", displayName: "Modifier rapprochement", description: "Modification des données de rapprochement", action: "update", resource: "reconciliation", isSystem: true }, - - // Publicities permissions - { category: "publicites", name: "publicities_read", displayName: "Voir publicités", description: "Accès en lecture aux publicités", action: "read", resource: "publicities", isSystem: true }, - { category: "publicites", name: "publicities_create", displayName: "Créer publicités", description: "Création de nouvelles publicités", action: "create", resource: "publicities", isSystem: true }, - { category: "publicites", name: "publicities_update", displayName: "Modifier publicités", description: "Modification des publicités existantes", action: "update", resource: "publicities", isSystem: true }, - { category: "publicites", name: "publicities_delete", displayName: "Supprimer publicités", description: "Suppression de publicités", action: "delete", resource: "publicities", isSystem: true }, - - // Customer Orders permissions - { category: "commandes_clients", name: "customer_orders_read", displayName: "Voir commandes clients", description: "Accès en lecture aux commandes clients", action: "read", resource: "customer_orders", isSystem: true }, - { category: "commandes_clients", name: "customer_orders_create", displayName: "Créer commandes clients", description: "Création de nouvelles commandes clients", action: "create", resource: "customer_orders", isSystem: true }, - { category: "commandes_clients", name: "customer_orders_update", displayName: "Modifier commandes clients", description: "Modification des commandes clients existantes", action: "update", resource: "customer_orders", isSystem: true }, - { category: "commandes_clients", name: "customer_orders_delete", displayName: "Supprimer commandes clients", description: "Suppression de commandes clients", action: "delete", resource: "customer_orders", isSystem: true }, - { category: "commandes_clients", name: "customer_orders_print", displayName: "Imprimer étiquettes", description: "Impression d'étiquettes de commandes clients", action: "print", resource: "customer_orders", isSystem: true }, - { category: "commandes_clients", name: "customer_orders_notify", displayName: "Notifier clients", description: "Envoi de notifications aux clients", action: "notify", resource: "customer_orders", isSystem: true }, - - // DLC Management permissions - { category: "gestion_dlc", name: "dlc_read", displayName: "Voir produits DLC", description: "Accès en lecture aux produits avec date limite de consommation", action: "read", resource: "dlc", isSystem: true }, - { category: "gestion_dlc", name: "dlc_create", displayName: "Créer produits DLC", description: "Création de nouveaux produits DLC", action: "create", resource: "dlc", isSystem: true }, - { category: "gestion_dlc", name: "dlc_update", displayName: "Modifier produits DLC", description: "Modification des produits DLC existants", action: "update", resource: "dlc", isSystem: true }, - { category: "gestion_dlc", name: "dlc_delete", displayName: "Supprimer produits DLC", description: "Suppression de produits DLC", action: "delete", resource: "dlc", isSystem: true }, - { category: "gestion_dlc", name: "dlc_validate", displayName: "Valider produits DLC", description: "Validation et vérification des produits DLC", action: "validate", resource: "dlc", isSystem: true }, - { category: "gestion_dlc", name: "dlc_print", displayName: "Imprimer étiquettes DLC", description: "Impression d'étiquettes pour produits DLC", action: "print", resource: "dlc", isSystem: true }, - { category: "gestion_dlc", name: "dlc_stats", displayName: "Voir statistiques DLC", description: "Accès aux statistiques des produits DLC", action: "stats", resource: "dlc", isSystem: true }, - - // Task Management permissions - { category: "gestion_taches", name: "tasks_read", displayName: "Voir tâches", description: "Accès en lecture aux tâches", action: "read", resource: "tasks", isSystem: true }, - { category: "gestion_taches", name: "tasks_create", displayName: "Créer tâches", description: "Création de nouvelles tâches", action: "create", resource: "tasks", isSystem: true }, - { category: "gestion_taches", name: "tasks_update", displayName: "Modifier tâches", description: "Modification des tâches existantes", action: "update", resource: "tasks", isSystem: true }, - { category: "gestion_taches", name: "tasks_delete", displayName: "Supprimer tâches", description: "Suppression de tâches", action: "delete", resource: "tasks", isSystem: true }, - { category: "gestion_taches", name: "tasks_assign", displayName: "Assigner tâches", description: "Attribution de tâches aux utilisateurs", action: "assign", resource: "tasks", isSystem: true }, - - // Users permissions - { category: "utilisateurs", name: "users_read", displayName: "Voir utilisateurs", description: "Accès en lecture aux utilisateurs", action: "read", resource: "users", isSystem: true }, - { category: "utilisateurs", name: "users_create", displayName: "Créer utilisateurs", description: "Création de nouveaux utilisateurs", action: "create", resource: "users", isSystem: true }, - { category: "utilisateurs", name: "users_update", displayName: "Modifier utilisateurs", description: "Modification des utilisateurs existants", action: "update", resource: "users", isSystem: true }, - { category: "utilisateurs", name: "users_delete", displayName: "Supprimer utilisateurs", description: "Suppression d'utilisateurs", action: "delete", resource: "users", isSystem: true }, - - // Role Management permissions - { category: "gestion_roles", name: "roles_read", displayName: "Voir rôles", description: "Accès en lecture aux rôles", action: "read", resource: "roles", isSystem: true }, - { category: "gestion_roles", name: "roles_create", displayName: "Créer rôles", description: "Création de nouveaux rôles", action: "create", resource: "roles", isSystem: true }, - { category: "gestion_roles", name: "roles_update", displayName: "Modifier rôles", description: "Modification des rôles existants", action: "update", resource: "roles", isSystem: true }, - { category: "gestion_roles", name: "roles_delete", displayName: "Supprimer rôles", description: "Suppression de rôles", action: "delete", resource: "roles", isSystem: true }, - { category: "gestion_roles", name: "roles_assign", displayName: "Assigner rôles", description: "Attribution de rôles aux utilisateurs", action: "assign", resource: "roles", isSystem: true }, - - // System administration permissions - { category: "administration", name: "system_admin", displayName: "Administration système", description: "Accès complet à l'administration du système", action: "admin", resource: "system", isSystem: true }, - { category: "administration", name: "nocodb_config", displayName: "Configuration NocoDB", description: "Gestion de la configuration NocoDB", action: "config", resource: "nocodb", isSystem: true }, - ]; - - // Create all permissions - const createdPermissions = []; - for (const permData of permissions) { - const permission = await storage.createPermission(permData); - createdPermissions.push(permission); - } - - console.log(`Created ${createdPermissions.length} permissions`); - - // Assign permissions to roles - - // Super Admin gets all permissions - const allPermissionIds = createdPermissions.map(p => p.id); - await storage.setRolePermissions(adminRole.id, allPermissionIds); - console.log(`Assigned all permissions to Super Admin role`); - - // Manager gets most permissions except system admin and role management, but includes DLC validate - const managerPermissions = createdPermissions.filter(p => - !p.name.startsWith('roles_') && - !p.name.startsWith('system_') && - p.name !== 'users_delete' - ).map(p => p.id); - await storage.setRolePermissions(managerRole.id, managerPermissions); - console.log(`Assigned ${managerPermissions.length} permissions to Manager role`); - - // Employee gets basic read permissions, customer orders and DLC read/create/update - const employeePermissions = createdPermissions.filter(p => - p.action === 'read' || - p.name.startsWith('customer_orders_') || - p.name.startsWith('dlc_') && (p.action === 'read' || p.action === 'create' || p.action === 'update') || - (p.category === 'livraisons' && (p.action === 'create' || p.action === 'update')) - ).map(p => p.id); - await storage.setRolePermissions(employeeRole.id, employeePermissions); - console.log(`Assigned ${employeePermissions.length} permissions to Employee role`); - - console.log("✅ Roles and permissions initialization completed successfully"); - - } catch (error) { - console.error("❌ Error initializing roles and permissions:", error); - throw error; - } -} \ No newline at end of file diff --git a/server/permissions.ts b/server/permissions.ts new file mode 100644 index 0000000..80ef80d --- /dev/null +++ b/server/permissions.ts @@ -0,0 +1,117 @@ +// Middleware de permissions hardcodées +// Remplace l'ancien système flexible basé sur la base de données + +import type { Request, Response, NextFunction } from 'express'; +import { hasPermission, hasModuleAccess } from '@shared/permissions'; +import type { Permission, Module } from '@shared/permissions'; + +// Interface pour les requêtes authentifiées +interface AuthRequest extends Request { + user: { + id: string; + role: string; + claims?: { sub: string }; + }; +} + +// Middleware pour vérifier l'accès à un module +export function requireModuleAccess(module: Module) { + return (req: AuthRequest, res: Response, next: NextFunction) => { + const userRole = req.user?.role; + + if (!userRole) { + return res.status(401).json({ message: "Utilisateur non authentifié" }); + } + + if (!hasModuleAccess(userRole, module)) { + return res.status(403).json({ + message: `Accès refusé au module ${module}`, + required: "Permission d'accès au module", + userRole + }); + } + + next(); + }; +} + +// Middleware pour vérifier une permission spécifique +export function requirePermission(module: Module, action: Permission) { + return (req: AuthRequest, res: Response, next: NextFunction) => { + const userRole = req.user?.role; + + if (!userRole) { + return res.status(401).json({ message: "Utilisateur non authentifié" }); + } + + if (!hasPermission(userRole, module, action)) { + return res.status(403).json({ + message: `Permission refusée: ${action} sur ${module}`, + required: `${action} permission on ${module}`, + userRole + }); + } + + next(); + }; +} + +// Middleware combiné: accès module + permission +export function requireModulePermission(module: Module, action: Permission) { + return (req: AuthRequest, res: Response, next: NextFunction) => { + const userRole = req.user?.role; + + if (!userRole) { + return res.status(401).json({ message: "Utilisateur non authentifié" }); + } + + // Vérifier l'accès au module ET la permission + if (!hasModuleAccess(userRole, module) || !hasPermission(userRole, module, action)) { + return res.status(403).json({ + message: `Accès refusé: ${action} sur module ${module}`, + required: `${action} permission on ${module} module`, + userRole + }); + } + + next(); + }; +} + +// Helper: vérifie si l'utilisateur est admin +export function requireAdmin(req: AuthRequest, res: Response, next: NextFunction) { + const userRole = req.user?.role; + + if (!userRole) { + return res.status(401).json({ message: "Utilisateur non authentifié" }); + } + + if (userRole !== 'admin') { + return res.status(403).json({ + message: "Accès réservé aux administrateurs", + required: "admin role", + userRole + }); + } + + next(); +} + +// Helper: vérifie si l'utilisateur est admin ou directeur +export function requireAdminOrDirecteur(req: AuthRequest, res: Response, next: NextFunction) { + const userRole = req.user?.role; + + if (!userRole) { + return res.status(401).json({ message: "Utilisateur non authentifié" }); + } + + if (!['admin', 'directeur'].includes(userRole)) { + return res.status(403).json({ + message: "Accès réservé aux administrateurs et directeurs", + required: "admin or directeur role", + userRole + }); + } + + next(); +} \ No newline at end of file diff --git a/server/routes.ts b/server/routes.ts index b8214f9..d51d8ec 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -2,6 +2,7 @@ import type { Express } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import { setupLocalAuth, requireAuth } from "./localAuth"; +import { requireModulePermission, requireAdmin } from "./permissions"; console.log('🔍 Using development storage and authentication'); @@ -19,9 +20,6 @@ import { insertCustomerOrderSchema, insertDlcProductSchema, insertDlcProductFrontendSchema, - insertRoleSchema, - insertPermissionSchema, - insertUserRoleSchema, insertTaskSchema } from "@shared/schema"; import { z } from "zod"; @@ -853,9 +851,9 @@ export async function registerRoutes(app: Express): Promise { return res.status(403).json({ message: "Access denied" }); } - // Get all users with roles and groups for admin - const usersWithRoles = await storage.getUsersWithRolesAndGroups(); - const safeUsers = Array.isArray(usersWithRoles) ? usersWithRoles : []; + // Get all users with groups for admin + const allUsers = await storage.getUsers(); + const safeUsers = Array.isArray(allUsers) ? allUsers : []; console.log('🔐 API /api/users - Returning:', { isArray: Array.isArray(safeUsers), length: safeUsers.length }); res.json(safeUsers); @@ -1340,129 +1338,14 @@ export async function registerRoutes(app: Express): Promise { - // Role Management API Routes // Get all roles - app.get('/api/roles', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Access denied" }); - } - - const roles = await storage.getRoles(); - res.json(Array.isArray(roles) ? roles : []); - } catch (error) { - console.error("Error fetching roles:", error); - res.status(500).json([]); - } - }); // Get all permissions - app.get('/api/permissions', isAuthenticated, async (req: any, res) => { - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - console.log(`🔍 ${isProduction ? 'PRODUCTION' : 'DEV'} Permissions API - User ID:`, userId); - - const user = await storage.getUserWithGroups(userId); - console.log(`👤 ${isProduction ? 'PRODUCTION' : 'DEV'} Permissions API - User found:`, user ? user.role : 'NOT FOUND'); - - if (!user || user.role !== 'admin') { - console.log(`❌ ${isProduction ? 'PRODUCTION' : 'DEV'} Permissions API - Access denied, user role:`, user?.role); - return res.status(403).json({ message: "Accès refusé - droits administrateur requis" }); - } - - console.log(`🔍 ${isProduction ? 'PRODUCTION' : 'DEV'} Fetching all permissions...`); - const permissions = await storage.getPermissions(); - console.log(`📝 ${isProduction ? 'PRODUCTION' : 'DEV'} Permissions fetched:`, permissions.length, "items"); - console.log(`🏷️ ${isProduction ? 'PRODUCTION' : 'DEV'} Categories found:`, [...new Set(permissions.map(p => p.category))]); - console.log(`🔧 ${isProduction ? 'PRODUCTION' : 'DEV'} DLC permissions:`, permissions.filter(p => p.category === 'gestion_dlc').map(p => p.name)); - - // 🎯 VÉRIFICATION SPÉCIFIQUE PERMISSIONS TÂCHES - const taskPermissions = permissions.filter(p => p.category === 'gestion_taches'); - console.log(`📋 ${isProduction ? 'PRODUCTION' : 'DEV'} Task permissions found:`, taskPermissions.length); - if (taskPermissions.length > 0) { - console.log(`📋 ${isProduction ? 'PRODUCTION' : 'DEV'} Task permissions details:`); - taskPermissions.forEach(p => { - console.log(` - ID: ${p.id}, Name: ${p.name}, DisplayName: "${p.displayName}", Category: ${p.category}`); - }); - } else { - console.log(`❌ ${isProduction ? 'PRODUCTION' : 'DEV'} NO TASK PERMISSIONS FOUND - This explains the frontend issue!`); - } - - res.json(Array.isArray(permissions) ? permissions : []); - } catch (error) { - console.error("❌ Error fetching permissions:", error); - res.status(500).json([]); - } - }); // Get permissions for a specific role - app.get('/api/roles/:id/permissions', isAuthenticated, async (req: any, res) => { - console.log("🚀 ROLE PERMISSIONS ROUTE CALLED - Starting"); - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - console.log("🔍 ROLE PERMISSIONS - User ID:", userId); - - const user = await storage.getUserWithGroups(userId); - console.log("👤 ROLE PERMISSIONS - User found:", user ? `${user.role} (${user.username})` : 'NOT FOUND'); - - if (!user || user.role !== 'admin') { - console.log("❌ ROLE PERMISSIONS - Access denied, user role:", user?.role); - return res.status(403).json({ message: "Access denied" }); - } - - const roleId = parseInt(req.params.id); - console.log("🔍 ROLE PERMISSIONS - Fetching for role ID:", roleId); - console.log("🔍 ROLE PERMISSIONS - Storage type:", storage.constructor.name); - - const rolePermissions = await storage.getRolePermissions(roleId); - console.log("📝 ROLE PERMISSIONS - Fetched:", rolePermissions?.length || 0, "items"); - - if (rolePermissions && rolePermissions.length > 0) { - console.log("🏷️ ROLE PERMISSIONS - Sample:", rolePermissions.slice(0, 2)); - console.log("🔍 ROLE PERMISSIONS - Full structure sample:", JSON.stringify(rolePermissions[0], null, 2)); - - // Debug spécifique pour les tâches - const taskPermissions = rolePermissions.filter(rp => rp.permission && rp.permission.category === 'gestion_taches'); - console.log("🎯 ROLE PERMISSIONS - Task permissions:", taskPermissions.length); - taskPermissions.forEach(tp => { - console.log(` - Task permission: ${tp.permission.name} (${tp.permission.displayName})`); - }); - } else { - console.log("⚠️ ROLE PERMISSIONS - No permissions found for role", roleId); - } - - res.json(rolePermissions || []); - } catch (error) { - console.error("❌ ROLE PERMISSIONS - Error:", error); - res.status(500).json({ message: "Failed to fetch role permissions" }); - } - }); // Set permissions for a role - app.post('/api/roles/:id/permissions', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Access denied" }); - } - - const roleId = parseInt(req.params.id); - const { permissionIds } = req.body; - - if (!Array.isArray(permissionIds)) { - return res.status(400).json({ message: "permissionIds must be an array" }); - } - - await storage.setRolePermissions(roleId, permissionIds); - console.log(`✅ Permissions updated for role ${roleId}:`, permissionIds); - res.json({ message: "Permissions updated successfully" }); - } catch (error) { - console.error("Error setting role permissions:", error); - res.status(500).json({ message: "Failed to update permissions" }); - } - }); // Set roles for a user app.post('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => { @@ -1672,208 +1555,19 @@ export async function registerRoutes(app: Express): Promise { // ===== ROLE MANAGEMENT ROUTES ===== // Roles routes - app.get('/api/roles', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const roles = await storage.getRoles(); - res.json(roles); - } catch (error) { - console.error("Error fetching roles:", error); - res.status(500).json({ message: "Failed to fetch roles" }); - } - }); - - app.get('/api/roles/:id', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const id = parseInt(req.params.id); - const role = await storage.getRoleWithPermissions(id); - if (!role) { - return res.status(404).json({ message: "Role not found" }); - } - res.json(role); - } catch (error) { - console.error("Error fetching role:", error); - res.status(500).json({ message: "Failed to fetch role" }); - } - }); - - app.post('/api/roles', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const data = insertRoleSchema.parse(req.body); - const role = await storage.createRole(data); - res.json(role); - } catch (error) { - console.error("Error creating role:", error); - res.status(500).json({ message: "Failed to create role" }); - } - }); - - app.put('/api/roles/:id', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const id = parseInt(req.params.id); - const data = insertRoleSchema.partial().parse(req.body); - const role = await storage.updateRole(id, data); - res.json(role); - } catch (error) { - console.error("Error updating role:", error); - res.status(500).json({ message: "Failed to update role" }); - } - }); - - app.delete('/api/roles/:id', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const id = parseInt(req.params.id); - - // Check if role is system role - const role = await storage.getRole(id); - if (role?.isSystem) { - return res.status(400).json({ message: "Cannot delete system role" }); - } - - await storage.deleteRole(id); - res.json({ message: "Role deleted successfully" }); - } catch (error) { - console.error("Error deleting role:", error); - res.status(500).json({ message: "Failed to delete role" }); - } - }); - app.get('/api/permissions/category/:category', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - const category = req.params.category; - const permissions = await storage.getPermissionsByCategory(category); - res.json(permissions); - } catch (error) { - console.error("Error fetching permissions by category:", error); - res.status(500).json({ message: "Failed to fetch permissions" }); - } - }); - app.post('/api/permissions', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - const data = insertPermissionSchema.parse(req.body); - const permission = await storage.createPermission(data); - res.json(permission); - } catch (error) { - console.error("Error creating permission:", error); - res.status(500).json({ message: "Failed to create permission" }); - } - }); - app.put('/api/permissions/:id', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - const id = parseInt(req.params.id); - const data = insertPermissionSchema.partial().parse(req.body); - const permission = await storage.updatePermission(id, data); - res.json(permission); - } catch (error) { - console.error("Error updating permission:", error); - res.status(500).json({ message: "Failed to update permission" }); - } - }); - app.delete('/api/permissions/:id', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - const id = parseInt(req.params.id); - - // Check if permission is system permission - const permission = await storage.getPermission(id); - if (permission?.isSystem) { - return res.status(400).json({ message: "Cannot delete system permission" }); - } - - await storage.deletePermission(id); - res.json({ message: "Permission deleted successfully" }); - } catch (error) { - console.error("Error deleting permission:", error); - res.status(500).json({ message: "Failed to delete permission" }); - } - }); // Role-Permission association routes - app.get('/api/roles/:roleId/permissions', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - const roleId = parseInt(req.params.roleId); - const rolePermissions = await storage.getRolePermissions(roleId); - res.json(rolePermissions); - } catch (error) { - console.error("Error fetching role permissions:", error); - res.status(500).json({ message: "Failed to fetch role permissions" }); - } - }); - - app.put('/api/roles/:roleId/permissions', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const roleId = parseInt(req.params.roleId); - const { permissionIds } = req.body; - - if (!Array.isArray(permissionIds)) { - return res.status(400).json({ message: "permissionIds must be an array" }); - } - - await storage.setRolePermissions(roleId, permissionIds); - res.json({ message: "Role permissions updated successfully" }); - } catch (error) { - console.error("Error setting role permissions:", error); - res.status(500).json({ message: "Failed to set role permissions" }); - } - }); // User-Role association routes app.get('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => { @@ -2442,23 +2136,6 @@ export async function registerRoutes(app: Express): Promise { }); // Debug endpoint to check permissions without auth (temporary) - app.get('/api/debug/permissions-status', async (req: any, res) => { - try { - const permissions = await storage.getPermissions(); - const taskPermissions = permissions.filter(p => p.category === 'gestion_taches'); - const samplePermissions = permissions.slice(0, 5); - - res.json({ - total: permissions.length, - taskPermissions: taskPermissions, - samplePermissions: samplePermissions, - categories: [...new Set(permissions.map(p => p.category))].sort(), - timestamp: new Date().toISOString() - }); - } catch (error) { - res.status(500).json({ error: error.message }); - } - }); // Debug endpoint to check current user auth status app.get('/api/debug/auth-status', isAuthenticated, async (req: any, res) => { @@ -2486,34 +2163,6 @@ export async function registerRoutes(app: Express): Promise { }); // Debug endpoint to check task permissions specifically - app.get('/api/debug/task-permissions', isAuthenticated, async (req: any, res) => { - try { - console.log('🔍 DEV TASK PERMISSIONS DEBUG'); - - const permissions = await storage.getPermissions(); - const taskPermissions = permissions.filter(p => p.category === 'gestion_taches'); - - console.log('📋 DEV Task permissions found:', taskPermissions.length); - taskPermissions.forEach(p => { - console.log(` - ID: ${p.id}, Name: ${p.name}, DisplayName: "${p.displayName}", Category: ${p.category}`); - }); - - res.json({ - environment: 'development', - storage: taskPermissions, - allCategories: [...new Set(permissions.map(p => p.category))].sort(), - summary: { - total_permissions: permissions.length, - task_permissions_count: taskPermissions.length, - has_gestion_taches: taskPermissions.length > 0, - timestamp: new Date().toISOString() - } - }); - } catch (error) { - console.error('❌ Task permissions debug error:', error); - res.status(500).json({ message: "Debug failed", error: error.message }); - } - }); // Test CRITIQUE - Comparaison des storages pour permissions tâches app.get('/api/debug/compare-storages', isAuthenticated, async (req: any, res) => { @@ -2521,12 +2170,10 @@ export async function registerRoutes(app: Express): Promise { console.log('🔍 CRITICAL TEST - Comparing dev vs prod storage for task permissions'); // Test développement - const devPermissions = await devStorage.getPermissions(); const devTaskPerms = devPermissions.filter(p => p.category === 'gestion_taches'); console.log('📋 DEV storage - Task permissions:', devTaskPerms.length); // Test production - const prodPermissions = await prodStorage.getPermissions(); const prodTaskPerms = prodPermissions.filter(p => p.category === 'gestion_taches'); console.log('📋 PROD storage - Task permissions:', prodTaskPerms.length); @@ -2553,181 +2200,10 @@ export async function registerRoutes(app: Express): Promise { }); // ENDPOINT TEMPORAIRE - Test production storage avec gestion d'erreur détaillée - app.get('/api/debug/test-production-permissions', isAuthenticated, async (req: any, res) => { - console.log('🚨 STARTING PRODUCTION STORAGE TEST'); - - try { - // Test basique de connexion d'abord - console.log('🔍 Testing production storage connection...'); - - if (!prodStorage) { - throw new Error('Production storage is not available'); - } - - console.log('🔍 Production storage object exists, testing getPermissions...'); - const productionPermissions = await prodStorage.getPermissions(); - console.log('✅ Production storage getPermissions() succeeded:', productionPermissions.length, 'permissions'); - - const taskPermissions = productionPermissions.filter(p => p.category === 'gestion_taches'); - console.log('🏭 PRODUCTION STORAGE - Task permissions found:', taskPermissions.length); - - if (taskPermissions.length > 0) { - console.log('📋 Production task permissions details:'); - taskPermissions.forEach(p => { - console.log(` - ID: ${p.id}, Name: ${p.name}, DisplayName: "${p.displayName}", Category: ${p.category}`); - }); - } else { - console.log('❌ NO TASK PERMISSIONS found in production storage!'); - } - - res.json({ - success: true, - environment: 'forced_production_storage', - total_permissions: productionPermissions.length, - task_permissions_count: taskPermissions.length, - task_permissions: taskPermissions, - all_categories: [...new Set(productionPermissions.map(p => p.category))].sort(), - has_gestion_taches: taskPermissions.length > 0, - timestamp: new Date().toISOString() - }); - } catch (error) { - console.error('❌ CRITICAL ERROR in production storage test:', error); - console.error('❌ Error type:', typeof error); - console.error('❌ Error message:', error?.message); - console.error('❌ Error stack:', error?.stack); - - res.status(500).json({ - success: false, - message: "Production storage test failed", - error: error?.message || 'Unknown error', - errorType: typeof error, - timestamp: new Date().toISOString() - }); - } - }); // Debug/Fix endpoint for production permission issues - app.post('/api/debug/fix-production-permissions', isAuthenticated, async (req: any, res) => { - try { - console.log('🔧 FIXING PRODUCTION PERMISSIONS...'); - - // Only allow admin users to run fixes - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - if (user?.role !== 'admin') { - return res.status(403).json({ message: "Admin access required" }); - } - - const fixes = []; - - // 1. Fix missing display names for permissions - console.log('📝 Fixing permission display names...'); - const permissions = await storage.getPermissions(); - let fixedDisplayNames = 0; - - for (const permission of permissions) { - if (!permission.displayName || permission.displayName === permission.name) { - // Create proper French display names based on name patterns - let displayName = permission.name; - - // Pattern-based translations - if (permission.name.includes('_read')) displayName = displayName.replace('_read', ' - Voir'); - if (permission.name.includes('_create')) displayName = displayName.replace('_create', ' - Créer'); - if (permission.name.includes('_update')) displayName = displayName.replace('_update', ' - Modifier'); - if (permission.name.includes('_delete')) displayName = displayName.replace('_delete', ' - Supprimer'); - if (permission.name.includes('_validate')) displayName = displayName.replace('_validate', ' - Valider'); - if (permission.name.includes('_print')) displayName = displayName.replace('_print', ' - Imprimer'); - if (permission.name.includes('_assign')) displayName = displayName.replace('_assign', ' - Assigner'); - if (permission.name.includes('_notify')) displayName = displayName.replace('_notify', ' - Notifier'); - if (permission.name.includes('_stats')) displayName = displayName.replace('_stats', ' - Statistiques'); - - // Resource-based translations - displayName = displayName - .replace('dashboard', 'tableau de bord') - .replace('groups', 'magasins') - .replace('suppliers', 'fournisseurs') - .replace('orders', 'commandes') - .replace('deliveries', 'livraisons') - .replace('publicities', 'publicités') - .replace('customer_orders', 'commandes clients') - .replace('tasks', 'tâches') - .replace('users', 'utilisateurs') - .replace('roles', 'rôles') - .replace('permissions', 'permissions') - .replace('dlc', 'produits DLC') - .replace('calendar', 'calendrier') - .replace('reconciliation', 'rapprochement') - .replace('bl_reconciliation', 'rapprochement BL') - .replace('nocodb_config', 'config NocoDB') - .replace('system', 'système'); - - // Capitalize first letter - displayName = displayName.charAt(0).toUpperCase() + displayName.slice(1); - - try { - await storage.updatePermission(permission.id, { - ...permission, - displayName: displayName - }); - fixedDisplayNames++; - } catch (updateError) { - console.error(`Error updating permission ${permission.id}:`, updateError); - } - } - } - - fixes.push(`Fixed ${fixedDisplayNames} permission display names`); - - // 2. Ensure missing task permissions exist - console.log('🎯 Checking task management permissions...'); - const taskPermissions = [ - { category: "gestion_taches", name: "tasks_read", displayName: "Voir tâches", description: "Accès en lecture aux tâches", action: "read", resource: "tasks" }, - { category: "gestion_taches", name: "tasks_create", displayName: "Créer tâches", description: "Création de nouvelles tâches", action: "create", resource: "tasks" }, - { category: "gestion_taches", name: "tasks_update", displayName: "Modifier tâches", description: "Modification des tâches existantes", action: "update", resource: "tasks" }, - { category: "gestion_taches", name: "tasks_delete", displayName: "Supprimer tâches", description: "Suppression de tâches", action: "delete", resource: "tasks" }, - { category: "gestion_taches", name: "tasks_assign", displayName: "Assigner tâches", description: "Attribution de tâches aux utilisateurs", action: "assign", resource: "tasks" } - ]; - - let createdTaskPermissions = 0; - for (const taskPerm of taskPermissions) { - const existing = permissions.find(p => p.name === taskPerm.name); - if (!existing) { - try { - await storage.createPermission({ - ...taskPerm, - isSystem: true - }); - createdTaskPermissions++; - } catch (createError) { - console.error(`Error creating task permission ${taskPerm.name}:`, createError); - } - } - } - - if (createdTaskPermissions > 0) { - fixes.push(`Created ${createdTaskPermissions} missing task permissions`); - } - - console.log('✅ Production permissions fix completed'); - - res.json({ - success: true, - message: 'Production permission fixes applied successfully', - fixes: fixes, - timestamp: new Date().toISOString() - }); - - } catch (error) { - console.error('❌ Error fixing production permissions:', error); - res.status(500).json({ - success: false, - message: 'Failed to fix production permissions', - error: error.message - }); - } - }); const httpServer = createServer(app); return httpServer; diff --git a/server/storage.ts b/server/storage.ts index d39149f..8c70b1a 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -7,10 +7,6 @@ import { userGroups, publicities, publicityParticipations, - roles, - permissions, - rolePermissions, - userRoles, customerOrders, dlcProducts, tasks, @@ -34,16 +30,6 @@ import { type PublicityParticipation, type InsertPublicityParticipation, type PublicityWithRelations, - type Role, - type InsertRole, - type Permission, - type InsertPermission, - type RolePermission, - type InsertRolePermission, - type UserRole, - type InsertUserRole, - type RoleWithPermissions, - type UserWithRoles, nocodbConfig, type NocodbConfig, type InsertNocodbConfig, @@ -69,8 +55,7 @@ export interface IStorage { upsertUser(user: UpsertUser): Promise; getUserWithGroups(id: string): Promise; getUsers(): Promise; - getUsersWithRoles(): Promise; - getUsersWithRolesAndGroups(): Promise<(UserWithRoles & { userGroups: any[] })[]>; + createUser(user: UpsertUser): Promise; updateUser(id: string, user: Partial): Promise; deleteUser(id: string): Promise; @@ -156,39 +141,8 @@ export interface IStorage { validateDlcProduct(id: number, validatedBy: string): Promise; getDlcStats(groupIds?: number[]): Promise<{ active: number; expiringSoon: number; expired: number; }>; - // Role management operations - getRoles(): Promise; - getRole(id: number): Promise; - createRole(role: InsertRole): Promise; - updateRole(id: number, role: Partial): Promise; - deleteRole(id: number): Promise; - getRoleWithPermissions(id: number): Promise; - // Permission management operations - getPermissions(): Promise; - getPermission(id: number): Promise; - createPermission(permission: InsertPermission): Promise; - updatePermission(id: number, permission: Partial): Promise; - deletePermission(id: number): Promise; - getPermissionsByCategory(category: string): Promise; - // Role-Permission association operations - getRolePermissions(roleId: number): Promise; - setRolePermissions(roleId: number, permissionIds: number[]): Promise; - addPermissionToRole(roleId: number, permissionId: number): Promise; - removePermissionFromRole(roleId: number, permissionId: number): Promise; - - // User-Role association operations - getUserRoles(userId: string): Promise; - setUserRoles(userId: string, roleIds: number[], assignedBy: string): Promise; - addRoleToUser(userId: string, roleId: number, assignedBy: string): Promise; - removeRoleFromUser(userId: string, roleId: number): Promise; - getUserWithRoles(userId: string): Promise; - - // Permission checking operations - userHasPermission(userId: string, permissionName: string): Promise; - userHasRole(userId: string, roleName: string): Promise; - getUserEffectivePermissions(userId: string): Promise; // Task operations getTasks(groupIds?: number[]): Promise; diff --git a/shared/permissions.ts b/shared/permissions.ts new file mode 100644 index 0000000..00af99a --- /dev/null +++ b/shared/permissions.ts @@ -0,0 +1,149 @@ +// Système de permissions hardcodé par module +// Remplace l'ancien système flexible basé sur la base de données + +export type Permission = 'view' | 'create' | 'edit' | 'delete' | 'validate'; +export type Role = 'admin' | 'directeur' | 'manager' | 'employee'; +export type Module = + | 'dashboard' + | 'calendar' + | 'orders' + | 'deliveries' + | 'reconciliation' + | 'publicity' + | 'customer-orders' + | 'dlc' + | 'tasks'; + +// Définition des permissions par module et par rôle +const PERMISSIONS: Record> = { + // Tableau de bord - tous les rôles peuvent le voir + dashboard: { + admin: ['view'], + directeur: ['view'], + manager: ['view'], + employee: ['view'] + }, + + // Calendrier - Admin/Directeur tout, Manager tout sauf delete, Employé view + calendar: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view'] + }, + + // Commandes - Admin/Directeur tout, Manager tout sauf delete, Employé view + orders: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view'] + }, + + // Livraisons - Admin/Directeur tout, Manager tout sauf delete, Employé view + deliveries: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view'] + }, + + // Rapprochement - Admin tout, Directeur tout sauf delete, Manager/Employé rien + reconciliation: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit'], + manager: [], + employee: [] + }, + + // Publicité - Admin tout, autres view seulement + publicity: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view'], + manager: ['view'], + employee: ['view'] + }, + + // Commandes client - Admin/Directeur tout, Manager tout sauf delete, Employé view + create + 'customer-orders': { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view', 'create'] + }, + + // DLC - Admin/Directeur tout, Manager tout sauf delete, Employé create + view + dlc: { + admin: ['view', 'create', 'edit', 'delete'], + directeur: ['view', 'create', 'edit', 'delete'], + manager: ['view', 'create', 'edit'], + employee: ['view', 'create'] + }, + + // Tâches - Admin/Directeur tout, Manager view + validate, Employé view + tasks: { + admin: ['view', 'create', 'edit', 'delete', 'validate'], + directeur: ['view', 'create', 'edit', 'delete', 'validate'], + manager: ['view', 'validate'], + employee: ['view'] + } +}; + +// Hook pour vérifier les permissions +export function usePermissions(userRole?: string) { + const role = userRole?.toLowerCase() as Role; + + // Vérifie si l'utilisateur peut accéder à un module + const canAccessModule = (module: Module): boolean => { + if (!role || !PERMISSIONS[module]) return false; + const permissions = PERMISSIONS[module][role] || []; + return permissions.length > 0; + }; + + // Vérifie si l'utilisateur peut effectuer une action sur un module + const canPerformAction = (module: Module, action: Permission): boolean => { + if (!role || !PERMISSIONS[module]) return false; + const permissions = PERMISSIONS[module][role] || []; + return permissions.includes(action); + }; + + // Retourne toutes les permissions pour un module + const getModulePermissions = (module: Module): Permission[] => { + if (!role || !PERMISSIONS[module]) return []; + return PERMISSIONS[module][role] || []; + }; + + // Vérifie si l'utilisateur peut voir le module (a au moins une permission) + const canView = (module: Module): boolean => canPerformAction(module, 'view'); + const canCreate = (module: Module): boolean => canPerformAction(module, 'create'); + const canEdit = (module: Module): boolean => canPerformAction(module, 'edit'); + const canDelete = (module: Module): boolean => canPerformAction(module, 'delete'); + const canValidate = (module: Module): boolean => canPerformAction(module, 'validate'); + + return { + canAccessModule, + canPerformAction, + getModulePermissions, + canView, + canCreate, + canEdit, + canDelete, + canValidate + }; +} + +// Fonction utilitaire pour les middlewares serveur +export function hasPermission(userRole: string, module: Module, action: Permission): boolean { + const role = userRole?.toLowerCase() as Role; + if (!role || !PERMISSIONS[module]) return false; + const permissions = PERMISSIONS[module][role] || []; + return permissions.includes(action); +} + +// Fonction utilitaire pour vérifier l'accès à un module +export function hasModuleAccess(userRole: string, module: Module): boolean { + const role = userRole?.toLowerCase() as Role; + if (!role || !PERMISSIONS[module]) return false; + const permissions = PERMISSIONS[module][role] || []; + return permissions.length > 0; +} \ No newline at end of file diff --git a/shared/schema.ts b/shared/schema.ts index cb09e89..9953127 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -136,40 +136,7 @@ export const publicityParticipations = pgTable("publicity_participations", { pk: primaryKey({ columns: [table.publicityId, table.groupId] }) })); -// Roles - Dynamic role management -export const roles = pgTable("roles", { - id: serial("id").primaryKey(), - name: varchar("name").notNull().unique(), - displayName: varchar("display_name").notNull(), - description: text("description"), - color: varchar("color").default("#6b7280"), // Couleur d'affichage - isSystem: boolean("is_system").default(false), // Rôles système non supprimables - isActive: boolean("is_active").default(true), - createdAt: timestamp("created_at").defaultNow(), - updatedAt: timestamp("updated_at").defaultNow(), -}); -// Permissions - Available permissions in the system -export const permissions = pgTable("permissions", { - id: serial("id").primaryKey(), - name: varchar("name").notNull().unique(), - displayName: varchar("display_name").notNull(), - description: text("description"), - category: varchar("category").notNull(), // dashboard, orders, deliveries, users, etc. - action: varchar("action").notNull(), // read, create, update, delete, validate - resource: varchar("resource").notNull(), // orders, deliveries, users, etc. - isSystem: boolean("is_system").default(true), // Permissions système - createdAt: timestamp("created_at").defaultNow(), -}); - -// Role Permissions - Many to many relationship -export const rolePermissions = pgTable("role_permissions", { - roleId: integer("role_id").notNull(), - permissionId: integer("permission_id").notNull(), - createdAt: timestamp("created_at").defaultNow(), -}, (table) => ({ - pk: primaryKey({ columns: [table.roleId, table.permissionId] }) -})); // NocoDB configuration globale (une seule instance NocoDB partagée) export const nocodbConfig = pgTable("nocodb_config", { @@ -221,15 +188,7 @@ export const customerOrders = pgTable("customer_orders", { updatedAt: timestamp("updated_at").defaultNow(), }); -// User Roles - Many to many relationship (supports multiple roles per user) -export const userRoles = pgTable("user_roles", { - userId: varchar("user_id").notNull(), - roleId: integer("role_id").notNull(), - assignedBy: varchar("assigned_by").notNull(), // Who assigned this role - assignedAt: timestamp("assigned_at").defaultNow(), -}, (table) => ({ - pk: primaryKey({ columns: [table.userId, table.roleId] }) -})); + // DLC Products (Date Limite de Consommation) export const dlcProducts = pgTable("dlc_products", { @@ -273,7 +232,6 @@ export const tasks = pgTable("tasks", { // Relations export const usersRelations = relations(users, ({ many }) => ({ userGroups: many(userGroups), - userRoles: many(userRoles), createdOrders: many(orders), createdDeliveries: many(deliveries), createdPublicities: many(publicities), @@ -368,36 +326,7 @@ export const publicityParticipationsRelations = relations(publicityParticipation }), })); -export const rolesRelations = relations(roles, ({ many }) => ({ - userRoles: many(userRoles), - rolePermissions: many(rolePermissions), -})); -export const userRolesRelations = relations(userRoles, ({ one }) => ({ - user: one(users, { - fields: [userRoles.userId], - references: [users.id], - }), - role: one(roles, { - fields: [userRoles.roleId], - references: [roles.id], - }), -})); - -export const permissionsRelations = relations(permissions, ({ many }) => ({ - rolePermissions: many(rolePermissions), -})); - -export const rolePermissionsRelations = relations(rolePermissions, ({ one }) => ({ - role: one(roles, { - fields: [rolePermissions.roleId], - references: [roles.id], - }), - permission: one(permissions, { - fields: [rolePermissions.permissionId], - references: [permissions.id], - }), -})); export const customerOrdersRelations = relations(customerOrders, ({ one }) => ({ group: one(groups, { @@ -502,24 +431,7 @@ export const insertPublicityParticipationSchema = createInsertSchema(publicityPa createdAt: true, }); -export const insertRoleSchema = createInsertSchema(roles).omit({ - id: true, - createdAt: true, - updatedAt: true, -}); -export const insertPermissionSchema = createInsertSchema(permissions).omit({ - id: true, - createdAt: true, -}); - -export const insertRolePermissionSchema = createInsertSchema(rolePermissions).omit({ - createdAt: true, -}); - -export const insertUserRoleSchema = createInsertSchema(userRoles).omit({ - assignedAt: true, -}); export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({ id: true, @@ -606,31 +518,6 @@ export type PublicityWithRelations = Publicity & { participations: (PublicityParticipation & { group: Group })[]; }; -export type Role = typeof roles.$inferSelect; -export type InsertRole = z.infer; - -export type Permission = typeof permissions.$inferSelect; -export type InsertPermission = z.infer; - -export type RolePermission = typeof rolePermissions.$inferSelect; -export type InsertRolePermission = z.infer; - -export type UserRole = typeof userRoles.$inferSelect; -export type InsertUserRole = z.infer; - -export type RoleWithPermissions = Role & { - rolePermissions: (RolePermission & { permission: Permission })[]; -}; - -export type UserWithRoles = User & { - userRoles: (UserRole & { role: Role })[]; -}; - -export type PermissionWithActions = Permission & { - action: string; - resource: string; -}; - export type NocodbConfig = typeof nocodbConfig.$inferSelect; export type InsertNocodbConfig = z.infer; @@ -643,11 +530,6 @@ export type CustomerOrderWithRelations = CustomerOrder & { supplier: Supplier; }; -export type UserWithRole = User & { - dynamicRole?: Role | null; - userGroups: (UserGroup & { group: Group })[]; -}; - export type DlcProduct = typeof dlcProducts.$inferSelect; export type InsertDlcProduct = z.infer;