From df479800e3974ad4e4b05caac64fa37444019462 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 11 Aug 2025 19:11:11 +0000 Subject: [PATCH] Implement a new hardcoded permissions system for enhanced user access control This commit replaces the previous dynamic, database-driven permissions system with a hardcoded, role-based access control (RBAC) structure. New files in `client/src/lib/permissions.ts` and `server/permissions.ts` define permissions per module and role, simplifying management and improving performance. Database tables related to roles, permissions, and user roles have been removed, and associated storage interface methods and routes have been deprecated or updated to reflect the new architecture. The `replit.md` file has also been updated to document these significant changes. Replit-Commit-Author: Agent Replit-Commit-Session-Id: a8a78c07-e900-425c-a577-5b4c5894379d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/a8a78c07-e900-425c-a577-5b4c5894379d/7SnSmh4 --- client/src/lib/permissions.ts | 149 +++++++++ client/src/pages/Users.tsx | 1 + replit.md | 19 ++ server/initRolesAndPermissions.ts | 179 ---------- server/permissions.ts | 117 +++++++ server/routes.ts | 532 +----------------------------- server/storage.ts | 48 +-- shared/permissions.ts | 149 +++++++++ shared/schema.ts | 120 +------ 9 files changed, 441 insertions(+), 873 deletions(-) create mode 100644 client/src/lib/permissions.ts delete mode 100644 server/initRolesAndPermissions.ts create mode 100644 server/permissions.ts create mode 100644 shared/permissions.ts 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;