338 lines
10 KiB
JavaScript
338 lines
10 KiB
JavaScript
/**
|
|
* ========================================
|
|
* RUTAS DE AUTENTICACIÓN
|
|
* ========================================
|
|
*
|
|
* Este módulo maneja todas las operaciones relacionadas con autenticación
|
|
* y gestión de perfil de usuarios:
|
|
* - Registro de nuevos usuarios
|
|
* - Login (inicio de sesión)
|
|
* - Obtener perfil del usuario autenticado
|
|
* - Actualizar perfil
|
|
* - Cambiar contraseña
|
|
*
|
|
* ORIGEN: Se registra en server.js como app.use('/api/auth', authRoutes)
|
|
* DESTINO: Todas las rutas aquí comienzan con /api/auth/*
|
|
*
|
|
* TABLA DE RUTAS:
|
|
* POST /api/auth/register - Crear nuevo usuario
|
|
* POST /api/auth/login - Iniciar sesión
|
|
* GET /api/auth/me - Obtener perfil (requiere auth)
|
|
* PUT /api/auth/me/profile - Actualizar perfil (requiere auth)
|
|
* PUT /api/auth/me/password - Cambiar contraseña (requiere auth)
|
|
*/
|
|
|
|
const express = require('express');
|
|
const bcrypt = require('bcryptjs'); // Para hashear/verificar contraseñas
|
|
const pool = require('../config/db'); // Pool de conexiones a MySQL
|
|
const { signToken, requireAuth } = require('../utils/jwt'); // JWT: generar y verificar tokens
|
|
const { registerValidator, loginValidator } = require('../validators/auth.validators');
|
|
const handleValidation = require('../middleware/handleValidation');
|
|
|
|
const router = express.Router();
|
|
|
|
/**
|
|
* ========================================
|
|
* POST /api/auth/register
|
|
* ========================================
|
|
* Registra un nuevo usuario en el sistema
|
|
*
|
|
* FLUJO DE EJECUCIÓN:
|
|
* 1. registerValidator valida los datos del body
|
|
* 2. handleValidation verifica que no haya errores de validación
|
|
* 3. Verifica que el email no esté registrado
|
|
* 4. Hashea la contraseña con bcrypt
|
|
* 5. Inserta el usuario en la base de datos
|
|
* 6. Genera un token JWT
|
|
* 7. Devuelve el token y los datos del usuario
|
|
*
|
|
* REQUEST BODY:
|
|
* {
|
|
* "nombre": "Juan",
|
|
* "apellido": "Pérez",
|
|
* "email": "juan@mail.com",
|
|
* "genero": "M",
|
|
* "password": "miPassword123"
|
|
* }
|
|
*
|
|
* RESPONSE (201):
|
|
* {
|
|
* "msg": "Usuario registrado con éxito",
|
|
* "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
* "user": {
|
|
* "id_usuario": 1,
|
|
* "nombre": "Juan",
|
|
* "apellido": "Pérez",
|
|
* "email": "juan@mail.com",
|
|
* "genero": "M"
|
|
* }
|
|
* }
|
|
*
|
|
* DESTINO: Frontend guarda el token y lo usa en peticiones futuras
|
|
*/
|
|
router.post('/register', registerValidator, handleValidation, async (req, res) => {
|
|
const { nombre, apellido, email, genero, password } = req.body;
|
|
|
|
try {
|
|
// 1. Verifica si el email ya está registrado
|
|
const [exists] = await pool.query(
|
|
'SELECT id_usuario FROM usuarios WHERE email = ?',
|
|
[email]
|
|
);
|
|
|
|
if (exists.length) {
|
|
// Email duplicado - retorna error 409 Conflict
|
|
return res.status(409).json({ error: 'Email ya registrado' });
|
|
}
|
|
|
|
// 2. Hashea la contraseña (bcrypt con 10 rondas de salt)
|
|
// NUNCA guardar contraseñas en texto plano
|
|
const hash = await bcrypt.hash(password, 10);
|
|
|
|
// 3. Inserta el nuevo usuario en la base de datos
|
|
const [result] = await pool.query(
|
|
`INSERT INTO usuarios (nombre, apellido, email, genero, password_hash, fecha_registro, activo)
|
|
VALUES (?, ?, ?, ?, ?, NOW(), 1)`,
|
|
[nombre, apellido, email, genero, hash]
|
|
);
|
|
|
|
// 4. Genera el token JWT con el ID del usuario recién creado
|
|
const token = signToken({ id_usuario: result.insertId, email });
|
|
|
|
// 5. Prepara el objeto de usuario (sin el password_hash)
|
|
const user = {
|
|
id_usuario: result.insertId,
|
|
nombre,
|
|
apellido,
|
|
email,
|
|
genero
|
|
};
|
|
|
|
// 6. Devuelve respuesta exitosa con el token y datos del usuario
|
|
return res.status(201).json({
|
|
msg: 'Usuario registrado con éxito',
|
|
token,
|
|
user
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error('REGISTER ERROR:', err);
|
|
return res.status(500).json({ error: 'Error en el servidor' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* ========================================
|
|
* POST /api/auth/login
|
|
* ========================================
|
|
* Autentica a un usuario existente
|
|
*
|
|
* FLUJO DE EJECUCIÓN:
|
|
* 1. loginValidator valida email y password
|
|
* 2. handleValidation verifica errores
|
|
* 3. Busca el usuario por email en la BD
|
|
* 4. Verifica que el usuario esté activo
|
|
* 5. Compara la contraseña con bcrypt.compare()
|
|
* 6. Genera token JWT
|
|
* 7. Devuelve token y datos del usuario
|
|
*
|
|
* REQUEST BODY:
|
|
* {
|
|
* "email": "juan@mail.com",
|
|
* "password": "miPassword123"
|
|
* }
|
|
*
|
|
* RESPONSE (200):
|
|
* {
|
|
* "msg": "Login exitoso",
|
|
* "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
* "user": { "id_usuario": 1, "nombre": "Juan", ... }
|
|
* }
|
|
*
|
|
* ERRORES:
|
|
* - 401: Email no existe o contraseña incorrecta
|
|
* - 401: Usuario inactivo
|
|
*/
|
|
router.post('/login', loginValidator, handleValidation, async (req, res) => {
|
|
const { email, password } = req.body;
|
|
|
|
try {
|
|
// 1. Busca el usuario por email (incluye password_hash para comparar)
|
|
const [users] = await pool.query(
|
|
`SELECT id_usuario, nombre, apellido, email, genero, password_hash, activo
|
|
FROM usuarios
|
|
WHERE email = ?`,
|
|
[email]
|
|
);
|
|
|
|
// 2. Verifica que el usuario exista
|
|
if (!users.length) {
|
|
// Mensaje genérico para no revelar si el email existe
|
|
return res.status(401).json({ error: 'Credenciales inválidas' });
|
|
}
|
|
|
|
const u = users[0];
|
|
|
|
// 3. Verifica que el usuario esté activo
|
|
if (Number(u.activo) === 0) {
|
|
return res.status(401).json({ error: 'Usuario inactivo' });
|
|
}
|
|
|
|
// 4. Compara la contraseña ingresada con el hash almacenado
|
|
const ok = await bcrypt.compare(password, u.password_hash || '');
|
|
if (!ok) {
|
|
return res.status(401).json({ error: 'Credenciales inválidas' });
|
|
}
|
|
|
|
// 5. Genera token JWT con los datos del usuario
|
|
const token = signToken({ id_usuario: u.id_usuario, email: u.email });
|
|
|
|
// 6. Prepara el objeto de usuario (sin password_hash por seguridad)
|
|
const user = {
|
|
id_usuario: u.id_usuario,
|
|
nombre: u.nombre,
|
|
apellido: u.apellido,
|
|
email: u.email,
|
|
genero: u.genero
|
|
};
|
|
|
|
// 7. Devuelve respuesta exitosa
|
|
return res.json({ msg: 'Login exitoso', token, user });
|
|
|
|
} catch (err) {
|
|
console.error('LOGIN ERROR:', err);
|
|
return res.status(500).json({ error: 'Error en el servidor' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* ========================================
|
|
* PUT /api/auth/me/profile
|
|
* ========================================
|
|
* Actualiza nombre y/o apellido del usuario autenticado
|
|
*
|
|
* REQUIERE: Token JWT en header Authorization
|
|
* FLUJO: requireAuth valida token → actualiza en BD → devuelve usuario actualizado
|
|
*/
|
|
router.put('/me/profile', requireAuth, async (req, res) => {
|
|
try {
|
|
const { id_usuario } = req.user;
|
|
const { nombre = null, apellido = null } = req.body;
|
|
|
|
if (nombre === null && apellido === null) {
|
|
return res.status(400).json({ error: 'Sin cambios' });
|
|
}
|
|
|
|
const [result] = await pool.query(
|
|
`UPDATE usuarios
|
|
SET nombre = COALESCE(?, nombre),
|
|
apellido = COALESCE(?, apellido)
|
|
WHERE id_usuario = ?`,
|
|
[nombre, apellido, id_usuario]
|
|
);
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({ error: 'Usuario no encontrado' });
|
|
}
|
|
|
|
const [rows] = await pool.query(
|
|
`SELECT id_usuario, nombre, apellido, email, genero
|
|
FROM usuarios
|
|
WHERE id_usuario = ?`,
|
|
[id_usuario]
|
|
);
|
|
|
|
return res.json({ ok: true, user: rows[0] });
|
|
} catch (e) {
|
|
console.error('PUT /me/profile', e);
|
|
return res.status(500).json({ error: 'Error al actualizar perfil' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* ========================================
|
|
* GET /api/auth/me
|
|
* ========================================
|
|
* Devuelve el perfil completo del usuario autenticado + sus roles
|
|
*
|
|
* REQUIERE: Token JWT
|
|
* FLUJO: requireAuth → consulta usuario → consulta roles → devuelve ambos
|
|
*/
|
|
router.get('/me', requireAuth, async (req, res) => {
|
|
try {
|
|
const { id_usuario } = req.user;
|
|
|
|
// Usuario básico
|
|
const [urows] = await pool.query(
|
|
`SELECT id_usuario, nombre, apellido, email, genero
|
|
FROM usuarios
|
|
WHERE id_usuario = ?`,
|
|
[id_usuario]
|
|
);
|
|
if (!urows.length) {
|
|
return res.status(404).json({ error: 'Usuario no encontrado' });
|
|
}
|
|
|
|
// Roles
|
|
const [rrows] = await pool.query(
|
|
`SELECT r.id_rol, r.nombre
|
|
FROM usuarios_roles ur
|
|
JOIN roles r ON r.id_rol = ur.id_rol
|
|
WHERE ur.id_usuario = ?`,
|
|
[id_usuario]
|
|
);
|
|
|
|
const user = { ...urows[0], roles: rrows || [] };
|
|
return res.json({ ok: true, user });
|
|
} catch (e) {
|
|
console.error('GET /api/auth/me', e);
|
|
return res.status(500).json({ error: 'Error al obtener perfil' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* ========================================
|
|
* PUT /api/auth/me/password
|
|
* ========================================
|
|
* Cambia la contraseña del usuario autenticado
|
|
*
|
|
* REQUIERE: Token JWT + contraseña actual correcta
|
|
* FLUJO: requireAuth → verifica contraseña actual → hashea nueva → actualiza BD
|
|
*/
|
|
router.put('/me/password', requireAuth, async (req, res) => {
|
|
try {
|
|
const { id_usuario } = req.user;
|
|
const { actual, nueva } = req.body;
|
|
|
|
if (!actual || !nueva) {
|
|
return res.status(400).json({ error: 'Faltan campos' });
|
|
}
|
|
|
|
// Obtiene hash actual
|
|
const [rows] = await pool.query(
|
|
'SELECT password_hash FROM usuarios WHERE id_usuario = ?',
|
|
[id_usuario]
|
|
);
|
|
if (!rows.length) {
|
|
return res.status(404).json({ error: 'Usuario no encontrado' });
|
|
}
|
|
|
|
const match = await bcrypt.compare(actual, rows[0].password_hash);
|
|
if (!match) {
|
|
return res.status(400).json({ error: 'La contraseña actual no coincide' });
|
|
}
|
|
|
|
const newHash = await bcrypt.hash(nueva, 10);
|
|
await pool.query(
|
|
'UPDATE usuarios SET password_hash = ? WHERE id_usuario = ?',
|
|
[newHash, id_usuario]
|
|
);
|
|
|
|
return res.json({ ok: true, message: 'Contraseña actualizada' });
|
|
} catch (e) {
|
|
console.error('PUT /me/password', e);
|
|
return res.status(500).json({ error: 'Error al actualizar contraseña' });
|
|
}
|
|
});
|
|
|
|
module.exports = router; |