diff --git a/srcs/auth/src/controllers/admin.controller.ts b/srcs/auth/src/controllers/admin.controller.ts index 8570be56..358ddb16 100644 --- a/srcs/auth/src/controllers/admin.controller.ts +++ b/srcs/auth/src/controllers/admin.controller.ts @@ -77,7 +77,8 @@ export async function updateUserHandler( }); } - if (role !== UserRole.USER && role !== UserRole.ADMIN) { + const validRoles = Object.values(UserRole); + if (!validRoles.includes(role)) { return reply.code(HTTP_STATUS.BAD_REQUEST).send({ error: { message: ERROR_MESSAGES.INVALID_ROLE, @@ -170,15 +171,15 @@ export async function deleteUserHandler( req: FastifyRequest, reply: FastifyReply, ) { - // Récupérer les informations admin déjà validées par verifyAdminRole - const adminUserId = (req as any).adminUserId; - const adminUsername = (req as any).adminUsername; + // Récupérer les informations verifyAdminRole + const authUser = (req as any).authUser; const targetUserId = Number((req.params as any).id); logger.info({ event: 'admin_delete_user_attempt', - admin: adminUsername, - adminUserId, + actor: authUser.username, + actorId: authUser.id, + actorRole: authUser.role, targetUserId, }); @@ -192,7 +193,7 @@ export async function deleteUserHandler( } // Empêcher l'auto-suppression - if (targetUserId === adminUserId) { + if (targetUserId === authUser.id) { return reply.code(HTTP_STATUS.BAD_REQUEST).send({ error: { message: ERROR_MESSAGES.SELF_DELETION_FORBIDDEN, @@ -218,7 +219,8 @@ export async function deleteUserHandler( logger.info({ event: 'admin_delete_user_success', - admin: adminUsername, + actor: authUser.username, + actorRole: authUser.role, targetUserId, targetUsername, }); @@ -229,7 +231,7 @@ export async function deleteUserHandler( } catch (err: any) { logger.error({ event: 'admin_delete_user_error', - admin: adminUsername, + actor: authUser.username, targetUserId, err: err?.message || err, }); @@ -244,22 +246,22 @@ export async function deleteUserHandler( } /** - * ADMIN ONLY - Désactiver la 2FA d'un utilisateur + * MODERATOR or ADMIN - Désactiver la 2FA d'un utilisateur */ export async function adminDisable2FAHandler( this: FastifyInstance, req: FastifyRequest, reply: FastifyReply, ) { - // Récupérer les informations admin déjà validées par verifyAdminRole - const adminUserId = (req as any).adminUserId; - const adminUsername = (req as any).adminUsername; + // Récupérer les informations verifyModeratorRole + const authUser = (req as any).authUser; const targetUserId = Number((req.params as any).id); logger.info({ event: 'admin_disable_2fa_attempt', - admin: adminUsername, - adminUserId, + actor: authUser.username, + actorId: authUser.id, + actorRole: authUser.role, targetUserId, }); @@ -299,7 +301,8 @@ export async function adminDisable2FAHandler( logger.info({ event: 'admin_disable_2fa_success', - admin: adminUsername, + actor: authUser.username, + actorRole: authUser.role, targetUserId, targetUsername: targetUser.username, }); @@ -310,7 +313,7 @@ export async function adminDisable2FAHandler( } catch (err: any) { logger.error({ event: 'admin_disable_2fa_error', - admin: adminUsername, + actor: authUser.username, targetUserId, err: err?.message || err, }); diff --git a/srcs/auth/src/index.ts b/srcs/auth/src/index.ts index 9ec723f6..d0045546 100644 --- a/srcs/auth/src/index.ts +++ b/srcs/auth/src/index.ts @@ -3,7 +3,7 @@ import fastifyCookie from '@fastify/cookie'; import fastifyJwt from '@fastify/jwt'; import fastifyRateLimit from '@fastify/rate-limit'; import { authRoutes } from './routes/auth.routes.js'; -import { adminRoutes } from './routes/admin.routes.js'; +import { adminRoutes, moderatorRoutes } from './routes/admin.routes.js'; import { initAdminUser, initInviteUser } from './utils/init-users.js'; import * as totpService from './services/totp.service.js'; import * as onlineService from './services/online.service.js'; @@ -141,6 +141,7 @@ app.register(fastifyRateLimit, { app.register(authRoutes, { prefix: '/' }); app.register(adminRoutes, { prefix: '/admin' }); +app.register(moderatorRoutes, { prefix: '/admin' }); (async () => { try { diff --git a/srcs/auth/src/routes/admin.routes.ts b/srcs/auth/src/routes/admin.routes.ts index 227c2fd1..446370ad 100644 --- a/srcs/auth/src/routes/admin.routes.ts +++ b/srcs/auth/src/routes/admin.routes.ts @@ -10,7 +10,7 @@ import { UserRole, HTTP_STATUS, ERROR_MESSAGES, ERROR_RESPONSE_CODES } from '../ /** * Pré-handler pour vérifier si l'utilisateur est administrateur - * Ajoute adminUserId et adminUsername a la requête + * Ajoute authUser (id, username, role) à la requête */ export async function verifyAdminRole(req: FastifyRequest, reply: FastifyReply) { const idHeader = (req.headers as any)['x-user-id']; @@ -31,8 +31,42 @@ export async function verifyAdminRole(req: FastifyRequest, reply: FastifyReply) }); } - (req as any).adminUserId = userId; - (req as any).adminUsername = username; + (req as any).authUser = { + id: userId, + username, + role: authService.getUserRole(userId), + }; +} + +/** + * Pré-handler pour vérifier si l'utilisateur est modérateur ou administrateur + * Permet aux modérateurs de désactiver la 2FA et lister les utilisateurs + * Ajoute authUser (id, username, role) à la requête + */ +export async function verifyModeratorRole(req: FastifyRequest, reply: FastifyReply) { + const idHeader = (req.headers as any)['x-user-id']; + const userId = idHeader ? Number(idHeader) : null; + const username = (req.headers as any)['x-user-name'] || null; + + if (!userId || !authService.hasRole(userId, UserRole.MODERATOR)) { + req.log.warn({ + event: 'moderator_access_forbidden', + user: username, + userId, + }); + return reply.code(HTTP_STATUS.FORBIDDEN).send({ + error: { + message: ERROR_MESSAGES.FORBIDDEN, + code: ERROR_RESPONSE_CODES.FORBIDDEN, + }, + }); + } + + (req as any).authUser = { + id: userId, + username, + role: authService.getUserRole(userId), + }; } /** @@ -43,20 +77,6 @@ export async function adminRoutes(app: FastifyInstance) { // Ajout du pré-handler pour toutes les routes admin app.addHook('onRequest', verifyAdminRole); - // Liste tous les utilisateurs - app.get( - '/users', - { - config: { - rateLimit: { - max: 50, - timeWindow: '1 minute', - }, - }, - }, - listAllUsers, - ); - // Mettre à jour un utilisateur app.put( '/users/:id', @@ -84,6 +104,29 @@ export async function adminRoutes(app: FastifyInstance) { }, deleteUserHandler, ); +} + +/** + * Routes de modération + * Ces routes nécessitent un rôle modérateur ou supérieur + */ +export async function moderatorRoutes(app: FastifyInstance) { + // Ajout du pré-handler pour toutes les routes modérateur + app.addHook('onRequest', verifyModeratorRole); + + // Liste tous les utilisateurs + app.get( + '/users', + { + config: { + rateLimit: { + max: 50, + timeWindow: '1 minute', + }, + }, + }, + listAllUsers, + ); // Désactiver la 2FA d'un utilisateur app.post( diff --git a/srcs/auth/src/services/auth.service.ts b/srcs/auth/src/services/auth.service.ts index 7e1162bb..07b1b6b5 100644 --- a/srcs/auth/src/services/auth.service.ts +++ b/srcs/auth/src/services/auth.service.ts @@ -212,10 +212,11 @@ export function hasRole(userId: number, requiredRole: UserRole): boolean { try { const userRole = getUserRole(userId); - // Hiérarchie des rôles : user < admin + // Hiérarchie des rôles : user < moderator < admin const roleHierarchy = { [UserRole.USER]: 0, - [UserRole.ADMIN]: 1, + [UserRole.MODERATOR]: 1, + [UserRole.ADMIN]: 2, }; const userRoleLevel = roleHierarchy[userRole as UserRole] ?? 0; diff --git a/srcs/auth/src/utils/constants.ts b/srcs/auth/src/utils/constants.ts index 48aeeec5..d627d227 100644 --- a/srcs/auth/src/utils/constants.ts +++ b/srcs/auth/src/utils/constants.ts @@ -98,9 +98,13 @@ export const AUTH_CONFIG = { /** * Rôles utilisateur pour RBAC (Role-Based Access Control) + * USER: utilisateur standard + * MODERATOR: peut consulter la liste des utilisateurs et désactiver la 2FA + * ADMIN: contrôle total (view, update, delete users, disable 2FA) */ export enum UserRole { USER = 'user', + MODERATOR = 'moderator', ADMIN = 'admin', } @@ -135,7 +139,7 @@ export const ERROR_MESSAGES = { INVALID_USER_ID: 'Invalid user ID', // Role errors - INVALID_ROLE: 'Role must be either "user" or "admin"', + INVALID_ROLE: 'Role must be either "user" or "admin" or "moderator"', // Field errors MISSING_FIELDS: 'Username, email, and role are required', diff --git a/srcs/nginx/src/html/admin.html b/srcs/nginx/src/html/admin.html index 7db5377f..d34bb902 100644 --- a/srcs/nginx/src/html/admin.html +++ b/srcs/nginx/src/html/admin.html @@ -49,6 +49,11 @@ color: #92400e; } + .badge.moderator { + background: linear-gradient(135deg, #ddd6fe, #c4b5fd); + color: #5b21b6; + } + .badge.user { background: linear-gradient(135deg, #e5e7eb, #d1d5db); color: #374151; @@ -403,7 +408,7 @@
Modérateurs
+-
+