Skip to content

Latest commit

 

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Backend2 — Registro y autenticación con Passport.js

Requisitos

  • Node.js v18+
  • MongoDB (local o Atlas)

Instalación

git clone https://github.com/AlessiaPrecedo/Backend2.git
cd Backend2
npm install

Variables de entorno

Copiá el archivo de ejemplo y completá los valores:

cp .env.example .env
PORT=8080
MONGO_URL=mongodb://localhost:27017/backend2
JWT_SECRET=unaclavesecretamuylargarandom123
JWT_EXPIRES_IN=1h
NODE_ENV=development

Correr el proyecto

# Desarrollo (recarga automática)
npm run dev

# Producción
npm start

Tests automatizados

npm test

Corre con el test runner nativo de Node (node:test, sin dependencias extra) y cubre: hash de contraseñas, generación/verificación de JWT (incluye token manipulado y expirado), el middleware validateLoginFields, y el middleware auth (que internamente usa la estrategia current de Passport). No incluye tests de integración contra una base de datos real.

Arquitectura en capas

Capa Responsabilidad
Route Define el endpoint y aplica middlewares
Controller Recibe el request HTTP, dispara la estrategia de Passport correspondiente, y genera el JWT / setea la cookie cuando corresponde
Passport strategy Contiene la lógica de autenticación (validaciones, hash, reglas de negocio de login/registro)
Repository Único punto de contacto con la base de datos
Utils Helpers reutilizables (bcrypt, JWT, errores)

