Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
This commit is contained in:
Omar Sánchez Pizarro
2026-01-20 03:21:50 +01:00
parent 19932854ca
commit 81bf0675ed
32 changed files with 3081 additions and 932 deletions

View File

@@ -0,0 +1,30 @@
import { getUser } from '../services/mongodb.js';
// Middleware para verificar que el usuario es administrador
export async function adminAuthMiddleware(req, res, next) {
// Debe estar autenticado primero (requiere basicAuthMiddleware antes)
if (!req.user || !req.user.username) {
return res.status(401).json({ error: 'Authentication required', message: 'Se requiere autenticación' });
}
try {
const user = await getUser(req.user.username);
if (!user) {
return res.status(401).json({ error: 'Invalid user', message: 'Usuario no encontrado' });
}
const userRole = user.role || 'user';
if (userRole !== 'admin') {
return res.status(403).json({ error: 'Forbidden', message: 'Se requieren permisos de administrador' });
}
// Usuario es admin, continuar
next();
} catch (error) {
console.error('Error verificando permisos de admin:', error);
res.status(500).json({ error: 'Internal server error', message: 'Error verificando permisos' });
}
}

View File

@@ -1,8 +1,8 @@
import crypto from 'crypto';
import { getRedisClient } from '../services/redis.js';
import { getDB, getSession, deleteSession as deleteSessionFromDB, deleteUserSessions as deleteUserSessionsFromDB, getUser } from '../services/mongodb.js';
// Duración de la sesión en segundos (24 horas)
const SESSION_DURATION = 24 * 60 * 60;
// Duración de la sesión en milisegundos (24 horas)
const SESSION_DURATION = 24 * 60 * 60 * 1000;
// Generar token seguro
function generateToken() {
@@ -11,10 +11,10 @@ function generateToken() {
// Autenticación por token Middleware
export async function authMiddleware(req, res, next) {
const redisClient = getRedisClient();
const db = getDB();
if (!redisClient) {
return res.status(500).json({ error: 'Redis no está disponible. La autenticación requiere Redis.' });
if (!db) {
return res.status(500).json({ error: 'MongoDB no está disponible. La autenticación requiere MongoDB.' });
}
const authHeader = req.headers.authorization;
@@ -30,32 +30,41 @@ export async function authMiddleware(req, res, next) {
}
try {
// Verificar token en Redis
const sessionKey = `session:${token}`;
const sessionData = await redisClient.get(sessionKey);
// Verificar token en MongoDB
const session = await getSession(token);
if (!sessionData) {
if (!session) {
return res.status(401).json({ error: 'Invalid token', message: 'Token inválido o sesión expirada' });
}
// Parsear datos de sesión
const session = JSON.parse(sessionData);
// Verificar que la sesión no haya expirado
if (session.expiresAt && new Date(session.expiresAt) < new Date()) {
await deleteSessionFromDB(token);
return res.status(401).json({ error: 'Invalid token', message: 'Sesión expirada' });
}
// Verificar que el usuario aún existe
const userKey = `user:${session.username}`;
const userExists = await redisClient.exists(userKey);
const user = await getUser(session.username);
if (!userExists) {
if (!user) {
// Eliminar sesión si el usuario ya no existe
await redisClient.del(sessionKey);
await deleteSessionFromDB(token);
return res.status(401).json({ error: 'Invalid token', message: 'Usuario no encontrado' });
}
// Actualizar TTL de la sesión (refresh)
await redisClient.expire(sessionKey, SESSION_DURATION);
// Actualizar expiración de la sesión (refresh)
const sessionsCollection = db.collection('sessions');
const newExpiresAt = new Date(Date.now() + SESSION_DURATION);
await sessionsCollection.updateOne(
{ token },
{ $set: { expiresAt: newExpiresAt } }
);
// Autenticación exitosa
req.user = { username: session.username };
// Autenticación exitosa - incluir rol en req.user
req.user = {
username: session.username,
role: user.role || 'user' // Por defecto 'user' si no tiene rol
};
req.token = token;
next();
} catch (error) {
@@ -69,35 +78,14 @@ export const basicAuthMiddleware = authMiddleware;
// Función para crear sesión
export async function createSession(username) {
const redisClient = getRedisClient();
if (!redisClient) {
throw new Error('Redis no está disponible');
}
const token = generateToken();
const sessionKey = `session:${token}`;
const sessionData = {
username,
createdAt: new Date().toISOString(),
};
// Almacenar sesión en Redis con TTL
await redisClient.setEx(sessionKey, SESSION_DURATION, JSON.stringify(sessionData));
return token;
const { createSession: createSessionInDB } = await import('../services/mongodb.js');
return await createSessionInDB(username);
}
// Función para invalidar sesión
export async function invalidateSession(token) {
const redisClient = getRedisClient();
if (!redisClient) {
return false;
}
try {
const sessionKey = `session:${token}`;
await redisClient.del(sessionKey);
return true;
return await deleteSessionFromDB(token);
} catch (error) {
console.error('Error invalidando sesión:', error);
return false;
@@ -106,31 +94,11 @@ export async function invalidateSession(token) {
// Función para invalidar todas las sesiones de un usuario
export async function invalidateUserSessions(username) {
const redisClient = getRedisClient();
if (!redisClient) {
return false;
}
try {
// Buscar todas las sesiones del usuario
const keys = await redisClient.keys('session:*');
let count = 0;
for (const key of keys) {
const sessionData = await redisClient.get(key);
if (sessionData) {
const session = JSON.parse(sessionData);
if (session.username === username) {
await redisClient.del(key);
count++;
}
}
}
return count;
return await deleteUserSessionsFromDB(username);
} catch (error) {
console.error('Error invalidando sesiones del usuario:', error);
return false;
return 0;
}
}

View File

@@ -1,4 +1,4 @@
import { getRateLimiter } from '../services/redis.js';
import { getRateLimiter } from '../services/mongodb.js';
// Rate Limiter Middleware
export async function rateLimitMiddleware(req, res, next) {