Agregango archivos necesarios para auth

This commit is contained in:
Roberto Castellanos 2025-10-19 22:12:18 -06:00
parent 90c2ad934c
commit 35fe8db1f9
2 changed files with 644 additions and 0 deletions

501
src/config/permissions.js Normal file
View File

@ -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
};

View File

@ -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
};