contact-gate.routes.js

Overview

Este archivo define el router Express del módulo de “contact gate”. El router se monta bajo /api/contact-gate desde services/api/src/app.js, y expone dos rutas autenticadas para el flujo de ALTCHA de contacto: emitir un challenge y autorizar una intención de contacto. Antes de llegar a los handlers, aplica requireAuth y dos limitadores por usuario, uno para cada ruta.

[!NOTE] Estas rutas siempre requieren sesión. El middleware requireAuth corre antes que los rate limiters, para que req.auth.userId exista cuando se calcula la clave por usuario.

How it works

El archivo crea un Router, aplica router.use(requireAuth), y luego registra dos endpoints. El primero es GET /altcha-challenge, que pasa por challengeRateLimit y delega en contactGateAltchaChallenge. El segundo es POST /authorize, que pasa por authorizeRateLimit y delega en contactGateAuthorize.

flowchart TB
    Client[Authenticated client] --> Router[contact gate router]
    Router --> RequireAuth[requireAuth]

    RequireAuth --> ChallengeLimit[challengeRateLimit]
    RequireAuth --> AuthorizeLimit[authorizeRateLimit]

    ChallengeLimit --> ChallengeHandler[contactGateAltchaChallenge]
    AuthorizeLimit --> AuthorizeHandler[contactGateAuthorize]

La ruta de challenge emite un challenge ALTCHA ligado al usuario autenticado. La ruta de authorize valida el intento de contacto y usa el payload ALTCHA solo cuando hace falta abrir una ventana nueva. El router no implementa esa lógica; solo la encamina hacia el controller. fileciteturn0file0turn0file2

Key components

{
    "title": "requireAuth",
    "content": "Middleware aplicado a todo el router antes de cualquier rate limiter. Garantiza que ambas rutas solo funcionen con sesi\u00f3n autenticada.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.middleware.js",
    "badges": [
        "public"
    ]
}
{
    "title": "contactGateAltchaChallenge",
    "content": "Handler importado desde el controller. Genera y devuelve un challenge ALTCHA ligado al usuario autenticado.",
    "type": "function",
    "filePath": "services/api/src/modules/contact-gate/contact-gate.controller.js",
    "badges": [
        "public"
    ]
}
{
    "title": "contactGateAuthorize",
    "content": "Handler importado desde el controller. Recibe un payload ALTCHA opcional y devuelve la autorizaci\u00f3n del intento de contacto.",
    "type": "function",
    "filePath": "services/api/src/modules/contact-gate/contact-gate.controller.js",
    "badges": [
        "public"
    ]
}

Data & fields

[
    {
        "name": "altchaPayload",
        "type": "string",
        "description": "Cuerpo opcional enviado por POST /api/contact-gate/authorize cuando no existe una ventana vigente."
    },
    {
        "name": "authorized",
        "type": "boolean",
        "description": "Marca la respuesta exitosa de autorización del intento de contacto."
    },
    {
        "name": "remaining",
        "type": "number",
        "description": "Intenciones de contacto restantes dentro de la ventana actual."
    },
    {
        "name": "resetAt",
        "type": "string | Date",
        "description": "Momento en que se restablece la ventana de autorización."
    },
    {
        "name": "error",
        "type": "string",
        "description": "Mensaje devuelto por los rate limiters cuando se supera el límite por usuario."
    }
]

Behaviors

  • router.use(requireAuth) — protege todo el módulo antes de registrar las rutas.
  • challengeRateLimit — limita GET /altcha-challenge a 30 solicitudes por 5 minutos y por usuario.
  • authorizeRateLimit — limita POST /authorize a 60 solicitudes por 15 minutos y por usuario.
  • keyGenerator — usa req.auth.userId para separar el consumo de cada usuario.
  • router.get('/altcha-challenge', ...) — expone el challenge ALTCHA del gate de contacto.
  • router.post('/authorize', ...) — expone la autorización de contacto.
  • standardHeaders: 'draft-8' y legacyHeaders: false — configuran los encabezados de rate limit con formato moderno.

[!IMPORTANT] ALTCHA complementa el control de frecuencia, pero no lo reemplaza. Si el usuario supera el límite del router, la respuesta es 429 aunque el flujo de negocio sea válido. fileciteturn0file0turn0file14

