feat: enhance authentication system with token-based login and session management

- Updated the backend to support token-based authentication, replacing basic auth.
- Added session management functions for creating, invalidating, and refreshing sessions.
- Refactored user routes to include login and logout endpoints.
- Modified frontend to handle token storage and session validation.
- Improved user experience by ensuring sessions are invalidated upon password changes.
This commit is contained in:
Omar Sánchez Pizarro
2026-01-20 00:48:49 +01:00
parent e99424c9ba
commit 19932854ca
6 changed files with 354 additions and 119 deletions

View File

@@ -1,10 +1,93 @@
import express from 'express';
import bcrypt from 'bcrypt';
import { getRedisClient } from '../services/redis.js';
import { basicAuthMiddleware } from '../middlewares/auth.js';
import { basicAuthMiddleware, createSession, invalidateSession, invalidateUserSessions } from '../middlewares/auth.js';
const router = express.Router();
// Endpoint de login (público)
router.post('/login', async (req, res) => {
try {
const redisClient = getRedisClient();
if (!redisClient) {
return res.status(500).json({ error: 'Redis no está disponible' });
}
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: 'username y password son requeridos' });
}
// Buscar usuario en Redis
const userKey = `user:${username}`;
const userExists = await redisClient.exists(userKey);
if (!userExists) {
return res.status(401).json({ error: 'Invalid credentials', message: 'Usuario o contraseña incorrectos' });
}
// Obtener hash de la contraseña
const userData = await redisClient.hGetAll(userKey);
const passwordHash = userData.passwordHash;
if (!passwordHash) {
return res.status(401).json({ error: 'Invalid credentials', message: 'Usuario o contraseña incorrectos' });
}
// Verificar contraseña
const match = await bcrypt.compare(password, passwordHash);
if (!match) {
return res.status(401).json({ error: 'Invalid credentials', message: 'Usuario o contraseña incorrectos' });
}
// Crear sesión/token
const token = await createSession(username);
console.log(`✅ Login exitoso: ${username}`);
res.json({
success: true,
token,
username,
message: 'Login exitoso'
});
} catch (error) {
console.error('Error en login:', error);
res.status(500).json({ error: error.message });
}
});
// Endpoint de logout (requiere autenticación)
router.post('/logout', basicAuthMiddleware, async (req, res) => {
try {
const token = req.token;
if (token) {
await invalidateSession(token);
console.log(`✅ Logout exitoso: ${req.user.username}`);
}
res.json({ success: true, message: 'Logout exitoso' });
} catch (error) {
console.error('Error en logout:', error);
res.status(500).json({ error: error.message });
}
});
// Verificar token (para validar si la sesión sigue activa)
router.get('/me', basicAuthMiddleware, async (req, res) => {
try {
res.json({
success: true,
username: req.user.username,
authenticated: true
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Cambiar contraseña de usuario
router.post('/change-password', basicAuthMiddleware, async (req, res) => {
try {
@@ -45,8 +128,11 @@ router.post('/change-password', basicAuthMiddleware, async (req, res) => {
updatedAt: new Date().toISOString(),
});
console.log(`✅ Contraseña actualizada para usuario: ${username}`);
res.json({ success: true, message: 'Contraseña actualizada correctamente' });
// Invalidar todas las sesiones del usuario (requiere nuevo login)
await invalidateUserSessions(username);
console.log(`✅ Contraseña actualizada para usuario: ${username} (todas las sesiones invalidadas)`);
res.json({ success: true, message: 'Contraseña actualizada correctamente. Por favor, inicia sesión nuevamente.' });
} catch (error) {
console.error('Error cambiando contraseña:', error);
res.status(500).json({ error: error.message });