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
requireAuthcorre antes que los rate limiters, para quereq.auth.userIdexista 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. fileciteturn0file0turn0file2
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— limitaGET /altcha-challengea 30 solicitudes por 5 minutos y por usuario.authorizeRateLimit— limitaPOST /authorizea 60 solicitudes por 15 minutos y por usuario.keyGenerator— usareq.auth.userIdpara 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'ylegacyHeaders: 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
429aunque el flujo de negocio sea válido. fileciteturn0file0turn0file14
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. fileciteturn0file0turn0file14
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. fileciteturn0file0turn0file2turn0file12
Gotchas & edge cases
[!WARNING] El orden de middleware importa. Si
requireAuthno se ejecutara primero,req.auth.userIdno estaría disponible para loskeyGeneratorde 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-challengeel mensaje es “Demasiadas solicitudes de verificacion. Intenta mas tarde.”, y enPOST /authorizees “Demasiados intentos de contacto. Intenta mas tarde.”.
[!NOTE] El router no contiene lógica de negocio ni valida
altchaPayloadpor sí mismo. Solo combina autenticación, límites por usuario y delegación a los handlers del controller. fileciteturn0file0turn0file2
Related
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. fileciteturn0file0turn0file2