import express from 'express'; import bcrypt from 'bcrypt'; import { getDB, getUser, createUser, deleteUser as deleteUserFromDB, getAllUsers, updateUserPassword, getActiveSessions } from '../services/mongodb.js'; import { basicAuthMiddleware, createSession, invalidateSession, invalidateUserSessions } from '../middlewares/auth.js'; import { adminAuthMiddleware } from '../middlewares/adminAuth.js'; import { combineFingerprint } from '../utils/fingerprint.js'; import { getActiveUsers } from '../services/websocket.js'; const router = express.Router(); // Endpoint de registro (público) router.post('/register', async (req, res) => { try { const db = getDB(); if (!db) { return res.status(500).json({ error: 'MongoDB no está disponible' }); } const { username, password, email, planId } = 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' }); } // Validar email si se proporciona if (email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return res.status(400).json({ error: 'Email no válido' }); } } // Verificar si el usuario ya existe const existingUser = await getUser(username); if (existingUser) { return res.status(409).json({ error: 'El usuario ya existe' }); } // Verificar si es plan de pago y si Stripe está disponible const selectedPlanId = planId || 'free'; const isPaidPlan = selectedPlanId !== 'free'; if (isPaidPlan) { // Para planes de pago, verificar que Stripe esté configurado const { getStripeClient } = await import('../services/stripe.js'); const stripeClient = getStripeClient(); if (!stripeClient) { return res.status(503).json({ error: 'El sistema de pagos no está disponible actualmente', message: 'No se pueden procesar planes de pago en este momento. Por favor, intenta con el plan gratuito o contacta con soporte.' }); } } // Hashear contraseña y crear usuario const passwordHash = await bcrypt.hash(password, 10); await createUser({ username, passwordHash, email: email || null, role: 'user', // Si es plan de pago, marcar como pendiente hasta que se complete el pago status: isPaidPlan ? 'pending_payment' : 'active', }); // Crear suscripción inicial const { updateUserSubscription } = await import('../services/mongodb.js'); await updateUserSubscription(username, { planId: selectedPlanId, status: isPaidPlan ? 'pending' : 'active', currentPeriodStart: new Date(), currentPeriodEnd: null, cancelAtPeriodEnd: false, }); console.log(`✅ Usuario registrado: ${username} (${selectedPlanId}) - Estado: ${isPaidPlan ? 'pending_payment' : 'active'}`); res.json({ success: true, message: 'Usuario creado correctamente', username, planId: selectedPlanId, requiresPayment: isPaidPlan, }); } catch (error) { console.error('Error registrando 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 }); } }); // 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, fingerprint: clientFingerprint, deviceInfo: clientDeviceInfo } = 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' }); } // Verificar estado del usuario if (user.status === 'pending_payment') { return res.status(403).json({ error: 'Payment pending', message: 'Tu cuenta está pendiente de pago. Por favor, completa el proceso de pago para activar tu cuenta.', status: 'pending_payment' }); } if (user.status === 'suspended' || user.status === 'disabled') { return res.status(403).json({ error: 'Account suspended', message: 'Tu cuenta ha sido suspendida. Contacta con soporte.', status: user.status }); } // Generar fingerprint del dispositivo const { fingerprint, deviceInfo } = combineFingerprint(clientFingerprint, clientDeviceInfo, req); // Crear sesión/token con fingerprint const token = await createSession(username, fingerprint, deviceInfo); // 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 }); } }); // Obtener usuarios activos/conectados (requiere autenticación) router.get('/active', basicAuthMiddleware, async (req, res) => { try { const db = getDB(); if (!db) { return res.status(500).json({ error: 'MongoDB no está disponible' }); } // Obtener usuarios activos del WebSocket const activeUsersWS = getActiveUsers(); // Obtener sesiones activas de la base de datos const activeSessions = await getActiveSessions(); // Combinar información y eliminar duplicados const userMap = new Map(); // Añadir usuarios del WebSocket for (const user of activeUsersWS) { if (!userMap.has(user.username)) { userMap.set(user.username, { username: user.username, role: user.role, status: user.status, lastActivity: user.lastActivity, connectedViaWebSocket: true }); } } // Añadir usuarios de sesiones activas (que podrían no estar en WebSocket) for (const session of activeSessions) { if (!userMap.has(session.username)) { const user = await getUser(session.username); userMap.set(session.username, { username: session.username, role: user?.role || 'user', status: 'active', lastActivity: session.lastActivity?.toISOString() || null, connectedViaWebSocket: false, deviceInfo: session.deviceInfo }); } } // Convertir a array const activeUsers = Array.from(userMap.values()); // Ordenar por última actividad (más recientes primero) activeUsers.sort((a, b) => { const dateA = a.lastActivity ? new Date(a.lastActivity).getTime() : 0; const dateB = b.lastActivity ? new Date(b.lastActivity).getTime() : 0; return dateB - dateA; }); res.json({ activeUsers, total: activeUsers.length, timestamp: new Date().toISOString() }); } catch (error) { console.error('Error obteniendo usuarios activos:', error); res.status(500).json({ error: error.message }); } }); export default router;