Agregango archivos necesarios para auth
This commit is contained in:
parent
90c2ad934c
commit
35fe8db1f9
|
|
@ -0,0 +1,501 @@
|
||||||
|
/**
|
||||||
|
* ========================================
|
||||||
|
* CONFIGURACIÓN DE PERMISOS Y ROLES
|
||||||
|
* ========================================
|
||||||
|
*
|
||||||
|
* Este archivo define:
|
||||||
|
* 1. Roles disponibles en el sistema
|
||||||
|
* 2. Permisos por rol
|
||||||
|
* 3. Rutas y recursos accesibles por rol
|
||||||
|
* 4. Metadata para construir menús en el frontend
|
||||||
|
*
|
||||||
|
* IMPORTANTE: Este archivo puede migrar a BD fácilmente
|
||||||
|
* manteniendo la misma estructura.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// DEFINICIÓN DE ROLES
|
||||||
|
// ========================================
|
||||||
|
const ROLES = {
|
||||||
|
ADMIN: 'admin',
|
||||||
|
USER: 'usuario',
|
||||||
|
GUEST: 'invitado'
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// DEFINICIÓN DE RECURSOS
|
||||||
|
// ========================================
|
||||||
|
// Cada recurso representa un módulo del sistema
|
||||||
|
const RESOURCES = {
|
||||||
|
// Gestión de usuarios
|
||||||
|
USERS: 'users',
|
||||||
|
PROFILE: 'profile',
|
||||||
|
|
||||||
|
// Catálogos
|
||||||
|
CATEGORIES: 'categories',
|
||||||
|
ACTIVITIES: 'activities',
|
||||||
|
BADGES: 'badges',
|
||||||
|
LEVELS: 'levels',
|
||||||
|
|
||||||
|
// Usuario
|
||||||
|
GOALS: 'goals',
|
||||||
|
AGENDA: 'agenda',
|
||||||
|
ACTIVITY_LOG: 'activity_log',
|
||||||
|
NOTIFICATIONS: 'notifications',
|
||||||
|
|
||||||
|
// Métricas
|
||||||
|
USER_METRICS: 'user_metrics',
|
||||||
|
ADMIN_METRICS: 'admin_metrics',
|
||||||
|
|
||||||
|
// Relaciones
|
||||||
|
USER_BADGES: 'user_badges',
|
||||||
|
USER_ROLES: 'user_roles'
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// DEFINICIÓN DE ACCIONES
|
||||||
|
// ========================================
|
||||||
|
const ACTIONS = {
|
||||||
|
CREATE: 'create',
|
||||||
|
READ: 'read',
|
||||||
|
UPDATE: 'update',
|
||||||
|
DELETE: 'delete',
|
||||||
|
LIST: 'list',
|
||||||
|
MANAGE: 'manage' // Todas las acciones
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// PERMISOS POR ROL
|
||||||
|
// ========================================
|
||||||
|
// Define qué puede hacer cada rol en cada recurso
|
||||||
|
const PERMISSIONS = {
|
||||||
|
[ROLES.ADMIN]: {
|
||||||
|
// El admin tiene acceso total a TODO
|
||||||
|
[RESOURCES.USERS]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.PROFILE]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.CATEGORIES]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.ACTIVITIES]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.BADGES]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.LEVELS]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.GOALS]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.AGENDA]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.ACTIVITY_LOG]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.NOTIFICATIONS]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.USER_METRICS]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.ADMIN_METRICS]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.USER_BADGES]: [ACTIONS.MANAGE],
|
||||||
|
[RESOURCES.USER_ROLES]: [ACTIONS.MANAGE]
|
||||||
|
},
|
||||||
|
|
||||||
|
[ROLES.USER]: {
|
||||||
|
// El usuario normal tiene acceso limitado
|
||||||
|
[RESOURCES.PROFILE]: [ACTIONS.READ, ACTIONS.UPDATE],
|
||||||
|
|
||||||
|
// Puede VER catálogos pero no modificarlos
|
||||||
|
[RESOURCES.CATEGORIES]: [ACTIONS.READ, ACTIONS.LIST],
|
||||||
|
[RESOURCES.ACTIVITIES]: [ACTIONS.READ, ACTIONS.LIST],
|
||||||
|
[RESOURCES.BADGES]: [ACTIONS.READ, ACTIONS.LIST],
|
||||||
|
[RESOURCES.LEVELS]: [ACTIONS.READ, ACTIONS.LIST],
|
||||||
|
|
||||||
|
// Puede gestionar SUS PROPIOS recursos
|
||||||
|
[RESOURCES.GOALS]: [ACTIONS.CREATE, ACTIONS.READ, ACTIONS.UPDATE, ACTIONS.DELETE, ACTIONS.LIST],
|
||||||
|
[RESOURCES.AGENDA]: [ACTIONS.CREATE, ACTIONS.READ, ACTIONS.UPDATE, ACTIONS.DELETE, ACTIONS.LIST],
|
||||||
|
[RESOURCES.ACTIVITY_LOG]: [ACTIONS.CREATE, ACTIONS.READ, ACTIONS.UPDATE, ACTIONS.DELETE, ACTIONS.LIST],
|
||||||
|
[RESOURCES.NOTIFICATIONS]: [ACTIONS.READ, ACTIONS.LIST, ACTIONS.UPDATE],
|
||||||
|
|
||||||
|
// Puede ver SUS PROPIAS métricas
|
||||||
|
[RESOURCES.USER_METRICS]: [ACTIONS.READ, ACTIONS.LIST],
|
||||||
|
[RESOURCES.USER_BADGES]: [ACTIONS.READ, ACTIONS.LIST],
|
||||||
|
|
||||||
|
// NO puede ver métricas de admin
|
||||||
|
[RESOURCES.ADMIN_METRICS]: [],
|
||||||
|
[RESOURCES.USER_ROLES]: []
|
||||||
|
},
|
||||||
|
|
||||||
|
[ROLES.GUEST]: {
|
||||||
|
// Invitado solo puede ver catálogos públicos
|
||||||
|
[RESOURCES.CATEGORIES]: [ACTIONS.READ, ACTIONS.LIST],
|
||||||
|
[RESOURCES.ACTIVITIES]: [ACTIONS.READ, ACTIONS.LIST],
|
||||||
|
[RESOURCES.BADGES]: [ACTIONS.READ, ACTIONS.LIST]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// CONFIGURACIÓN DE RUTAS
|
||||||
|
// ========================================
|
||||||
|
// Define las rutas del sistema con metadata para menús
|
||||||
|
const ROUTE_CONFIG = [
|
||||||
|
// ========== PERFIL ==========
|
||||||
|
{
|
||||||
|
resource: RESOURCES.PROFILE,
|
||||||
|
path: '/api/auth/me',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.READ,
|
||||||
|
metadata: {
|
||||||
|
label: 'Mi Perfil',
|
||||||
|
icon: 'user',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Cuenta',
|
||||||
|
order: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: RESOURCES.PROFILE,
|
||||||
|
path: '/api/auth/me/profile',
|
||||||
|
method: 'PUT',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.UPDATE,
|
||||||
|
metadata: {
|
||||||
|
showInMenu: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ========== CATÁLOGOS ==========
|
||||||
|
{
|
||||||
|
resource: RESOURCES.CATEGORIES,
|
||||||
|
path: '/api/categorias',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: false,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Categorías',
|
||||||
|
icon: 'folder',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Catálogos',
|
||||||
|
order: 10,
|
||||||
|
roles: [ROLES.ADMIN, ROLES.USER]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: RESOURCES.CATEGORIES,
|
||||||
|
path: '/api/categorias',
|
||||||
|
method: 'POST',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.CREATE,
|
||||||
|
metadata: {
|
||||||
|
showInMenu: false,
|
||||||
|
roles: [ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
resource: RESOURCES.ACTIVITIES,
|
||||||
|
path: '/api/actividades',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: false,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Actividades',
|
||||||
|
icon: 'activity',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Catálogos',
|
||||||
|
order: 11,
|
||||||
|
roles: [ROLES.ADMIN, ROLES.USER]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: RESOURCES.ACTIVITIES,
|
||||||
|
path: '/api/actividades',
|
||||||
|
method: 'POST',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.CREATE,
|
||||||
|
metadata: {
|
||||||
|
showInMenu: false,
|
||||||
|
roles: [ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
resource: RESOURCES.BADGES,
|
||||||
|
path: '/api/insignias',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: false,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Insignias',
|
||||||
|
icon: 'award',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Catálogos',
|
||||||
|
order: 12,
|
||||||
|
roles: [ROLES.ADMIN, ROLES.USER]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
resource: RESOURCES.LEVELS,
|
||||||
|
path: '/api/niveles',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: false,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Niveles',
|
||||||
|
icon: 'trending-up',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Catálogos',
|
||||||
|
order: 13,
|
||||||
|
roles: [ROLES.ADMIN, ROLES.USER]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ========== MIS RECURSOS ==========
|
||||||
|
{
|
||||||
|
resource: RESOURCES.GOALS,
|
||||||
|
path: '/api/metas',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Mis Metas',
|
||||||
|
icon: 'target',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Mi Progreso',
|
||||||
|
order: 20,
|
||||||
|
roles: [ROLES.USER, ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: RESOURCES.GOALS,
|
||||||
|
path: '/api/metas',
|
||||||
|
method: 'POST',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.CREATE,
|
||||||
|
metadata: {
|
||||||
|
showInMenu: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
resource: RESOURCES.AGENDA,
|
||||||
|
path: '/api/agenda',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Mi Agenda',
|
||||||
|
icon: 'calendar',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Mi Progreso',
|
||||||
|
order: 21,
|
||||||
|
roles: [ROLES.USER, ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
resource: RESOURCES.ACTIVITY_LOG,
|
||||||
|
path: '/api/registroactividad',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Mis Actividades',
|
||||||
|
icon: 'list',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Mi Progreso',
|
||||||
|
order: 22,
|
||||||
|
roles: [ROLES.USER, ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
resource: RESOURCES.NOTIFICATIONS,
|
||||||
|
path: '/api/notificaciones',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Notificaciones',
|
||||||
|
icon: 'bell',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Mi Progreso',
|
||||||
|
order: 23,
|
||||||
|
roles: [ROLES.USER, ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ========== MÉTRICAS ==========
|
||||||
|
{
|
||||||
|
resource: RESOURCES.USER_METRICS,
|
||||||
|
path: '/api/metricas',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Mis Estadísticas',
|
||||||
|
icon: 'bar-chart',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Estadísticas',
|
||||||
|
order: 30,
|
||||||
|
roles: [ROLES.USER, ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
resource: RESOURCES.USER_BADGES,
|
||||||
|
path: '/api/usuario-insignias',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Mis Insignias',
|
||||||
|
icon: 'award',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Estadísticas',
|
||||||
|
order: 31,
|
||||||
|
roles: [ROLES.USER, ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ========== ADMIN ==========
|
||||||
|
{
|
||||||
|
resource: RESOURCES.ADMIN_METRICS,
|
||||||
|
path: '/api/admin/metrics',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.READ,
|
||||||
|
metadata: {
|
||||||
|
label: 'Métricas del Sistema',
|
||||||
|
icon: 'pie-chart',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Administración',
|
||||||
|
order: 100,
|
||||||
|
roles: [ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
resource: RESOURCES.USER_ROLES,
|
||||||
|
path: '/api/usuario-roles',
|
||||||
|
method: 'GET',
|
||||||
|
requiresAuth: true,
|
||||||
|
action: ACTIONS.LIST,
|
||||||
|
metadata: {
|
||||||
|
label: 'Gestión de Roles',
|
||||||
|
icon: 'shield',
|
||||||
|
showInMenu: true,
|
||||||
|
group: 'Administración',
|
||||||
|
order: 101,
|
||||||
|
roles: [ROLES.ADMIN]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// FUNCIONES DE AYUDA
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si un rol tiene permiso para una acción en un recurso
|
||||||
|
* @param {string} role - El rol a verificar
|
||||||
|
* @param {string} resource - El recurso
|
||||||
|
* @param {string} action - La acción
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function hasPermission(role, resource, action) {
|
||||||
|
const rolePermissions = PERMISSIONS[role];
|
||||||
|
if (!rolePermissions) return false;
|
||||||
|
|
||||||
|
const resourcePermissions = rolePermissions[resource];
|
||||||
|
if (!resourcePermissions) return false;
|
||||||
|
|
||||||
|
// Si tiene permiso MANAGE, tiene todos los permisos
|
||||||
|
if (resourcePermissions.includes(ACTIONS.MANAGE)) return true;
|
||||||
|
|
||||||
|
// Sino, verificar la acción específica
|
||||||
|
return resourcePermissions.includes(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene todas las rutas accesibles para un rol
|
||||||
|
* @param {string} role - El rol
|
||||||
|
* @param {boolean} onlyMenuItems - Si solo devolver rutas que se muestran en menú
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
function getRoutesForRole(role, onlyMenuItems = false) {
|
||||||
|
return ROUTE_CONFIG.filter(route => {
|
||||||
|
// Verificar si tiene permiso para esta ruta
|
||||||
|
const hasAccess = hasPermission(role, route.resource, route.action);
|
||||||
|
if (!hasAccess) return false;
|
||||||
|
|
||||||
|
// Si solo queremos items de menú
|
||||||
|
if (onlyMenuItems && !route.metadata.showInMenu) return false;
|
||||||
|
|
||||||
|
// Verificar si la ruta especifica roles permitidos
|
||||||
|
if (route.metadata.roles && !route.metadata.roles.includes(role)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye el menú para un rol específico
|
||||||
|
* @param {string} role - El rol
|
||||||
|
* @returns {Object} - Menú agrupado
|
||||||
|
*/
|
||||||
|
function buildMenuForRole(role) {
|
||||||
|
const routes = getRoutesForRole(role, true);
|
||||||
|
|
||||||
|
// Agrupar por grupo
|
||||||
|
const grouped = {};
|
||||||
|
routes.forEach(route => {
|
||||||
|
const group = route.metadata.group || 'Otros';
|
||||||
|
if (!grouped[group]) {
|
||||||
|
grouped[group] = [];
|
||||||
|
}
|
||||||
|
grouped[group].push({
|
||||||
|
label: route.metadata.label,
|
||||||
|
icon: route.metadata.icon,
|
||||||
|
path: route.path,
|
||||||
|
method: route.method,
|
||||||
|
order: route.metadata.order || 999
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ordenar items dentro de cada grupo
|
||||||
|
Object.keys(grouped).forEach(group => {
|
||||||
|
grouped[group].sort((a, b) => a.order - b.order);
|
||||||
|
});
|
||||||
|
|
||||||
|
return grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene todos los permisos de un rol
|
||||||
|
* @param {string} role - El rol
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function getPermissionsForRole(role) {
|
||||||
|
return PERMISSIONS[role] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si un usuario (con sus roles) puede acceder a una ruta
|
||||||
|
* @param {Array} userRoles - Array de roles del usuario
|
||||||
|
* @param {string} resource - Recurso
|
||||||
|
* @param {string} action - Acción
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function canAccess(userRoles, resource, action) {
|
||||||
|
if (!Array.isArray(userRoles) || userRoles.length === 0) return false;
|
||||||
|
|
||||||
|
// Si tiene al menos un rol con permiso, puede acceder
|
||||||
|
return userRoles.some(role => hasPermission(role, resource, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// EXPORTACIÓN
|
||||||
|
// ========================================
|
||||||
|
module.exports = {
|
||||||
|
ROLES,
|
||||||
|
RESOURCES,
|
||||||
|
ACTIONS,
|
||||||
|
PERMISSIONS,
|
||||||
|
ROUTE_CONFIG,
|
||||||
|
hasPermission,
|
||||||
|
getRoutesForRole,
|
||||||
|
buildMenuForRole,
|
||||||
|
getPermissionsForRole,
|
||||||
|
canAccess
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
/**
|
||||||
|
* ========================================
|
||||||
|
* MIDDLEWARE DE VERIFICACIÓN DE PERMISOS
|
||||||
|
* ========================================
|
||||||
|
*
|
||||||
|
* Este middleware verifica si el usuario autenticado tiene
|
||||||
|
* permisos para acceder a un recurso con una acción específica.
|
||||||
|
*
|
||||||
|
* FLUJO:
|
||||||
|
* 1. requireAuth → Verifica token y adjunta req.user
|
||||||
|
* 2. checkPermission → Verifica permisos del usuario
|
||||||
|
* 3. Ruta → Se ejecuta solo si tiene permiso
|
||||||
|
*
|
||||||
|
* USO:
|
||||||
|
* router.get('/metas',
|
||||||
|
* requireAuth,
|
||||||
|
* checkPermission(RESOURCES.GOALS, ACTIONS.LIST),
|
||||||
|
* async (req, res) => { ... }
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { canAccess } = require('../config/permissions');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ========================================
|
||||||
|
* checkPermission
|
||||||
|
* ========================================
|
||||||
|
* Factory function que retorna un middleware de verificación de permisos
|
||||||
|
*
|
||||||
|
* @param {string} resource - El recurso a verificar (ej: 'goals')
|
||||||
|
* @param {string} action - La acción a verificar (ej: 'read', 'create')
|
||||||
|
* @returns {Function} Middleware de Express
|
||||||
|
*
|
||||||
|
* EJEMPLO:
|
||||||
|
* const { RESOURCES, ACTIONS } = require('../config/permissions');
|
||||||
|
* const checkPermission = require('../middleware/checkPermission');
|
||||||
|
*
|
||||||
|
* router.post('/metas',
|
||||||
|
* requireAuth,
|
||||||
|
* checkPermission(RESOURCES.GOALS, ACTIONS.CREATE),
|
||||||
|
* async (req, res) => {
|
||||||
|
* // Solo llega aquí si tiene permiso
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
function checkPermission(resource, action) {
|
||||||
|
return (req, res, next) => {
|
||||||
|
// 1. Verificar que el usuario esté autenticado
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(401).json({
|
||||||
|
ok: false,
|
||||||
|
error: 'No autenticado. Este middleware requiere requireAuth antes.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Obtener roles del usuario
|
||||||
|
// Los roles deben venir en req.user.roles (array)
|
||||||
|
const userRoles = req.user.roles || [];
|
||||||
|
|
||||||
|
// 3. Log para debugging
|
||||||
|
console.log(`[PERMISOS] Usuario ${req.user.id_usuario} intenta ${action} en ${resource}`);
|
||||||
|
console.log(`[PERMISOS] Roles del usuario:`, userRoles);
|
||||||
|
|
||||||
|
// 4. Verificar si tiene permiso
|
||||||
|
const hasAccess = canAccess(userRoles, resource, action);
|
||||||
|
|
||||||
|
if (!hasAccess) {
|
||||||
|
console.log(`[PERMISOS] ❌ ACCESO DENEGADO`);
|
||||||
|
return res.status(403).json({
|
||||||
|
ok: false,
|
||||||
|
error: 'No tienes permisos para realizar esta acción',
|
||||||
|
details: {
|
||||||
|
resource,
|
||||||
|
action,
|
||||||
|
yourRoles: userRoles
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[PERMISOS] ✅ ACCESO PERMITIDO`);
|
||||||
|
|
||||||
|
// 5. Tiene permiso, continuar
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ========================================
|
||||||
|
* requireRole
|
||||||
|
* ========================================
|
||||||
|
* Middleware simplificado que solo verifica si el usuario tiene un rol específico
|
||||||
|
*
|
||||||
|
* @param {string|Array} allowedRoles - Rol o roles permitidos
|
||||||
|
* @returns {Function} Middleware de Express
|
||||||
|
*
|
||||||
|
* EJEMPLO:
|
||||||
|
* router.get('/admin/metrics',
|
||||||
|
* requireAuth,
|
||||||
|
* requireRole('admin'),
|
||||||
|
* async (req, res) => { ... }
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
function requireRole(allowedRoles) {
|
||||||
|
// Normalizar a array
|
||||||
|
const roles = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles];
|
||||||
|
|
||||||
|
return (req, res, next) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(401).json({
|
||||||
|
ok: false,
|
||||||
|
error: 'No autenticado'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRoles = req.user.roles || [];
|
||||||
|
|
||||||
|
// Verificar si tiene al menos uno de los roles permitidos
|
||||||
|
const hasRole = userRoles.some(role => roles.includes(role));
|
||||||
|
|
||||||
|
if (!hasRole) {
|
||||||
|
return res.status(403).json({
|
||||||
|
ok: false,
|
||||||
|
error: 'No tienes el rol necesario para acceder',
|
||||||
|
details: {
|
||||||
|
requiredRoles: roles,
|
||||||
|
yourRoles: userRoles
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ========================================
|
||||||
|
* EXPORTACIÓN
|
||||||
|
* ========================================
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
checkPermission,
|
||||||
|
requireRole
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue