- 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.
264 lines
8.3 KiB
JavaScript
264 lines
8.3 KiB
JavaScript
import express from 'express';
|
|
import bcrypt from 'bcrypt';
|
|
import { getRedisClient } from '../services/redis.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 {
|
|
const redisClient = getRedisClient();
|
|
if (!redisClient) {
|
|
return res.status(500).json({ error: 'Redis no está disponible' });
|
|
}
|
|
|
|
const { currentPassword, newPassword } = req.body;
|
|
const username = req.user.username;
|
|
|
|
if (!currentPassword || !newPassword) {
|
|
return res.status(400).json({ error: 'currentPassword y newPassword son requeridos' });
|
|
}
|
|
|
|
if (newPassword.length < 6) {
|
|
return res.status(400).json({ error: 'La nueva contraseña debe tener al menos 6 caracteres' });
|
|
}
|
|
|
|
const userKey = `user:${username}`;
|
|
const userData = await redisClient.hGetAll(userKey);
|
|
|
|
if (!userData || !userData.passwordHash) {
|
|
return res.status(404).json({ error: 'Usuario no encontrado' });
|
|
}
|
|
|
|
// Verificar contraseña actual
|
|
const match = await bcrypt.compare(currentPassword, userData.passwordHash);
|
|
if (!match) {
|
|
return res.status(401).json({ error: 'Contraseña actual incorrecta' });
|
|
}
|
|
|
|
// Hashear nueva contraseña y actualizar
|
|
const newPasswordHash = await bcrypt.hash(newPassword, 10);
|
|
await redisClient.hSet(userKey, {
|
|
...userData,
|
|
passwordHash: newPasswordHash,
|
|
updatedAt: new Date().toISOString(),
|
|
});
|
|
|
|
// 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 });
|
|
}
|
|
});
|
|
|
|
// Obtener lista de usuarios (requiere autenticación admin)
|
|
router.get('/', basicAuthMiddleware, async (req, res) => {
|
|
try {
|
|
const redisClient = getRedisClient();
|
|
if (!redisClient) {
|
|
return res.status(500).json({ error: 'Redis no está disponible' });
|
|
}
|
|
|
|
// Obtener todas las claves de usuarios
|
|
const userKeys = await redisClient.keys('user:*');
|
|
const users = [];
|
|
|
|
for (const key of userKeys) {
|
|
const username = key.replace('user:', '');
|
|
const userData = await redisClient.hGetAll(key);
|
|
|
|
if (userData && userData.username) {
|
|
users.push({
|
|
username: userData.username,
|
|
createdAt: userData.createdAt || null,
|
|
updatedAt: userData.updatedAt || null,
|
|
createdBy: userData.createdBy || null,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Ordenar por fecha de creación (más recientes primero)
|
|
users.sort((a, b) => {
|
|
const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
|
const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
|
return dateB - dateA;
|
|
});
|
|
|
|
res.json({ users, total: users.length });
|
|
} catch (error) {
|
|
console.error('Error obteniendo usuarios:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Crear nuevo usuario (requiere autenticación admin)
|
|
router.post('/', basicAuthMiddleware, 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' });
|
|
}
|
|
|
|
if (username.length < 3) {
|
|
return res.status(400).json({ error: 'El nombre de usuario debe tener al menos 3 caracteres' });
|
|
}
|
|
|
|
if (password.length < 6) {
|
|
return res.status(400).json({ error: 'La contraseña debe tener al menos 6 caracteres' });
|
|
}
|
|
|
|
const userKey = `user:${username}`;
|
|
const userExists = await redisClient.exists(userKey);
|
|
|
|
if (userExists) {
|
|
return res.status(409).json({ error: 'El usuario ya existe' });
|
|
}
|
|
|
|
// Hashear contraseña y crear usuario
|
|
const passwordHash = await bcrypt.hash(password, 10);
|
|
await redisClient.hSet(userKey, {
|
|
username,
|
|
passwordHash,
|
|
createdAt: new Date().toISOString(),
|
|
createdBy: req.user.username,
|
|
});
|
|
|
|
console.log(`✅ Usuario creado: ${username} por ${req.user.username}`);
|
|
res.json({ success: true, message: 'Usuario creado correctamente', username });
|
|
} catch (error) {
|
|
console.error('Error creando usuario:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Eliminar usuario (requiere autenticación admin)
|
|
router.delete('/:username', basicAuthMiddleware, async (req, res) => {
|
|
try {
|
|
const redisClient = getRedisClient();
|
|
if (!redisClient) {
|
|
return res.status(500).json({ error: 'Redis no está disponible' });
|
|
}
|
|
|
|
const { username } = req.params;
|
|
const currentUser = req.user.username;
|
|
|
|
// No permitir eliminar el propio usuario
|
|
if (username === currentUser) {
|
|
return res.status(400).json({ error: 'No puedes eliminar tu propio usuario' });
|
|
}
|
|
|
|
const userKey = `user:${username}`;
|
|
const userExists = await redisClient.exists(userKey);
|
|
|
|
if (!userExists) {
|
|
return res.status(404).json({ error: 'Usuario no encontrado' });
|
|
}
|
|
|
|
// Eliminar usuario
|
|
await redisClient.del(userKey);
|
|
|
|
console.log(`✅ Usuario eliminado: ${username} por ${currentUser}`);
|
|
res.json({ success: true, message: `Usuario ${username} eliminado correctamente` });
|
|
} catch (error) {
|
|
console.error('Error eliminando usuario:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
export default router;
|
|
|