Middleware de autenticación y verificación de sesión

Overview

Este archivo define tres middlewares de seguridad para el backend de auth. Su función principal es validar tokens de acceso, limitar el acceso por rol y bloquear acciones cuando el correo aún no está verificado.

El archivo actúa como una frontera de autorización reutilizable. requireAuth construye req.auth a partir del JWT y de la consulta al modelo User. requireSystemRole y requireVerifiedEmail se apoyan en ese contexto ya autenticado. El repositorio muestra este middleware como pieza transversal de rutas protegidas, y también como apoyo para flujos de autenticación y cuenta

[!NOTE] req.auth solo existe después de una validación exitosa. Las rutas posteriores deben tratarlo como un dato derivado del middleware, no como una entrada confiable por sí sola

How it works

El flujo central empieza con un encabezado Authorization. Si el valor no usa Bearer, el middleware rechaza la petición. Si el token existe, lo verifica con verifyAccessToken, confirma que pertenece a un user, y carga datos mínimos desde User para validar la sesión. Si todo encaja, adjunta la identidad en req.auth y continúa con next().

flowchart TB
    Request[Peticion entrante] --> AuthMW[requireAuth]
    AuthMW --> Header[Leer authorization]
    Header --> Bearer{Bearer valido}
    Bearer -- No --> Missing[ApiError 401 token no proporcionado]
    Bearer -- Si --> Token[Extraer token]
    Token --> Verify[verifyAccessToken]
    Verify --> Subject{SubjectType user}
    Subject -- No --> TypeError[ApiError 401 tipo no autorizado]
    Subject -- Si --> UserLookup[User findById select lean]
    UserLookup --> Active{Cuenta activa y version valida}
    Active -- No --> SessionError[ApiError 401 sesion invalida]
    Active -- Si --> AuthCtx[req auth]
    AuthCtx --> Next[next]

    AuthMW -. cualquier fallo .-> Catch[ApiError 401 token invalido o expirado]

El middleware no responde con res. Solo decide si la petición avanza o termina en next(error). Ese patrón permite combinarlo con otros guards, como el control por rol y la verificación de correo.

  1. Lee req.headers.authorization.
  2. Verifica que empiece con Bearer .
  3. Extrae el token con replace y trim.
  4. Llama a verifyAccessToken(token).
  5. Comprueba que payload.subjectType sea user.
  6. Busca el usuario con User.findById(payload.sub).
  7. Valida que la cuenta esté activa.
  8. Compara payload.tokenVersion con user.tokenVersion.
  9. Construye req.auth.
  10. Llama a next().

Key components

{
    "title": "requireAuth",
    "content": "[badge:public] Middleware de Express que valida un JWT de acceso, confirma la sesi\u00f3n del usuario y deja `req.auth` listo para el siguiente handler.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.middleware.js",
    "badges": [
        "public"
    ]
}
{
    "title": "requireSystemRole",
    "content": "[badge:public] Factory de middleware que restringe el acceso seg\u00fan `req.auth.systemRole` y devuelve 403 si el rol no coincide.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.middleware.js",
    "badges": [
        "public"
    ]
}
{
    "title": "requireVerifiedEmail",
    "content": "[badge:public] Middleware que bloquea peticiones sin `emailVerifiedAt`, lanza un recordatorio en segundo plano y responde con 403 `EMAIL_NOT_VERIFIED`.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.middleware.js",
    "badges": [
        "public"
    ]
}

requireSystemRole devuelve una función interna llamada systemRoleGuard. Esa función usa allowedRoles.includes(req.auth.systemRole) para decidir el acceso. requireVerifiedEmail revisa req.auth.emailVerifiedAt y, si falta, dispara maybeSendVerificationReminder sin esperar su resultado.

Data & fields

El archivo trabaja con tres grupos de datos. El primero llega desde la petición. El segundo proviene del JWT verificado. El tercero se construye con datos del usuario cargado desde la base.