DAO / Repository / DTO

  • DAO: archivos en src/dao/* son los únicos que importan los modelos de Mongoose y exponen métodos CRUD/consulta (findById, findOne, create, update, aggregate, etc.).
  • Repository: usa los DAOs; no importa modelos directamente; expone métodos orientados al dominio (ej: findByEmail, countOccupiedSpots, findPublishedEvents).
  • Service: consume repositories, contiene la lógica de negocio (validaciones, control de cupos, permisos, envío de emails).
  • Controller: coordina request/response; no contiene lógica de negocio.
  • DTOs: en src/dto/* transforman documentos a objetos de respuesta filtrando campos sensibles (ej: password).

Casos a probar antes de entregar

  • Flujo completo: registro → login → crear evento → inscribirse → consultar mis tickets → cancelar (la respuesta de /api/sessions/current no incluye password).
  • Respuesta de ticket con populate no incluye password del usuario.
  • Endpoint con error de negocio devuelve código HTTP correcto (400/401/403/404/409 según corresponda, no 500).
  • Endpoint protegido sin sesión → 401; con sesión sin permisos → 403.

Autenticación con Passport.js

Toda la autenticación pasa por estrategias de Passport, centralizadas en src/config/passport.js. app.js solo llama a initializePassport() y a passport.initialize(); no define ninguna estrategia ahí.

Estrategia Tipo Qué hace
register passport-local (con passReqToCallback) Valida los campos, normaliza el email, verifica que no exista otro usuario con ese email, hashea la contraseña con bcrypt y crea el usuario con role: "user" por defecto
login passport-local Busca el usuario por email y compara la contraseña con bcrypt. Si algo no coincide, devuelve done(null, false) sin indicar cuál fue el problema
current passport-jwt (con extractor custom desde cookie) Lee el JWT de la cookie currentUser, lo verifica, y deja el payload decodificado en req.user

Puntos importantes:

  • El JWT se genera en el controller (controllers/session.controller.js), nunca dentro de una estrategia. La estrategia login solo confirma la identidad del usuario.
  • La cookie currentUser se setea con httpOnly: true, sameSite: "lax", maxAge: 3600000 y secure solo si NODE_ENV=production.
  • POST /api/sessions/logout no pasa por Passport: solo borra la cookie.

Preparado para providers externos (Google, GitHub, etc.)

passport.js está pensado para crecer sin tocar app.js ni las rutas: agregar un login social es sumar un nuevo passport.use("google", new GoogleStrategy(...)) dentro de initializePassport(), y una ruta nueva que apunte a esa estrategia. El resto de la app (cookie, JWT, middleware auth) no cambia.

Endpoints

Eventos

Método Ruta Acceso Descripción
GET /api/events Público Listar eventos con filtros, paginación y ordenamiento
GET /api/events/:id Público Obtener un evento por ID
POST /api/events organizer, admin Crear evento. organizer se asigna desde req.user
PUT /api/events/:id Dueño del evento o admin Actualizar evento. Eventos cancelados requieren justificación para editar
PATCH /api/events/:id/status Dueño del evento o admin Cambiar el status de un evento

Filtros admitidos para GET /api/events:

  • statusdraft, published, cancelled, finished
  • category
  • location
  • dateFrom, dateTo
  • page, limit
  • sort (ej: sort=date, sort=-date)

Sesiones

Método Ruta Descripción
POST /api/sessions/register Registro de usuario
POST /api/sessions/login Login de usuario, setea cookie currentUser con el JWT
GET /api/sessions/current Devuelve el usuario autenticado a partir de la cookie
POST /api/sessions/logout Elimina la cookie currentUser

Usuarios

Método Ruta Descripción
GET /users Obtener todos los usuarios
GET /users/:id Obtener usuario por ID
POST /users Crear usuario
PUT /users/:id Actualizar usuario
DELETE /users/:id Eliminar usuario

Tickets

Método Ruta Acceso Descripción
POST /api/events/:eid/tickets Autenticado Crear inscripción a un evento con control de cupos y confirmación
GET /api/tickets/my-tickets Autenticado Listar los tickets del usuario autenticado con datos del evento
GET /api/events/:eid/tickets organizer o admin Ver las inscripciones de un evento propio
PATCH /api/tickets/:tid/cancel Dueño o admin Cancelar un ticket y liberar cupo automáticamente

Estados de ticket

  • confirmed: inscripción activa confirmada.
  • pending: inscripción pendiente (si se usa en futuras extensiones).
  • cancelled: ticket cancelado, no ocupa cupo.

Flujo de inscripción

  1. El usuario autenticado envía quantity y el eventId por la ruta /api/events/:eid/tickets.
  2. El servicio valida que el evento exista, esté published, tenga cupos disponibles y que el usuario no tenga ya un ticket activo para ese evento.
  3. Si todo está bien, se crea el ticket y se envía un email de confirmación mediante Nodemailer.
  4. Al cancelar, el ticket pasa a cancelled y se registra cancelledAt; el cupo queda disponible automáticamente porque los tickets cancelados no cuentan en el cálculo.

Variables de email

El proyecto usa las siguientes variables de entorno para Nodemailer:

  • MAIL_HOST
  • MAIL_PORT
  • MAIL_USER
  • MAIL_PASS
  • MAIL_FROM

Endpoint: Registro de usuario

POST /api/sessions/register

Delega en la estrategia register de Passport.

Body (JSON)

{
  "first_name": "Kenny",
  "last_name": "Test",
  "email": "kenny@test.com",
  "password": "abc12345"
}

Respuesta exitosa 201

{
  "status": "success",
  "payload": {
    "_id": "664f...",
    "first_name": "Kenny",
    "last_name": "Test",
    "email": "kenny@test.com",
    "role": "user"
  }
}

La contraseña no se devuelve en la respuesta, ni en texto plano ni hasheada.

Casos de prueba

Caso Body Respuesta esperada
Registro exitoso Todos los campos válidos 201 con datos del usuario
Campo faltante Sin first_name, por ejemplo 400 All fields are required
Email inválido email: "kenny" 400 Invalid email format
Contraseña corta password: "abc" 400 Password must be at least 8 characters long
Email ya registrado Mismo email dos veces 400 User already exists

Endpoint: Login

POST /api/sessions/login

Valida presencia de campos con validateLoginFields, y delega en la estrategia login de Passport. El JWT lo genera el controller una vez que Passport confirma la identidad.

Body (JSON)

{
  "email": "kenny@test.com",
  "password": "abc12345"
}

Respuesta exitosa 200

{
  "status": "success",
  "message": "Login correcto"
}

Además de la respuesta, se setea una cookie currentUser (httpOnly, sameSite: lax, maxAge: 1h, secure solo en producción) con el JWT firmado, cuyo payload es { id, email, role }.

Respuesta de error 400 (campos faltantes)

{ "status": "error", "message": "Email y password son requeridos" }

Respuesta de error 401 (email inexistente o password incorrecta — mismo mensaje en ambos casos)

{ "status": "error", "message": "Invalid credentials" }

Endpoint: Usuario actual

GET /api/sessions/current

Protegida por el middleware auth, que usa la estrategia current de Passport para leer y validar el JWT desde la cookie currentUser (se envía automáticamente por el navegador tras el login).

Respuesta exitosa 200

{
  "status": "success",
  "payload": {
    "id": "664f...",
    "email": "kenny@test.com",
    "role": "user"
  }
}

Respuesta de error 401 (sin cookie)

{ "status": "error", "message": "No autenticado" }

Respuesta de error 401 (token inválido o expirado)

{ "status": "error", "message": "Token inválido o expirado" }

Endpoint: Logout

POST /api/sessions/logout

Elimina la cookie currentUser. No pasa por Passport.

Respuesta exitosa 200

{ "status": "success", "message": "Logout exitoso" }

Casos de prueba — Sesión

Caso Request Respuesta esperada
Registro → login → /current → logout → /current Flujo completo 201200 + cookie → 200 payload → 200401
Registro con email duplicado Mismo email dos veces 400 "User already exists"
Login con email inexistente Email que no existe 401 "Invalid credentials"
Login con password incorrecta Email correcto, password mal 401 "Invalid credentials"
/current sin cookie Sin header Cookie 401 "No autenticado"
/current con token manipulado/expirado Cookie alterada o vencida 401 "Token inválido o expirado"

Roles y autorización

Roles

role vive en el modelo User, con 3 valores posibles: user (default), organizer, admin. El registro público (POST /api/sessions/register) siempre asigna role: "user", sin importar qué venga en el body — así nadie puede autoasignarse admin u organizer.

Matriz de permisos

Acción user organizer admin
Consultar eventos publicados
Crear eventos
Modificar/cancelar eventos propios
Modificar cualquier evento
Ver todos los usuarios

401 vs 403

  • 401 (No autenticado): no hay cookie, o el JWT es inválido/expiró. No sabemos quién sos. Lo maneja el middleware auth.
  • 403 (Prohibido): sabemos quién sos (JWT válido), pero tu role no tiene permiso para esa acción. Lo maneja el middleware authorize.

Middlewares

Middleware Archivo Qué hace
auth middlewares/auth.middleware.js Valida el JWT de la cookie currentUser (vía la estrategia current de Passport). Responde 401 si no hay sesión válida y deja el usuario en req.user
authorize(...roles) middlewares/authorize.middleware.js Recibe los roles permitidos como parámetro y los compara contra req.user.role. Responde 403 si no coincide

Siempre se usan en ese orden: auth primero (saber quién sos), authorize después (ver si te corresponde).

Rutas protegidas

Método Ruta Protección
GET /api/sessions/current auth (cualquier usuario autenticado)
POST /events auth + authorize("organizer","admin")
PUT /events/:id auth + authorize("organizer","admin") + dueño del evento (o admin)
DELETE /events/:id auth + authorize("organizer","admin") + dueño del evento (o admin)
GET /users auth + authorize("admin")

Propiedad de recursos

Al crear un evento, el organizer se toma de req.user.id (nunca del body). Al modificar o borrar un evento, si req.user.role !== "admin", se valida que event.organizer coincida con req.user.id; si no, 403.

Tickets e inscripciones

Modelo Ticket

Campo Tipo Notas
event ObjectId (ref Event) referencia, no objeto embebido
user ObjectId (ref User) referencia, no objeto embebido
quantity Number debe ser un entero > 0
status String pending, confirmed, cancelled
reservationCode String código único generado al confirmar
createdAt Date automático (timestamps)
cancelledAt Date se completa recién al cancelar

Rutas

Método Ruta Acceso
POST /api/events/:eid/tickets autenticado
GET /api/tickets/my-tickets autenticado (solo sus propios tickets)
GET /api/events/:eid/tickets dueño del evento (organizer) o admin
PATCH /api/tickets/:tid/cancel dueño del ticket o admin

Flujo de inscripción y reglas de negocio (en services/ticket.service.js)

Al hacer POST /api/events/:eid/tickets con { "quantity": N }, en este orden:

  1. quantity tiene que ser un entero mayor a 0
  2. El evento tiene que existir (404 si no)
  3. El evento tiene que estar published (esto ya descarta draft, cancelled y finished en un solo chequeo)
  4. El usuario no puede tener ya un ticket activo (no cancelado) para ese mismo evento
  5. Tiene que haber cupo: capacity - (suma de quantity de todos los tickets NO cancelados) tiene que ser ≥ a la quantity pedida
  6. Si todo pasa: se crea el ticket con status: "confirmed" y un reservationCode único, y se dispara el email de confirmación (si el mail falla, no revierte la inscripción — solo se loguea el error)

Cancelación

PATCH /api/tickets/:tid/cancel: valida que el ticket exista, que sea del usuario que lo pide (o que sea admin), y que no esté ya cancelado. Cambia status a cancelled y completa cancelledAtnunca borra el documento. Como el cálculo de cupo solo suma tickets no cancelados, el cupo queda libre automáticamente apenas se cancela.

Notificaciones por email

utils/mailer.js usa Nodemailer con las credenciales de MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS, MAIL_FROM (siempre desde variables de entorno, nunca hardcodeadas). Se dispara un mail de confirmación al crear un ticket.

Seguridad

  • La contraseña se hashea con bcrypt antes de guardarse en MongoDB
  • El role no puede manipularse desde el body; siempre se asigna user por defecto
  • El email se normaliza (trim + lowercase) antes de guardarse
  • El JWT nunca incluye la contraseña en su payload
  • La cookie del JWT es httpOnly, por lo que no es accesible desde JavaScript del navegador

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages