Rutas de autenticación
Resumen
Este archivo define el router de autenticación de Express para la API. Conecta cada ruta HTTP con su controlador correspondiente y aplica requireAuth solo donde la operación necesita una sesión activa. El módulo cubre registro, inicio de sesión, perfil autenticado, recuperación de cuenta, cambio de contraseña, verificación de correo, preferencias de notificación y verificación telefónica.
También introduce varias capas de limitación de frecuencia. Algunas protegen desafíos ALTCHA, otras protegen operaciones sensibles sobre la cuenta, y otras agrupan intentos por usuario, IP o teléfono destino. El archivo no contiene la lógica de negocio; solo ensambla rutas, middlewares y limitadores.
[!NOTE] La documentación interna previa menciona una superficie parcial de auth, pero este router expone también
PATCH /change-password.
Cómo funciona
El flujo es directo: Router() crea el enrutador, cada endpoint se registra con un verbo HTTP, y los controladores hacen el trabajo real. Las rutas protegidas pasan por requireAuth antes de llegar al manejador, y varias rutas añaden rateLimit para controlar abuso.
flowchart TD
Client[Cliente HTTP] --> Router[auth routes]
Router --> PublicGroup[Flujos publicos]
Router --> AuthGroup[Flujos autenticados]
Router --> OneTimeGroup[Flujos con token unico]
Router --> PhoneGroup[Verificacion de telefono]
PublicGroup --> AltchaChallenge[GET altcha challenge]
PublicGroup --> Register[POST register]
PublicGroup --> SignIn[POST sign-in]
PublicGroup --> GoogleAuth[POST google]
AuthGroup --> GoogleDeletionReauth[POST account deletion google reauth]
AuthGroup --> AccountDeletionChallenge[GET account deletion challenge]
AuthGroup --> Me[GET me]
AuthGroup --> UpdateMe[PATCH me]
AuthGroup --> DeleteMe[DELETE me]
AuthGroup --> PasswordCreateGoogleReauth[POST password create google reauth]
AuthGroup --> PasswordCreate[POST password create]
AuthGroup --> ResendVerification[POST resend verification]
AuthGroup --> ChangePassword[PATCH change password]
OneTimeGroup --> RestoreChallenge[GET restore account altcha challenge]
OneTimeGroup --> RestoreAccount[POST restore account]
OneTimeGroup --> AccountRestorationResend[POST account restoration resend]
OneTimeGroup --> AccountExport[GET account export]
OneTimeGroup --> ForgotPassword[POST forgot password]
OneTimeGroup --> ResetPassword[POST reset password]
OneTimeGroup --> VerifyEmail[POST verify email]
OneTimeGroup --> DigestUnsubscribeGet[GET digest unsubscribe]
OneTimeGroup --> DigestUnsubscribePost[POST digest unsubscribe]
PhoneGroup --> PhoneStart[POST phone verify start]
PhoneGroup --> PhoneCheck[POST phone verify check]
GoogleDeletionReauth --> RequireAuth[requireAuth]
AccountDeletionChallenge --> RequireAuth
Me --> RequireAuth
UpdateMe --> RequireAuth
DeleteMe --> RequireAuth
PasswordCreateGoogleReauth --> RequireAuth
PasswordCreate --> RequireAuth
ResendVerification --> RequireAuth
ChangePassword --> RequireAuth
PhoneStart --> RequireAuth
PhoneCheck --> RequireAuth
Este router actúa como capa de entrada. Primero decide si la ruta es pública, autenticada o de token de un solo uso. Después aplica el middleware correcto y delega en auth.controller.js o phone-verification.controller.js.
Componentes clave
Estos son los elementos que el archivo ensambla y usa directamente. Cada uno tiene una responsabilidad concreta dentro del módulo de autenticación.
router— Instancia deRouter()que agrupa todas las rutas del módulo.challengeRateLimit— Limita los endpoints de challenge a 30 peticiones cada 5 minutos.registrationRateLimit— Limita registro y alta con Google a 20 peticiones cada 15 minutos.accountDeletionRateLimit— Limita la eliminación de cuenta a 10 intentos cada 30 minutos.googleAccountDeletionReauthRateLimit— Limita la reautenticación Google para eliminación de cuenta a 10 intentos cada 30 minutos.googlePasswordCreateReauthRateLimit— Limita la reautenticación Google para crear contraseña a 10 intentos cada 30 minutos.passwordCreateRateLimit— Limita la creación de contraseña a 10 intentos cada 30 minutos.accountRestorationRateLimit— Limita la restauración de cuenta a 10 intentos cada 30 minutos.accountExportRateLimit— Limita la descarga de exportación a 20 peticiones cada 15 minutos.resendVerificationRateLimit— Limita el reenvío de verificación a 5 peticiones cada 15 minutos.verifyEmailRateLimit— Limita la verificación de correo a 10 intentos cada 30 minutos.accountRestorationResendRateLimit— Limita el reenvío de restauración a 5 peticiones cada 15 minutos.digestUnsubscribeRateLimit— Limita el alta y confirmación de baja del digest a 20 peticiones cada 15 minutos.phoneVerificationStartIpRateLimit— Limita el inicio de verificación telefónica por IP a 10 peticiones cada 30 minutos.phoneVerificationStartUserRateLimit— Limita el inicio de verificación telefónica por usuario o IP a 5 peticiones cada 30 minutos.phoneVerificationStartPhoneRateLimit— Limita el inicio de verificación telefónica por teléfono destino a 5 peticiones cada 30 minutos.phoneVerificationCheckUserRateLimit— Limita la comprobación telefónica por usuario o IP a 20 intentos cada 15 minutos.phoneVerificationCheckPhoneRateLimit— Limita la comprobación telefónica por teléfono destino a 20 intentos cada 15 minutos.phoneRateLimitKey(req)— Genera la key de limitación por teléfono connormalizePhoneE164yhashPhoneForRateLimit; si no hay teléfono válido, usa la IP.
Datos y campos
La mayor parte de los datos de este archivo son configuraciones de limitación de frecuencia. También hay una función auxiliar que calcula claves de control por teléfono.
Limitadores de desafío y registro
windowMs[badge:read-only]number— Ventana temporal del limitador, expresada en milisegundos.limit[badge:read-only]number— Número máximo de solicitudes dentro de la ventana.standardHeaders[badge:read-only]string— Todos los limitadores usandraft-8.legacyHeaders[badge:read-only]boolean— Todos desactivan headers heredados.message.error[badge:read-only]string— Mensaje devuelto cuando se supera el límite.
Limitadores con clave por usuario
keyGenerator[badge:read-only](req) => string— En algunos límites usareq.auth.userId.req.auth.userId[badge:read-only]string— Identidad autenticada usada por varioskeyGenerator.req.ip[badge:read-only]string— Fallback cuando no existe identidad autenticada.
Limitadores con clave por teléfono
normalizePhoneE164(req.body?.phone)— Normaliza el teléfono antes de calcular la clave.hashPhoneForRateLimit(phone)— Genera el hash SHA-256 usado como identificador opaco.phone:${hash}— Prefijo de agrupación usado por el store de rate limit.ipKeyGenerator(req.ip)— Fallback cuando el cuerpo no contiene un teléfono válido.
[!IMPORTANT] El comentario del archivo deja claro que el número en claro no se usa como clave. Solo se guarda el hash en memoria de proceso.
Rutas y parámetros visibles
GET /restore-account/altcha-challenge— Usatokencomo query param en los ejemplos de pruebas internas.POST /phone/verify/start— El rate limiter consultareq.body.phone.POST /phone/verify/check— El rate limiter también consultareq.body.phone.
Comportamientos
Estos son los comportamientos verificables que fija el archivo. El router no implementa la lógica de negocio, pero sí define el acceso y la protección de cada operación.
GET /altcha/challenge— Devuelve un challenge ALTCHA para el flujo de autenticación general.POST /register— Crea una cuenta y aplica limitación de registro.POST /sign-in— Inicia sesión sin limitador local en este archivo.POST /google— Crea o autentica una cuenta mediante Google ID token verificado server-side.POST /account-deletion/google-reauth— Emite una reautenticación Google de un solo uso para una operación sensible.GET /altcha/account-deletion-challenge— Devuelve un challenge ALTCHA para borrar la cuenta autenticada.GET /me— Lee la identidad autenticada.PATCH /me— Actualiza el perfil autenticado.DELETE /me— Elimina la cuenta autenticada con un límite adicional.GET /restore-account/altcha-challenge— Prepara el desafío para restaurar una cuenta enpending_deletion.POST /restore-account— Completa la restauración con token de propósito.POST /account-restoration/resend— Reenvía la restauración sin revelar si el correo existe.GET /account-export— Descarga la exportación de cuenta con token de propósito.POST /forgot-password— Inicia el flujo de recuperación de contraseña.POST /reset-password— Finaliza el restablecimiento de contraseña.PATCH /change-password— Cambia la contraseña de una cuenta autenticada.POST /password/create/google-reauth— Emite la reautenticación necesaria para crear contraseña en cuentas Google-only.POST /password/create— Crea contraseña para una cuenta autenticada.POST /verify-email— Verifica una dirección de correo sin requerir sesión.POST /resend-verification— Reenvía la verificación usando la sesión actual, no un email del body.GET /notification-preferences/digest/unsubscribe— Solo valida el token y muestra confirmación.POST /notification-preferences/digest/unsubscribe— Ejecuta la baja real del digest.POST /phone/verify/start— Inicia la verificación telefónica con SMS.POST /phone/verify/check— Comprueba el código de verificación ya enviado.
[!WARNING]
GET /notification-preferences/digest/unsubscribeyPOST /notification-preferences/digest/unsubscribecomparten la misma ruta, pero no la misma responsabilidad. ElGETsolo valida y muestra confirmación; elPOSTmuta la preferencia.
[!IMPORTANT]
POST /resend-verificationno acepta un email en el body. Usa siempre la identidad de la sesión para evitar enumeración. fileciteturn0file0turn0file4
API
Los bloques siguientes documentan cada endpoint visible en este archivo. Los cuerpos de respuesta se mantienen conservadores, porque este archivo solo define el enrutado. La serialización final vive en los controladores importados.
GET /altcha/challenge
Este endpoint entrega un challenge ALTCHA general. El limitador propio protege el volumen de solicitudes.
{
"title": "GET /api/auth/altcha/challenge",
"description": "Entrega un challenge ALTCHA para el flujo general de autenticaci\u00f3n.",
"method": "GET",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/altcha/challenge",
"headers": [],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Challenge generado.",
"body": "{}"
},
"429": {
"description": "Demasiadas solicitudes de challenges.",
"body": "{\n \"error\": \"Demasiadas solicitudes de challenges. Intenta mas tarde.\"\n}"
}
}
}
POST /register
Este endpoint crea una cuenta nueva. Usa registrationRateLimit y delega el alta a register.
{
"title": "POST /api/auth/register",
"description": "Crea una cuenta nueva y aplica limitaci\u00f3n de registro.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/register",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"201": {
"description": "Cuenta creada.",
"body": "{\n \"token\": \"<accessToken>\",\n \"user\": {}\n}"
},
"429": {
"description": "Demasiados intentos de registro.",
"body": "{\n \"error\": \"Demasiados intentos de registro. Intenta mas tarde.\"\n}"
}
}
}
POST /sign-in
Este endpoint inicia sesión sin un limitador local en este archivo. La lógica real vive en signIn.
{
"title": "POST /api/auth/sign-in",
"description": "Inicia sesi\u00f3n y devuelve la respuesta serializada por el controlador.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/sign-in",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Autenticaci\u00f3n correcta.",
"body": "{\n \"token\": \"<accessToken>\",\n \"user\": {}\n}"
}
}
}
POST /google
Este endpoint autentica o crea cuenta mediante Google ID token. El comentario del archivo aclara que no usa ALTCHA, pero sí registrationRateLimit.
{
"title": "POST /api/auth/google",
"description": "Autentica o crea una cuenta mediante Google ID token verificado en servidor.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/google",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"201": {
"description": "Autenticaci\u00f3n o alta completada.",
"body": "{\n \"token\": \"<accessToken>\",\n \"user\": {}\n}"
},
"429": {
"description": "Demasiados intentos de registro.",
"body": "{\n \"error\": \"Demasiados intentos de registro. Intenta mas tarde.\"\n}"
}
}
}
POST /account-deletion/google-reauth
Este endpoint exige sesión activa y una reautenticación Google de un solo uso. El router aplica requireAuth antes del limitador.
{
"title": "POST /api/auth/account-deletion/google-reauth",
"description": "Emite la reautenticaci\u00f3n Google necesaria para confirmar una eliminaci\u00f3n de cuenta.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/account-deletion/google-reauth",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Reautenticaci\u00f3n emitida.",
"body": "{}"
},
"429": {
"description": "Demasiados intentos de confirmacion con Google.",
"body": "{\n \"error\": \"Demasiados intentos de confirmacion con Google. Intenta mas tarde.\"\n}"
}
}
}
GET /altcha/account-deletion-challenge
Este endpoint entrega un challenge ALTCHA para confirmar la eliminación de la cuenta autenticada. También exige sesión activa.
{
"title": "GET /api/auth/altcha/account-deletion-challenge",
"description": "Entrega un challenge ALTCHA para la eliminaci\u00f3n de cuenta autenticada.",
"method": "GET",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/altcha/account-deletion-challenge",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Challenge generado.",
"body": "{}"
},
"429": {
"description": "Demasiadas solicitudes de challenges.",
"body": "{\n \"error\": \"Demasiadas solicitudes de challenges. Intenta mas tarde.\"\n}"
}
}
}
GET /me
Este endpoint devuelve el usuario autenticado. requireAuth corre antes del controlador y rellena req.auth. fileciteturn0file0turn0file4
{
"title": "GET /api/auth/me",
"description": "Devuelve el perfil autenticado activo.",
"method": "GET",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/me",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Usuario autenticado.",
"body": "{\n \"id\": \"66b1f0a1d8a4e6c3d1234567\",\n \"name\": \"Ana Garc\u00eda\",\n \"firstName\": \"Ana\",\n \"lastName\": \"Garc\u00eda\",\n \"email\": \"[email protected]\",\n \"phone\": \"+34123456789\",\n \"username\": \"ana.garcia\",\n \"role\": \"agent\",\n \"accountRole\": \"creator\",\n \"bio\": \"\",\n \"photo\": \"\",\n \"coverPhoto\": \"\",\n \"website\": \"\",\n \"location\": \"\",\n \"plan\": \"free\",\n \"publicProfileActive\": true,\n \"createdAt\": \"2026-06-25T10:00:00.000Z\",\n \"updatedAt\": \"2026-06-25T10:00:00.000Z\"\n}"
},
"401": {
"description": "Token inv\u00e1lido, expirado o usuario no autenticado.",
"body": "{\n \"message\": \"Token inv\u00e1lido o expirado\"\n}"
}
}
}
PATCH /me
Este endpoint actualiza el perfil autenticado. El router exige sesión antes de delegar en updateMe.
{
"title": "PATCH /api/auth/me",
"description": "Actualiza el perfil del usuario autenticado.",
"method": "PATCH",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/me",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Perfil actualizado.",
"body": "{}"
}
}
}
DELETE /me
Este endpoint elimina la cuenta autenticada. Usa un limitador específico para reducir abuso.
{
"title": "DELETE /api/auth/me",
"description": "Elimina la cuenta autenticada.",
"method": "DELETE",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/me",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Cuenta eliminada.",
"body": "{}"
},
"429": {
"description": "Demasiados intentos de eliminacion.",
"body": "{\n \"error\": \"Demasiados intentos de eliminacion. Intenta mas tarde.\"\n}"
}
}
}
GET /restore-account/altcha-challenge
Este endpoint prepara la restauración de una cuenta pending_deletion. El comentario del archivo aclara que no usa requireAuth, porque la sesión ya no es válida.
{
"title": "GET /api/auth/restore-account/altcha-challenge",
"description": "Entrega un challenge ALTCHA para restaurar una cuenta sin sesi\u00f3n activa.",
"method": "GET",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/restore-account/altcha-challenge",
"headers": [],
"queryParams": [
{
"key": "token",
"value": "Token de restauracion",
"required": true
}
],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Challenge generado para restauraci\u00f3n.",
"body": "{}"
},
"400": {
"description": "Token de restauraci\u00f3n inv\u00e1lido.",
"body": "{\n \"code\": \"INVALID_RESTORATION_TOKEN\"\n}"
},
"429": {
"description": "Demasiados intentos de restauracion.",
"body": "{\n \"error\": \"Demasiados intentos de restauracion. Intenta mas tarde.\"\n}"
}
}
}
POST /restore-account
Este endpoint completa la restauración con un token de propósito y un payload ALTCHA. Las pruebas internas muestran que rechaza tokens desconocidos o expirados.
{
"title": "POST /api/auth/restore-account",
"description": "Restaura una cuenta en pending_deletion usando un token de prop\u00f3sito.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/restore-account",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Cuenta restaurada.",
"body": "{}"
},
"400": {
"description": "Token de restauraci\u00f3n inv\u00e1lido.",
"body": "{\n \"code\": \"INVALID_RESTORATION_TOKEN\"\n}"
},
"429": {
"description": "Demasiados intentos de restauracion.",
"body": "{\n \"error\": \"Demasiados intentos de restauracion. Intenta mas tarde.\"\n}"
}
}
}
POST /account-restoration/resend
Este endpoint reenvía la restauración sin exponer si el correo existe o si la cuenta está activa. El comentario del archivo lo deja explícito.
{
"title": "POST /api/auth/account-restoration/resend",
"description": "Reenv\u00eda el correo o enlace de restauraci\u00f3n sin revelar el estado de la cuenta.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/account-restoration/resend",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud aceptada.",
"body": "{}"
},
"429": {
"description": "Demasiadas solicitudes.",
"body": "{\n \"error\": \"Demasiadas solicitudes. Intenta mas tarde.\"\n}"
}
}
}
GET /account-export
Este endpoint descarga la exportación de cuenta. No usa requireAuth, porque depende de un token de propósito de un solo uso.
{
"title": "GET /api/auth/account-export",
"description": "Descarga la exportaci\u00f3n de cuenta mediante un token de prop\u00f3sito.",
"method": "GET",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/account-export",
"headers": [],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Exportaci\u00f3n descargada.",
"body": ""
},
"429": {
"description": "Demasiadas descargas de exportacion.",
"body": "{\n \"error\": \"Demasiadas descargas de exportacion. Intenta mas tarde.\"\n}"
}
}
}
POST /forgot-password
Este endpoint inicia el flujo de recuperación de contraseña. El controlador mantiene una respuesta neutral y solo expone devToken fuera de producción.
{
"title": "POST /api/auth/forgot-password",
"description": "Inicia la recuperaci\u00f3n de contrase\u00f1a con una respuesta neutral.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/forgot-password",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud aceptada.",
"body": "{}"
}
}
}
POST /reset-password
Este endpoint finaliza el restablecimiento de contraseña. El router no añade requireAuth aquí, porque la prueba de identidad llega por el propio flujo de recuperación.
{
"title": "POST /api/auth/reset-password",
"description": "Completa el restablecimiento de contrase\u00f1a.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/reset-password",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Contrase\u00f1a restablecida.",
"body": "{}"
}
}
}
PATCH /change-password
Este endpoint cambia la contraseña de una cuenta con sesión activa. La documentación interna lo trata como un contrato separado de password/create. fileciteturn0file3turn0file9
{
"title": "PATCH /api/auth/change-password",
"description": "Cambia la contrase\u00f1a de una cuenta autenticada.",
"method": "PATCH",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/change-password",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Contrase\u00f1a cambiada.",
"body": "{}"
}
}
}
POST /password/create/google-reauth
Este endpoint es específico para cuentas Google-only. Exige requireAuth y una reautenticación Google de un solo uso emitida por la ruta hermana.
{
"title": "POST /api/auth/password/create/google-reauth",
"description": "Emite la reautenticaci\u00f3n Google necesaria para crear contrase\u00f1a en una cuenta Google-only.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/password/create/google-reauth",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Reautenticaci\u00f3n emitida.",
"body": "{\n \"googleReauthToken\": \"<token>\"\n}"
},
"429": {
"description": "Demasiados intentos de confirmacion con Google.",
"body": "{\n \"error\": \"Demasiados intentos de confirmacion con Google. Intenta mas tarde.\"\n}"
}
}
}
POST /password/create
Este endpoint crea una contraseña nueva para una cuenta autenticada. El archivo lo separa explícitamente del flujo Google-only.
{
"title": "POST /api/auth/password/create",
"description": "Crea una contrase\u00f1a para una cuenta autenticada.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/password/create",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Contrase\u00f1a creada.",
"body": "{}"
},
"429": {
"description": "Demasiados intentos.",
"body": "{\n \"error\": \"Demasiados intentos. Intenta mas tarde.\"\n}"
}
}
}
POST /verify-email
Este endpoint verifica una dirección de correo sin requerir sesión. El comentario del archivo lo compara con restauración y exportación de cuenta.
{
"title": "POST /api/auth/verify-email",
"description": "Verifica la direcci\u00f3n de correo mediante un token de prop\u00f3sito.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/verify-email",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Correo verificado.",
"body": "{}"
},
"429": {
"description": "Demasiados intentos de verificacion.",
"body": "{\n \"error\": \"Demasiados intentos de verificacion. Intenta mas tarde.\"\n}"
}
}
}
POST /resend-verification
Este endpoint reenvía el correo de verificación usando la identidad de la sesión. El archivo aclara que nunca toma un email del body.
{
"title": "POST /api/auth/resend-verification",
"description": "Reenv\u00eda la verificaci\u00f3n usando la sesi\u00f3n autenticada.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/resend-verification",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud aceptada.",
"body": "{}"
},
"429": {
"description": "Demasiadas solicitudes de verificacion.",
"body": "{\n \"error\": \"Demasiadas solicitudes de verificacion. Intenta mas tarde.\"\n}"
}
}
}
GET /notification-preferences/digest/unsubscribe
Este GET solo valida el token y presenta un formulario de confirmación. El comentario del archivo dice que no debe mutar el estado.
{
"title": "GET /api/auth/notification-preferences/digest/unsubscribe",
"description": "Valida el token de baja y muestra la confirmaci\u00f3n previa.",
"method": "GET",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/notification-preferences/digest/unsubscribe",
"headers": [],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Formulario o p\u00e1gina de confirmaci\u00f3n.",
"body": ""
},
"429": {
"description": "Demasiadas solicitudes.",
"body": "{\n \"error\": \"Demasiadas solicitudes. Intenta mas tarde.\"\n}"
}
}
}
POST /notification-preferences/digest/unsubscribe
Este POST ejecuta la baja real del digest de rendimiento de listings. Comparte el mismo limitador que el GET, pero sí cambia estado.
{
"title": "POST /api/auth/notification-preferences/digest/unsubscribe",
"description": "Desactiva la preferencia del digest de rendimiento de listings.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/notification-preferences/digest/unsubscribe",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Preferencia desactivada.",
"body": "{}"
},
"429": {
"description": "Demasiadas solicitudes.",
"body": "{\n \"error\": \"Demasiadas solicitudes. Intenta mas tarde.\"\n}"
}
}
}
POST /phone/verify/start
Este endpoint inicia la verificación telefónica y dispara un SMS real. El archivo explica que combina límites por IP, por usuario y por teléfono destino.
{
"title": "POST /api/auth/phone/verify/start",
"description": "Inicia la verificaci\u00f3n telef\u00f3nica de una cuenta autenticada.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/phone/verify/start",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud de verificaci\u00f3n iniciada.",
"body": "{}"
},
"429": {
"description": "Demasiadas solicitudes de verificacion.",
"body": "{\n \"error\": \"Demasiadas solicitudes de verificacion. Intenta mas tarde.\"\n}"
}
}
}
POST /phone/verify/check
Este endpoint valida el código ya enviado. El archivo lo protege con límites independientes por usuario e IP y por teléfono destino.
{
"title": "POST /api/auth/phone/verify/check",
"description": "Comprueba el c\u00f3digo de verificaci\u00f3n telef\u00f3nica ya enviado.",
"method": "POST",
"baseUrl": "https://api.example.com",
"endpoint": "/api/auth/phone/verify/check",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "C\u00f3digo verificado.",
"body": "{}"
},
"429": {
"description": "Demasiados intentos de verificacion.",
"body": "{\n \"error\": \"Demasiados intentos de verificacion. Intenta mas tarde.\"\n}"
}
}
}
Workflows
Este diagrama resume el recorrido típico de una petición pública y una autenticada. También muestra dónde entra requireAuth y dónde termina el trabajo del router. fileciteturn0file0turn0file4
sequenceDiagram
participant Client as Cliente
participant Routes as auth routes
participant AuthMw as requireAuth
participant AuthCtl as auth controller
participant PhoneCtl as phone verification controller
Client->>Routes: POST register
Routes->>AuthCtl: register
AuthCtl-->>Client: respuesta JSON
Client->>Routes: GET me
Routes->>AuthMw: requireAuth
AuthMw-->>Routes: req auth listo
Routes->>AuthCtl: me
AuthCtl-->>Client: respuesta JSON
Client->>Routes: POST phone verify start
Routes->>AuthMw: requireAuth
AuthMw-->>Routes: req auth listo
Routes->>PhoneCtl: startPhoneVerificationRequest
PhoneCtl-->>Client: respuesta JSON
El archivo no contiene bucles internos ni lógica de negocio compleja. Su responsabilidad es ordenar el acceso a cada manejador, aplicar seguridad y frenar abuso con límites.
Advertencias y casos límite
[!WARNING]
POST /sign-inno tienerateLimitlocal en este archivo. Si se requiere control de abuso, no vive aquí.
[!WARNING]
POST /phone/verify/startyPOST /phone/verify/checkdependen derequireAuthantes del limitador por usuario. El comentario del archivo dice que elkeyGeneratornecesita leerreq.auth.
[!CAUTION]
GET /restore-account/altcha-challenge,POST /restore-account,GET /account-exportyPOST /verify-emailno usanrequireAuth. El token de propósito es la prueba de identidad en esos flujos.
[!IMPORTANT] El router monta dos verbos sobre
/notification-preferences/digest/unsubscribe. ElGETnunca debe mutar y elPOSTes el único que desactiva la preferencia.
[!NOTE] La ruta
POST /password/create/google-reauthexiste para cuentas Google-only. El comentario del archivo deja claro que la sesión JWT por sí sola no basta para esa operación sensible.
Relación con otros módulos
Este archivo depende de otros módulos del backend para ejecutar la lógica real. El router importa controladores de autenticación y de verificación telefónica, y también usa requireAuth del middleware de auth.
services/api/src/modules/auth/auth.controller.js— aporta los manejadores de todos los endpoints de autenticación y preferencias.services/api/src/modules/auth/phone-verification.controller.js— aportastartPhoneVerificationRequestycheckPhoneVerificationRequest.services/api/src/modules/auth/auth.middleware.js— aportarequireAuth, usado en rutas protegidas.services/api/src/utils/phone.js— aportanormalizePhoneE164yhashPhoneForRateLimit.services/api/src/app.js— monta este router dentro de la API general.
[!IMPORTANT] La ruta real forma parte del conjunto montado bajo
/api. La documentación interna y las pruebas ya la consumen como/api/auth/.... fileciteturn0file10turn0file13