API

GET /api/contact-gate/altcha-challenge

{
    "title": "Get contact gate ALTCHA challenge",
    "description": "Returns an ALTCHA challenge tied to the authenticated user for the contact gate.",
    "method": "GET",
    "baseUrl": "/api",
    "endpoint": "/contact-gate/altcha-challenge",
    "headers": [
        {
            "key": "Authorization",
            "value": "Bearer <token>",
            "required": true
        }
    ],
    "queryParams": [],
    "pathParams": [],
    "bodyType": "none",
    "requestBody": "",
    "formData": [],
    "rawBody": "",
    "responses": {
        "200": {
            "description": "Success",
            "body": "{\n  \"challenge\": \"<ALTCHA challenge object>\"\n}"
        },
        "429": {
            "description": "Rate limit exceeded",
            "body": "{\n  \"error\": \"Demasiadas solicitudes de verificacion. Intenta mas tarde.\"\n}"
        }
    }
}

La ruta usa challengeRateLimit antes del handler. El límite es de 30 solicitudes cada 5 minutos, por usuario autenticado. fileciteturn0file0turn0file14

POST /api/contact-gate/authorize

{
    "title": "Authorize contact intent",
    "description": "Consumes a valid contact gate window or accepts an ALTCHA payload to open a new authorization window.",
    "method": "POST",
    "baseUrl": "/api",
    "endpoint": "/contact-gate/authorize",
    "headers": [
        {
            "key": "Authorization",
            "value": "Bearer <token>",
            "required": true
        },
        {
            "key": "Content-Type",
            "value": "application/json",
            "required": true
        }
    ],
    "queryParams": [],
    "pathParams": [],
    "bodyType": "json",
    "requestBody": "{\n  \"altchaPayload\": \"<optional ALTCHA payload>\"\n}",
    "formData": [],
    "rawBody": "",
    "responses": {
        "200": {
            "description": "Success",
            "body": "{\n  \"authorized\": true,\n  \"remaining\": 14,\n  \"resetAt\": \"2026-01-01T00:00:00.000Z\"\n}"
        },
        "400": {
            "description": "ALTCHA required",
            "body": "{\n  \"error\": \"Se requiere completar la verificacion ALTCHA para continuar.\",\n  \"code\": \"ALTCHA_REQUIRED\",\n  \"field\": \"altchaPayload\"\n}"
        },
        "429": {
            "description": "Rate limit exceeded",
            "body": "{\n  \"error\": \"Demasiados intentos de contacto. Intenta mas tarde.\"\n}"
        }
    }
}

La ruta usa authorizeRateLimit antes del handler. El límite es de 60 solicitudes cada 15 minutos, por usuario autenticado. El body admite altchaPayload opcional, y el controller decide si hace falta según la ventana del usuario. fileciteturn0file0turn0file2turn0file12

Gotchas & edge cases

[!WARNING] El orden de middleware importa. Si requireAuth no se ejecutara primero, req.auth.userId no estaría disponible para los keyGenerator de los limitadores. Este archivo evita ese problema de forma explícita.

[!WARNING] Los mensajes de bloqueo son genéricos y salen desde el rate limiter, no desde el controller. En GET /altcha-challenge el mensaje es “Demasiadas solicitudes de verificacion. Intenta mas tarde.”, y en POST /authorize es “Demasiados intentos de contacto. Intenta mas tarde.”.

[!NOTE] El router no contiene lógica de negocio ni valida altchaPayload por sí mismo. Solo combina autenticación, límites por usuario y delegación a los handlers del controller. fileciteturn0file0turn0file2

services/api/src/app.js monta este router en /api/contact-gate, junto al resto de módulos API.

services/api/src/modules/contact-gate/contact-gate.controller.js implementa los dos handlers que este router expone. contactGateAltchaChallenge genera el challenge, y contactGateAuthorize consume el payload y responde con el resultado.

services/api/src/modules/contact-gate/contact-gate.service.js contiene la regla de autorización del gate, incluido el purpose contact-intent, la ventana de contacto y el contador persistido en User.contactGate.

services/api/src/config/altcha.js provee la infraestructura ALTCHA reutilizada por este flujo. El router depende de esa capa a través del controller y del service. fileciteturn0file0turn0file2