services/api/src/modules/auth/auth.controller.js
Overview
Este archivo concentra la capa HTTP del módulo de autenticación. Toma datos desde req.body, req.query y req.auth.userId, valida la entrada cuando corresponde, y delega el trabajo real en auth.service.js, account-export.service.js y ../../config/altcha.js. También fija códigos HTTP, cabeceras de caché y el formato final de la respuesta.
El controlador se monta bajo /api/auth desde services/api/src/app.js, y el router aplica requireAuth en los flujos protegidos. Eso hace que varios handlers reciban req.auth.userId ya resuelto antes de entrar al controlador.
[!NOTE] Este archivo mezcla respuestas JSON, HTML y binarios ZIP. El flujo de cancelación de digest devuelve HTML directo, y la exportación de cuenta responde con un archivo ZIP en memoria.
How it works
flowchart TB
Client[Cliente HTTP] --> Routes[auth routes]
Routes --> Controller[auth.controller.js]
Controller --> Validation[auth.validation.js]
Controller --> AuthService[auth.service.js]
Controller --> Altcha[altcha helpers]
Controller --> ExportSvc[account export service]
Controller --> Errors[ApiError]
Controller --> Json[JSON response]
Controller --> Html[HTML response]
Controller --> Zip[ZIP response]
El flujo general es consistente en casi todos los handlers. Primero se valida el cuerpo o el token recibido. Después se llama a un servicio concreto y se serializa la respuesta final. Los casos especiales son los endpoints de ALTCHA, la exportación de cuenta y la cancelación de digest, porque devuelven challenge, ZIP o HTML.
[!IMPORTANT] El flujo de cancelación de digest tiene dos fases distintas. El GET solo valida el token y muestra un formulario. El POST realiza la mutación real sobre
notificationPreferences.listingPerformanceDigest.
Key components
Este controlador depende de validadores, servicios de dominio y helpers de seguridad. La mayor parte del archivo es orquestación, no lógica de negocio propia.
{
"title": "generateAltchaChallenge",
"content": "Crea el challenge ALTCHA que el controlador devuelve en los endpoints p\u00fablicos y protegidos de challenge.",
"type": "function",
"filePath": "services/api/src/config/altcha.js",
"badges": [
"public"
]
}
{
"title": "verifyAndConsumeAltchaPayload",
"content": "Verifica y consume el payload ALTCHA antes de aceptar registro, eliminaci\u00f3n de cuenta o restauraci\u00f3n.",
"type": "function",
"filePath": "services/api/src/config/altcha.js",
"badges": [
"public"
]
}
{
"title": "verifyAccountExportRequest",
"content": "Valida el token de exportaci\u00f3n y resuelve el usuario al que pertenece la descarga.",
"type": "function",
"filePath": "services/api/src/modules/auth/account-export.service.js",
"badges": [
"public"
]
}
{
"title": "buildAccountExportZip",
"content": "Construye en memoria el ZIP que contiene la exportaci\u00f3n de cuenta.",
"type": "function",
"filePath": "services/api/src/modules/auth/account-export.service.js",
"badges": [
"public"
]
}
{
"title": "ApiError",
"content": "Representa errores HTTP con estado, mensaje y metadatos adicionales.",
"type": "class",
"filePath": "services/api/src/utils/apiError.js",
"badges": [
"public"
]
}
{
"title": "digestUnsubscribeConfirmHtml",
"content": "Genera el HTML de confirmaci\u00f3n que se muestra antes de cancelar Tu Plano Digest.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.controller.js",
"badges": [
"public"
]
}
Validadores usados por el controlador
El archivo también depende de validadores específicos antes de llamar a los servicios. Esos validadores definen la forma final de cada payload de entrada.
validateRegisterInputvalidateSignInInputvalidateGoogleAuthInputvalidateGoogleAccountDeletionReauthInputvalidatePasswordCreateInputvalidateDeleteAccountInputvalidateRestoreAccountInputvalidateAccountRestorationResendInput
Data & fields
Este controlador no define un modelo persistente propio. Solo consume datos de entrada y usa constantes HTML internas para la ruta de cancelación de digest.
[
{
"name": "req.auth.userId",
"type": "string",
"required": true,
"description": "Identifica al usuario autenticado en los handlers protegidos."
},
{
"name": "req.body",
"type": "object",
"required": true,
"description": "Cuerpo de entrada que cada validador normaliza antes de delegar al servicio."
},
{
"name": "req.query.token",
"type": "string",
"description": "Token usado en restauración, exportación y cancelación de digest."
},
{
"name": "altchaPayload",
"type": "string",
"required": true,
"description": "Prueba ALTCHA que se verifica y consume en registro, eliminación y restauración."
},
{
"name": "credential",
"type": "string",
"required": true,
"description": "Credencial de Google enviada por el cliente."
},
{
"name": "email",
"type": "string",
"required": true,
"description": "Correo usado para recuperar contraseña o reenviar restauración."
},
{
"name": "token",
"type": "string",
"required": true,
"description": "Token usado en reset, verificación, restauración, exportación y cancelación."
},
{
"name": "currentPassword",
"type": "string",
"required": true,
"description": "Contraseña actual usada en cambio de contraseña o eliminación de cuenta."
},
{
"name": "newPassword",
"type": "string",
"required": true,
"description": "Nueva contraseña usada en el cambio de contraseña."
},
{
"name": "googleReauthToken",
"type": "string",
"description": "Token de reautenticación de uso único emitido por un flujo de Google."
},
{
"name": "deletionGracePreferences",
"type": "object",
"description": "Preferencias de gracia que se pasan al flujo de eliminación de cuenta."
},
{
"name": "DIGEST_UNSUBSCRIBE_SUCCESS_HTML",
"type": "string",
"required": true,
"description": "HTML devuelto tras cancelar correctamente Tu Plano Digest."
},
{
"name": "DIGEST_UNSUBSCRIBE_ERROR_HTML",
"type": "string",
"required": true,
"description": "HTML devuelto cuando el token de cancelación falta o es inválido."
}
]
Behaviors
altchaChallenge[badge:public] — genera un challenge ALTCHA genérico en JSON y marca la respuesta como no cacheable.accountDeletionAltchaChallenge[badge:public] — genera un challenge ALTCHA ligado aaccount-deletiony al usuario autenticado.register[badge:public] — valida el alta, consumealtchaPayload, crea el usuario y responde201.signIn[badge:public] — valida credenciales y responde200con el resultado del inicio de sesión.googleAuth[badge:public] — validacredentialy delega en el flujo de Google para iniciar sesión o registrar.googleAccountDeletionReauth[badge:public] — emite un reauth de Google de un solo uso para la eliminación de cuenta.me[badge:public] — devuelve el usuario actual usandoreq.auth.userId.updateMe[badge:public] — actualiza el perfil del usuario autenticado y devuelve el usuario actualizado.forgotPassword[badge:public] — solicita un reset de contraseña y devuelve un mensaje neutral.resetPassword[badge:public] — validatokenypassword, cambia la contraseña y confirma la operación.verifyEmailAddress[badge:public] — verifica el correo con un token y devuelve el resultado del servicio.resendVerification[badge:public] — reenvía el correo de verificación usando la identidad de la sesión.changePassword[badge:public] — valida contraseña actual y nueva, exige una longitud mínima y cambia la credencial.googlePasswordCreateReauth[badge:public] — emite un reauth de Google de un solo uso para crear una contraseña local.createPassword[badge:public] — crea una contraseña para una cuenta Google-only.deleteMe[badge:public] — verifica ALTCHA, valida credenciales y solicita la eliminación de la cuenta.restoreAccountAltchaChallenge[badge:public] — valida un token de restauración y genera un challenge ALTCHA ligado al usuario.restoreAccount[badge:public] — valida restauración y ALTCHA, restaura la cuenta y devuelve el resultado.resendAccountRestoration[badge:public] — reenvía el enlace de restauración con una respuesta neutral.downloadAccountExport[badge:public] — valida el token de exportación, genera un ZIP en memoria y lo devuelve como adjunto.confirmListingPerformanceDigestUnsubscribe[badge:public] — valida el token de digest y devuelve el formulario HTML de confirmación.unsubscribeListingPerformanceDigest[badge:public] — procesa el submit del formulario y desactiva sololistingPerformanceDigest.
Workflows
El archivo organiza tres grupos de flujos claros. Todos usan la misma frontera de controlador, pero cambian en autenticación, formato y persistencia.
- Flujos públicos con token:
forgotPassword,resetPassword,restoreAccountAltchaChallenge,restoreAccount,resendAccountRestoration,downloadAccountExportyconfirmListingPerformanceDigestUnsubscribe. - Flujos autenticados:
me,updateMe,changePassword,deleteMe,resendVerification,googleAccountDeletionReauth,googlePasswordCreateReauthycreatePassword. - Flujos HTML o binarios:
downloadAccountExportresponde con ZIP, y los handlers de digest responden con HTML directo.
API
La superficie HTTP de este controlador vive en services/api/src/modules/auth/auth.routes.js y se monta bajo /api/auth. El router agrega requireAuth a los handlers protegidos y deja públicos los flujos basados en token o correo.
GET /auth/altcha/challenge
{
"title": "Generar challenge ALTCHA",
"description": "Crea un challenge ALTCHA gen\u00e9rico y devuelve una respuesta no cacheable.",
"method": "GET",
"baseUrl": "/api",
"endpoint": "/auth/altcha/challenge",
"headers": [],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Challenge generado",
"body": "{\n \"challenge\": \"<altcha challenge>\"\n}"
}
}
}
GET /auth/altcha/account-deletion-challenge
{
"title": "Challenge ALTCHA para eliminaci\u00f3n",
"description": "Genera un challenge ALTCHA ligado al usuario autenticado y al prop\u00f3sito account-deletion.",
"method": "GET",
"baseUrl": "/api",
"endpoint": "/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": "{\n \"challenge\": \"<altcha challenge>\"\n}"
},
"401": {
"description": "Sesi\u00f3n inv\u00e1lida",
"body": "{\n \"message\": \"Token inv\u00e1lido o expirado\"\n}"
}
}
}
POST /auth/register
{
"title": "Registrar usuario",
"description": "Valida el cuerpo, consume el payload ALTCHA y crea una nueva cuenta.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/register",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"altchaPayload\": \"<payload>\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"201": {
"description": "Usuario creado",
"body": "{\n \"token\": \"<jwt>\",\n \"user\": {\n \"_id\": \"<user-id>\",\n \"email\": \"[email protected]\"\n }\n}"
},
"400": {
"description": "Solicitud inv\u00e1lida",
"body": "{\n \"message\": \"Datos de registro inv\u00e1lidos\"\n}"
}
}
}
POST /auth/sign-in
{
"title": "Iniciar sesi\u00f3n",
"description": "Valida credenciales y devuelve el resultado del login.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/sign-in",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"email\": \"[email protected]\",\n \"password\": \"Secret123!\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Sesi\u00f3n iniciada",
"body": "{\n \"token\": \"<jwt>\",\n \"user\": {\n \"_id\": \"<user-id>\",\n \"email\": \"[email protected]\"\n }\n}"
},
"400": {
"description": "Datos inv\u00e1lidos",
"body": "{\n \"message\": \"Credenciales inv\u00e1lidas\"\n}"
}
}
}
POST /auth/google
{
"title": "Autenticaci\u00f3n con Google",
"description": "Valida un credential de Google y delega en el flujo de inicio o registro.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/google",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"credential\": \"<google-id-token>\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Autenticaci\u00f3n completada",
"body": "{\n \"token\": \"<jwt>\",\n \"user\": {\n \"_id\": \"<user-id>\",\n \"email\": \"[email protected]\"\n }\n}"
},
"400": {
"description": "Credential inv\u00e1lido",
"body": "{\n \"message\": \"Credencial de Google inv\u00e1lida\"\n}"
}
}
}
POST /auth/account-deletion/google-reauth
{
"title": "Reauth de Google para eliminaci\u00f3n",
"description": "Emite un token reauth de un solo uso para confirmar una eliminaci\u00f3n de cuenta.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/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": "{\n \"credential\": \"<google-id-token>\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Token reauth emitido",
"body": "{\n \"googleReauthToken\": \"<scoped-token>\"\n}"
},
"401": {
"description": "Sesi\u00f3n inv\u00e1lida",
"body": "{\n \"message\": \"Token inv\u00e1lido o expirado\"\n}"
}
}
}
GET /auth/me
{
"title": "Obtener usuario actual",
"description": "Devuelve el usuario autenticado. La ruta requiere sesi\u00f3n activa.",
"method": "GET",
"baseUrl": "/api",
"endpoint": "/auth/me",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Usuario actual",
"body": "{\n \"_id\": \"<user-id>\",\n \"email\": \"[email protected]\",\n \"name\": \"Ana Garc\u00eda\",\n \"emailVerifiedAt\": null\n}"
},
"401": {
"description": "No autenticado",
"body": "{\n \"message\": \"Token inv\u00e1lido o expirado\"\n}"
}
}
}
PATCH /auth/me
{
"title": "Actualizar perfil actual",
"description": "Actualiza el perfil del usuario autenticado y devuelve el recurso actualizado.",
"method": "PATCH",
"baseUrl": "/api",
"endpoint": "/auth/me",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"name\": \"Ana Garc\u00eda\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Perfil actualizado",
"body": "{\n \"_id\": \"<user-id>\",\n \"email\": \"[email protected]\",\n \"name\": \"Ana Garc\u00eda\"\n}"
},
"401": {
"description": "No autenticado",
"body": "{\n \"message\": \"Token inv\u00e1lido o expirado\"\n}"
}
}
}
DELETE /auth/me
{
"title": "Solicitar eliminaci\u00f3n de cuenta",
"description": "Verifica ALTCHA, valida credenciales y solicita la eliminaci\u00f3n de la cuenta autenticada.",
"method": "DELETE",
"baseUrl": "/api",
"endpoint": "/auth/me",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"altchaPayload\": \"<payload>\",\n \"currentPassword\": \"Secret123!\",\n \"googleReauthToken\": \"<scoped-token>\",\n \"deletionGracePreferences\": {}\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud registrada",
"body": "{\n \"message\": \"La eliminacion de la cuenta fue solicitada.\"\n}"
},
"401": {
"description": "No autenticado",
"body": "{\n \"message\": \"Token inv\u00e1lido o expirado\"\n}"
}
}
}
GET /auth/restore-account/altcha-challenge
{
"title": "Challenge de restauraci\u00f3n",
"description": "Valida el token de restauraci\u00f3n y genera un challenge ALTCHA asociado al usuario restaurable.",
"method": "GET",
"baseUrl": "/api",
"endpoint": "/auth/restore-account/altcha-challenge",
"headers": [],
"queryParams": [
{
"key": "token",
"value": "Token de restauraci\u00f3n",
"required": true
}
],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Challenge generado",
"body": "{\n \"challenge\": \"<altcha challenge>\"\n}"
},
"400": {
"description": "Token ausente o inv\u00e1lido",
"body": "{\n \"message\": \"El enlace de restauracion es invalido o ha expirado\"\n}"
}
}
}
POST /auth/restore-account
{
"title": "Restaurar cuenta",
"description": "Valida el token de restauraci\u00f3n y el payload ALTCHA, y restaura la cuenta.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/restore-account",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"token\": \"<restoration-token>\",\n \"altchaPayload\": \"<payload>\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Cuenta restaurada",
"body": "{\n \"message\": \"Tu cuenta fue restaurada correctamente.\"\n}"
},
"400": {
"description": "Token inv\u00e1lido o expirado",
"body": "{\n \"message\": \"El enlace de restauracion es invalido o ha expirado\"\n}"
}
}
}
POST /auth/account-restoration/resend
{
"title": "Reenviar restauraci\u00f3n",
"description": "Reenv\u00eda el enlace de restauraci\u00f3n con una respuesta neutral que no revela si la cuenta existe.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/account-restoration/resend",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"email\": \"[email protected]\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud aceptada",
"body": "{\n \"message\": \"Si la cuenta existe y est\u00e1 en proceso de eliminaci\u00f3n, enviamos un nuevo enlace de restauraci\u00f3n.\"\n}"
},
"400": {
"description": "Correo faltante",
"body": "{\n \"message\": \"El correo es requerido\"\n}"
}
}
}
GET /auth/account-export
{
"title": "Descargar exportaci\u00f3n de cuenta",
"description": "Verifica el token de exportaci\u00f3n y devuelve un ZIP generado en memoria. No persiste archivos.",
"method": "GET",
"baseUrl": "/api",
"endpoint": "/auth/account-export",
"headers": [],
"queryParams": [
{
"key": "token",
"value": "Token de exportaci\u00f3n",
"required": true
}
],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "ZIP descargable",
"body": "<binary ZIP attachment>"
},
"401": {
"description": "Token inv\u00e1lido o expirado",
"body": "{\n \"message\": \"El enlace de exportacion no es valido o expiro\"\n}"
}
}
}
POST /auth/forgot-password
{
"title": "Solicitar restablecimiento de contrase\u00f1a",
"description": "Inicia el flujo de recuperaci\u00f3n. Devuelve un mensaje neutral y, fuera de producci\u00f3n, puede incluir devToken.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/forgot-password",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"email\": \"[email protected]\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud aceptada",
"body": "{\n \"message\": \"Si el correo existe, recibir\u00e1s instrucciones para recuperar tu contrase\u00f1a.\",\n \"devToken\": \"<only-in-non-production>\"\n}"
},
"400": {
"description": "Correo faltante",
"body": "{\n \"message\": \"El correo es requerido\"\n}"
}
}
}
POST /auth/reset-password
{
"title": "Restablecer contrase\u00f1a",
"description": "Valida el token de recuperaci\u00f3n y reemplaza la contrase\u00f1a.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/reset-password",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"token\": \"<reset-token>\",\n \"password\": \"Secret123!\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Contrase\u00f1a actualizada",
"body": "{\n \"message\": \"Contrase\u00f1a actualizada correctamente\"\n}"
},
"400": {
"description": "Token o contrase\u00f1a faltantes",
"body": "{\n \"message\": \"Token y contrase\u00f1a son requeridos\"\n}"
}
}
}
PATCH /auth/change-password
{
"title": "Cambiar contrase\u00f1a",
"description": "Requiere sesi\u00f3n activa y cambia la contrase\u00f1a tras validar la actual y la nueva.",
"method": "PATCH",
"baseUrl": "/api",
"endpoint": "/auth/change-password",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"currentPassword\": \"Secret123!\",\n \"newPassword\": \"Secret456!\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Contrase\u00f1a actualizada",
"body": "{\n \"message\": \"Contrase\u00f1a actualizada correctamente\"\n}"
},
"400": {
"description": "Datos insuficientes o contrase\u00f1a demasiado corta",
"body": "{\n \"message\": \"La contrase\u00f1a actual y la nueva son requeridas\"\n}"
}
}
}
POST /auth/password/create/google-reauth
{
"title": "Reauth para crear contrase\u00f1a",
"description": "Emite un reauth de un solo uso para crear una contrase\u00f1a local en una cuenta Google-only.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/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": "{\n \"credential\": \"<google-id-token>\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Token reauth emitido",
"body": "{\n \"googleReauthToken\": \"<scoped-token>\"\n}"
},
"401": {
"description": "Sesi\u00f3n inv\u00e1lida",
"body": "{\n \"message\": \"Token inv\u00e1lido o expirado\"\n}"
}
}
}
POST /auth/password/create
{
"title": "Crear contrase\u00f1a local",
"description": "Crea una contrase\u00f1a para una cuenta Google-only usando la sesi\u00f3n activa y el reauth previo.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/password/create",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"googleReauthToken\": \"<scoped-token>\",\n \"password\": \"Secret123!\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Contrase\u00f1a creada",
"body": "{\n \"message\": \"Contrase\u00f1a creada correctamente\"\n}"
},
"400": {
"description": "Datos inv\u00e1lidos",
"body": "{\n \"message\": \"Datos de contrase\u00f1a inv\u00e1lidos\"\n}"
}
}
}
POST /auth/verify-email
{
"title": "Verificar correo",
"description": "Verifica un token de email y devuelve el resultado de la verificaci\u00f3n.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/verify-email",
"headers": [
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"token\": \"<verification-token>\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Correo verificado",
"body": "{\n \"message\": \"Correo verificado\"\n}"
},
"400": {
"description": "Token faltante",
"body": "{\n \"message\": \"El token de verificacion es obligatorio\"\n}"
}
}
}
POST /auth/resend-verification
{
"title": "Reenviar verificaci\u00f3n",
"description": "Reenv\u00eda el correo de verificaci\u00f3n usando la identidad de la sesi\u00f3n.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/resend-verification",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud aceptada",
"body": "{\n \"message\": \"Te enviamos un nuevo enlace de verificaci\u00f3n.\"\n}"
},
"401": {
"description": "Sesi\u00f3n inv\u00e1lida",
"body": "{\n \"message\": \"Token inv\u00e1lido o expirado\"\n}"
}
}
}
GET /auth/notification-preferences/digest/unsubscribe
{
"title": "Confirmar cancelaci\u00f3n de digest",
"description": "Valida el token y devuelve una p\u00e1gina HTML de confirmaci\u00f3n. No muta datos en GET.",
"method": "GET",
"baseUrl": "/api",
"endpoint": "/auth/notification-preferences/digest/unsubscribe",
"headers": [],
"queryParams": [
{
"key": "token",
"value": "Token de cancelaci\u00f3n de digest",
"required": true
}
],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Formulario de confirmaci\u00f3n",
"body": "<html>...formulario de confirmaci\u00f3n...</html>"
},
"400": {
"description": "Token faltante o inv\u00e1lido",
"body": "<html>...enlace no v\u00e1lido...</html>"
}
}
}
POST /auth/notification-preferences/digest/unsubscribe
{
"title": "Cancelar Tu Plano Digest",
"description": "Procesa el submit del formulario y desactiva solo notificationPreferences.listingPerformanceDigest.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/auth/notification-preferences/digest/unsubscribe",
"headers": [
{
"key": "Content-Type",
"value": "application/x-www-form-urlencoded",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "form",
"requestBody": "",
"formData": [
{
"key": "token",
"value": "Token de cancelaci\u00f3n de digest",
"required": true
}
],
"rawBody": "",
"responses": {
"200": {
"description": "Preferencia desactivada",
"body": "<html>...confirmaci\u00f3n de cancelaci\u00f3n...</html>"
},
"400": {
"description": "Token faltante o inv\u00e1lido",
"body": "<html>...enlace no v\u00e1lido...</html>"
}
}
}
Gotchas & edge cases
[!WARNING]
forgotPasswordno revela si el correo existe. En producción devuelve solo el mensaje neutral. Fuera de producción, puede añadirdevToken.
[!CAUTION]
changePasswordrechazanewPasswordcon menos de 8 caracteres antes de llamar al servicio. Esa validación ocurre en el controlador, no en el router.
[!IMPORTANT]
confirmListingPerformanceDigestUnsubscribeno muta la base de datos. La mutación real solo ocurre enunsubscribeListingPerformanceDigest.
[!NOTE]
downloadAccountExportusa un token de un solo propósito enreq.query.token. El ZIP se arma en memoria y se devuelve comosomos-plano-export.zip.
Related
services/api/src/modules/auth/auth.routes.jsmonta todos los handlers de este archivo bajo/api/authy aplicarequireAuthen los flujos que lo necesitan.services/api/src/modules/auth/auth.validation.jsnormaliza los payloads de registro, login, Google, reauth, creación de contraseña, eliminación, restauración y reenvío.services/api/src/modules/auth/auth.service.jsconcentra la lógica de negocio que este controlador invoca.services/api/src/modules/auth/account-export.service.jsverifica el token de exportación y construye el ZIP en memoria.services/api/src/config/altcha.jsgenera y consume los challenges usados por registro, eliminación y restauración.services/api/src/utils/apiError.jsprovee los errores HTTP que el controlador lanza cuando faltan campos o tokens.