Files
wallabicher/web/backend/routes/users.js
Omar Sánchez Pizarro 81bf0675ed mongodb
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2026-01-20 03:21:50 +01:00

267 lines
8.6 KiB
JavaScript

import express from 'express';
import bcrypt from 'bcrypt';
import { getDB, getUser, createUser, deleteUser as deleteUserFromDB, getAllUsers, updateUserPassword } from '../services/mongodb.js';
import { basicAuthMiddleware, createSession, invalidateSession, invalidateUserSessions } from '../middlewares/auth.js';
import { adminAuthMiddleware } from '../middlewares/adminAuth.js';
const router = express.Router();
// Endpoint de login (público)
router.post('/login', async (req, res) => {
try {
const db = getDB();
if (!db) {
return res.status(500).json({ error: 'MongoDB 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 MongoDB
const user = await getUser(username);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials', message: 'Usuario o contraseña incorrectos' });
}
// Obtener hash de la contraseña
const passwordHash = user.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);
// Obtener rol del usuario
const userRole = user.role || 'user';
console.log(`✅ Login exitoso: ${username} (${userRole})`);
res.json({
success: true,
token,
username,
role: userRole,
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 {
const user = await getUser(req.user.username);
res.json({
success: true,
username: req.user.username,
role: user?.role || 'user',
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 db = getDB();
if (!db) {
return res.status(500).json({ error: 'MongoDB 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 user = await getUser(username);
if (!user || !user.passwordHash) {
return res.status(404).json({ error: 'Usuario no encontrado' });
}
// Verificar contraseña actual
const match = await bcrypt.compare(currentPassword, user.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 updateUserPassword(username, newPasswordHash);
// 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 ve todos, user ve solo suyo)
router.get('/', basicAuthMiddleware, async (req, res) => {
try {
const db = getDB();
if (!db) {
return res.status(500).json({ error: 'MongoDB no está disponible' });
}
const users = await getAllUsers(req.user.username);
// Convertir ObjectId a string y formatear fechas
const formattedUsers = users.map(user => {
const formatted = { ...user };
formatted._id = user._id?.toString();
if (user.createdAt && typeof user.createdAt === 'object') {
formatted.createdAt = user.createdAt.toISOString();
}
if (user.updatedAt && typeof user.updatedAt === 'object') {
formatted.updatedAt = user.updatedAt.toISOString();
}
return formatted;
});
// Ordenar por fecha de creación (más recientes primero)
formattedUsers.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: formattedUsers, total: formattedUsers.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, adminAuthMiddleware, async (req, res) => {
try {
const db = getDB();
if (!db) {
return res.status(500).json({ error: 'MongoDB 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' });
}
// Verificar si el usuario ya existe
const existingUser = await getUser(username);
if (existingUser) {
return res.status(409).json({ error: 'El usuario ya existe' });
}
// Hashear contraseña y crear usuario
const passwordHash = await bcrypt.hash(password, 10);
await createUser({
username,
passwordHash,
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);
// Manejar error de duplicado
if (error.code === 11000) {
return res.status(409).json({ error: 'El usuario ya existe' });
}
res.status(500).json({ error: error.message });
}
});
// Eliminar usuario (requiere autenticación admin)
router.delete('/:username', basicAuthMiddleware, adminAuthMiddleware, async (req, res) => {
try {
const db = getDB();
if (!db) {
return res.status(500).json({ error: 'MongoDB 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' });
}
// Verificar si el usuario existe
const user = await getUser(username);
if (!user) {
return res.status(404).json({ error: 'Usuario no encontrado' });
}
// Eliminar usuario y sus sesiones
await deleteUserFromDB(username);
await invalidateUserSessions(username);
// También eliminar sus workers
const workersCollection = db.collection('workers');
await workersCollection.deleteOne({ username });
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;