[
    {
        "name": "req.headers.authorization",
        "type": "string",
        "required": true,
        "description": "Encabezado HTTP que debe comenzar con `Bearer `."
    },
    {
        "name": "payload",
        "type": "object",
        "required": true,
        "description": "Payload devuelto por `verifyAccessToken`.",
        "children": [
            {
                "name": "sub",
                "type": "string",
                "required": true,
                "description": "Identificador del usuario autenticado."
            },
            {
                "name": "subjectType",
                "type": "string",
                "required": true,
                "description": "Debe ser `user` para que el middleware acepte el token."
            },
            {
                "name": "tokenVersion",
                "type": "number",
                "description": "Versión del token usada para invalidar sesiones antiguas."
            }
        ]
    },
    {
        "name": "req.auth",
        "type": "object",
        "description": "Contexto autenticado agregado por `requireAuth` cuando la validación termina bien.",
        "children": [
            {
                "name": "userId",
                "type": "string",
                "required": true,
                "description": "Valor tomado de `payload.sub`."
            },
            {
                "name": "email",
                "type": "string",
                "required": true,
                "description": "Correo del usuario cargado desde `User`."
            },
            {
                "name": "subjectType",
                "type": "string",
                "required": true,
                "description": "Valor fijo `user`."
            },
            {
                "name": "systemRole",
                "type": "string",
                "required": true,
                "description": "Rol global del usuario."
            },
            {
                "name": "accountRole",
                "type": "string",
                "required": true,
                "description": "Rol funcional de la cuenta."
            },
            {
                "name": "tokenVersion",
                "type": "number",
                "required": true,
                "description": "Versión efectiva del token confirmada contra el usuario."
            },
            {
                "name": "emailVerifiedAt",
                "type": "string or null",
                "description": "Fecha de verificación del correo, o null si no existe."
            }
        ]
    },
    {
        "name": "allowedRoles",
        "type": "string[]",
        "description": "Lista variable de roles permitidos para `requireSystemRole`."
    }
]

Campos clave en la validación

  • authorization controla si la petición entra en la ruta protegida.
  • subjectType evita usar tokens de otro tipo como si fueran de usuario.
  • sub identifica al usuario a cargar.
  • tokenVersion permite invalidar sesiones antiguas.
  • systemRole decide el acceso en requireSystemRole.
  • emailVerifiedAt decide el acceso en requireVerifiedEmail.

Behaviors

  • requireAuth — Lee el token desde Authorization, verifica el JWT y carga el usuario asociado. Si todo coincide, asigna req.auth y llama a next().
  • requireAuth — Rechaza tokens ausentes, vacíos, con tipo incorrecto, con sesión inválida o con verificación fallida.
  • requireSystemRole(...allowedRoles) — Devuelve un middleware que responde con 403 si req.auth.systemRole no está en la lista permitida.
  • requireVerifiedEmail — Permite seguir solo cuando req.auth.emailVerifiedAt existe.
  • requireVerifiedEmail — Si falta el correo verificado, envía un recordatorio best-effort con maybeSendVerificationReminder(req.auth.userId) y luego termina con 403.

Workflows

  • requireAuth valida la presencia del encabezado.
  • requireAuth extrae y verifica el JWT.
  • requireAuth comprueba que el token pertenezca a un user.
  • requireAuth consulta User para validar cuenta y versión.
  • requireAuth construye req.auth.
  • requireSystemRole compara el rol actual con allowedRoles.
  • requireVerifiedEmail bloquea escrituras cuando el correo no está verificado.

Gotchas & edge cases

[!WARNING] requireAuth normaliza cualquier fallo como ApiError(401, 'Token inválido o expirado'). Eso incluye ausencia de token, formato inválido, token no autorizado y cualquier error capturado en el try.

[!CAUTION] requireSystemRole depende de que req.auth exista y tenga systemRole. Sin requireAuth antes, siempre termina en 403.

[!NOTE] requireVerifiedEmail no espera el envío del recordatorio. Usa void maybeSendVerificationReminder(...) para dispararlo en segundo plano.

[!IMPORTANT] El comentario del archivo ubica requireVerifiedEmail en escrituras de Listings y en POST /api/conversations/:id/refinements. También aclara que no se usa en lecturas, incluido Match, ni en el resto de conversaciones, comentarios, likes, contact events, leads o billing

[!WARNING] La comparación de tokenVersion invalida sesiones antiguas. Si el valor del token y el valor persistido no coinciden, la petición termina en Sesion invalida.

  • services/api/src/utils/jwt.js — aporta verifyAccessToken, que valida el JWT que consume requireAuth
  • services/api/src/utils/apiError.js — aporta ApiError, usado para normalizar respuestas de autenticación y autorización
  • services/api/src/models/User.js — requireAuth usa findById, select y lean para recuperar el estado de la cuenta.
  • services/api/src/modules/auth/auth.service.js — aporta maybeSendVerificationReminder, usado por requireVerifiedEmail
  • services/api/src/modules/auth/auth.routes.js — aplica requireAuth en rutas autenticadas como GET /me, PATCH /me y PATCH /change-password
  • services/api/test/user-auth.test.js — confirma que un token con subjectType distinto de user es rechazado con 401

[!NOTE] En el contexto del repositorio, este middleware aparece como una pieza reutilizable de seguridad para varias rutas protegidas, no solo para auth. El material recuperado lo vincula con módulos de acceso transversal