Servicio de autenticación y lifecycle de cuenta
services/api/src/modules/auth/auth.service.js concentra la lógica central de identidad de usuario. Este archivo crea sesiones, verifica credenciales, gestiona Google Identity Services y coordina cambios sensibles de cuenta. También prepara datos seguros para la capa HTTP y dispara eventos de notificación después de persistir cambios. El controlador services/api/src/modules/auth/auth.controller.js consume estas funciones directamente .
Overview
Este servicio cubre el ciclo completo de autenticación y parte del lifecycle de la cuenta. Incluye registro local, inicio de sesión, Google auth, verificación de correo, restablecimiento de contraseña, eliminación diferida, restauración y preferencias de digest. También expone la forma sanitizada del usuario que el frontend consume para sesión y perfil.
[!IMPORTANT] Este archivo no define rutas HTTP.
auth.controller.jses quien valida el request y llama a estas funciones .
[!NOTE] Las operaciones sensibles usan tokens de corta vida o de un solo uso. El código también aplica controles anti-enumeración en varios flujos.
How it works
La estructura sigue una idea simple. Las funciones leen el estado del User, validan reglas de negocio y luego escriben cambios. Cuando hay efectos secundarios, primero se persiste el cambio principal y después se emiten notificaciones.
flowchart TD
Controller[auth controller] --> Service[auth service]
Service --> UserModel[User]
Service --> ListingModel[Listing]
Service --> NotificationEvents[notification events]
Service --> JwtUtils[jwt utils]
Service --> PasswordUtils[password utils]
Service --> EmailUtils[email verification utils]
Service --> GoogleClient[google identity client]
Service --> AccountExport[account export service]
Service --> PhoneUtils[phone utils]
Service --> ApiError[ApiError]
subgraph CoreFlows[Core flows]
registerUserFlow[registerUser]
signInUserFlow[signInUser]
googleFlow[signInOrRegisterWithGoogle]
verificationFlow[verifyEmail]
passwordFlow[requestPasswordReset resetUserPassword changeUserPassword createPasswordForGoogleOnlyUser]
lifecycleFlow[requestAccountDeletion restoreUserAccount resendAccountRestorationLink]
end
registerUserFlow --> UserModel
registerUserFlow --> NotificationEvents
signInUserFlow --> UserModel
signInUserFlow --> JwtUtils
googleFlow --> GoogleClient
googleFlow --> UserModel
googleFlow --> NotificationEvents
verificationFlow --> EmailUtils
verificationFlow --> NotificationEvents
passwordFlow --> PasswordUtils
passwordFlow --> NotificationEvents
lifecycleFlow --> ListingModel
lifecycleFlow --> AccountExport
lifecycleFlow --> NotificationEvents
El archivo separa la lógica por dominios. Unos métodos crean o validan sesiones. Otros manejan correo, contraseña, Google reauth y protección de listings. Esa separación coincide con el uso del controlador y de las pruebas del módulo .
Key components
Este archivo combina helpers internos y funciones exportadas. Las exportadas forman la API interna del módulo. Los helpers internos sostienen la normalización, la verificación y el control atómico.
{
"title": "sanitizeUser",
"content": "Convierte un documento de User en una respuesta segura. Oculta credenciales y expone campos listos para sesi\u00f3n y perfil.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.service.js",
"badges": [
"public"
]
}
{
"title": "registerUser",
"content": "Crea una cuenta local, valida duplicados, guarda passwordHash, emite verificacion de correo y devuelve token mas usuario sanitizado.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.service.js",
"badges": [
"public"
]
}
{
"title": "signInOrRegisterWithGoogle",
"content": "Verifica el ID token de Google, enlaza cuentas cuando corresponde y emite una sesion para la cuenta resultante.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.service.js",
"badges": [
"public"
]
}
{
"title": "requestAccountDeletion",
"content": "Mueve la cuenta a pending_deletion, protege listings publicados, programa restauracion y emite el evento de eliminacion.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.service.js",
"badges": [
"public"
]
}
{
"title": "restoreUserAccount",
"content": "Revierte una cuenta protegida a active y restaura solo los listings que entraron en protection en esa misma solicitud.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.service.js",
"badges": [
"public"
]
}
{
"title": "buildDigestUnsubscribeUrl",
"content": "Firma un token scoped para cancelar el digest y devuelve una URL absoluta hacia la API.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.service.js",
"badges": [
"public"
]
}
Internal helpers
Estos helpers no se exportan. Todos sirven para mantener reglas repetidas dentro del mismo archivo.
normalizeUsername— Recorta, elimina@iniciales y convierte el valor a minúsculas.assertValidUsername— Exige entre 3 y 30 caracteres, con letras, números, punto o guion bajo.registrationConflict— Construye unApiError409 para correo, teléfono o usuario duplicados.duplicateFieldFromMongoError— Detecta la clave duplicada en errores MongoDB11000.issueEmailVerification— Persiste el hash del token y dispara el evento de verificación.deriveNameParts— Deriva nombre y apellido desde claims de Google.rejectNonActiveAccount— Normaliza errores de Google auth para cuentas no activas.googleIdentityFingerprint— Firma la identidad Google del usuario autenticado.issueGoogleReauthToken— Emite un token de reauth breve y de un solo uso.verifyGoogleReauth— Valida el token, el propósito y eljticonsumible.parseDigestUnsubscribeToken— Valida el token scoped de cancelación de digest.invalidGoogleReauth— Devuelve el error uniforme para reauth inválido.
Data & fields
La salida principal del archivo es el objeto de usuario sanitizado. Esa forma se reutiliza en login, registro, sesión actual y Google auth. También hay campos de estado que controlan lifecycle, verificación y suscripción.
[!IMPORTANT]
publicProfileActivees informativo.canAttachSourceDocumenttambién es informativo. Ninguno reemplaza una decisión de autorización real.
[
{
"name": "sanitizeUser(userDoc)",
"type": "object",
"required": true,
"description": "Forma segura devuelta por este servicio para sesión y perfil.",
"children": [
{
"name": "id",
"type": "string",
"required": true,
"description": "String de _id."
},
{
"name": "name",
"type": "string",
"required": true,
"description": "Nombre visible del usuario."
},
{
"name": "firstName",
"type": "string",
"required": true,
"description": "Primer nombre, o cadena vacía."
},
{
"name": "lastName",
"type": "string",
"required": true,
"description": "Apellido, o cadena vacía."
},
{
"name": "email",
"type": "string",
"required": true,
"description": "Correo principal de la cuenta."
},
{
"name": "phone",
"type": "string",
"required": true,
"description": "Teléfono normalizado, o cadena vacía."
},
{
"name": "username",
"type": "string",
"required": true,
"description": "Nombre público, o cadena vacía."
},
{
"name": "systemRole",
"type": "string",
"required": true,
"description": "Rol de sistema, con default user."
},
{
"name": "accountRole",
"type": "string",
"required": true,
"description": "Rol funcional, con default creator."
},
{
"name": "bio",
"type": "string",
"required": true,
"description": "Biografía pública."
},
{
"name": "photo",
"type": "string",
"required": true,
"description": "Foto de perfil."
},
{
"name": "coverPhoto",
"type": "string",
"required": true,
"description": "Imagen de portada."
},
{
"name": "website",
"type": "string",
"required": true,
"description": "Sitio web público."
},
{
"name": "location",
"type": "string",
"required": true,
"description": "Ubicación pública."
},
{
"name": "plan",
"type": "string",
"required": true,
"description": "Plan visible, con default free."
},
{
"name": "emailVerifiedAt",
"type": "date or null",
"required": true,
"description": "Marca de verificación de correo."
},
{
"name": "phoneVerifiedAt",
"type": "date or null",
"required": true,
"description": "Marca de verificación de teléfono."
},
{
"name": "subscriptionStatus",
"type": "string",
"required": true,
"description": "Estado de suscripción, con default none."
},
{
"name": "currentPeriodEnd",
"type": "date or null",
"required": true,
"description": "Fin del periodo actual."
},
{
"name": "cancelAtPeriodEnd",
"type": "boolean",
"required": true,
"description": "Si la suscripción se cancela al final del periodo."
},
{
"name": "hasStripeCustomer",
"type": "boolean",
"required": true,
"description": "Indica si existe stripeCustomerId."
},
{
"name": "hasStripeSubscription",
"type": "boolean",
"required": true,
"description": "Indica si existe stripeSubscriptionId."
},
{
"name": "hasPassword",
"type": "boolean",
"required": true,
"description": "Indica si existe passwordHash."
},
{
"name": "hasGoogleAccount",
"type": "boolean",
"required": true,
"description": "Indica si existe google.sub."
},
{
"name": "publicProfileActive",
"type": "boolean",
"required": true,
"description": "Verdadero solo para plan creator o enterprise con username."
},
{
"name": "canAttachSourceDocument",
"type": "boolean",
"required": true,
"description": "Capacidad derivada desde canAttachSourceDocument."
},
{
"name": "createdAt",
"type": "date",
"required": true,
"description": "Fecha de creación."
},
{
"name": "updatedAt",
"type": "date",
"required": true,
"description": "Fecha de última actualización."
}
]
},
{
"name": "registrationConflict(field)",
"type": "object",
"required": true,
"description": "ApiError 409 devuelto por conflictos de registro.",
"children": [
{
"name": "code",
"type": "string",
"required": true,
"description": "EMAIL_ALREADY_EXISTS, PHONE_ALREADY_EXISTS o USERNAME_ALREADY_EXISTS."
},
{
"name": "field",
"type": "string",
"required": true,
"description": "Campo que provocó el conflicto."
}
]
}
]
Constants and thresholds
Estos valores controlan seguridad, tiempo de vida y lifecycle. El archivo los usa para evitar duplicados, limitar reintentos y mantener tokens acotados.
REGISTRATION_CONFLICTS— Define mensajes y códigos paraemail,phoneyusername.RESEND_VERIFICATION_COOLDOWN_MS— Fija un enfriamiento de 60 segundos.ACTIVE_SUBSCRIPTION_STATUSES— Bloquea eliminación cuando la suscripción sigue activa o pendiente.DEFAULT_DELETION_GRACE_PREFERENCES— Conserva publicaciones visibles y habilita lead capture y comentarios.THIRTY_DAYS_MS— Marca el inicio de la anonimización programada.SIXTY_DAYS_MS— Marca el purge programado.GOOGLE_ACCOUNT_DELETION_REAUTH_PURPOSE— Sello scoped para eliminar cuenta.GOOGLE_PASSWORD_CREATE_REAUTH_PURPOSE— Sello scoped para crear contraseña.GOOGLE_REAUTH_TTL_SECONDS— Vida máxima del token de reauth de Google.GOOGLE_CREDENTIAL_MAX_AGE_MS— Recencia máxima aceptada para la credencial Google.GOOGLE_CREDENTIAL_FUTURE_TOLERANCE_MS— Tolerancia mínima para reloj adelantado.DIGEST_UNSUBSCRIBE_TOKEN_PURPOSE— Propósito scoped de cancelación de digest.DIGEST_UNSUBSCRIBE_TOKEN_TTL_SECONDS— Vida larga de dos años para el enlace de digest.EMAIL_VERIFICATION_TOKEN_TTL_MS— TTL importado para el token de verificación de correo.
Behaviors
Las funciones exportadas forman la superficie operativa del módulo. El controlador las invoca para responder a las rutas de auth. Cada una devuelve una forma concreta y predecible.
Session and identity
registerUser[badge:public] — Normaliza teléfono y usuario, valida duplicados, cifra la contraseña, crea elUser, emite verificación y devuelve{ token, user }.signInUser[badge:public] — Busca por correo, exigepasswordHash, compara contraseña, bloqueapending_deletiony devuelve{ token, user }.signInOrRegisterWithGoogle[badge:public] — Verifica el ID token, enlaza porgoogle.sub, crea una cuenta nueva si no existe y devuelve la sesión resultante. El flujo usaverifyGoogleIdToken, y la cuenta nueva puede recibiremailVerifiedAtde inmediato.getCurrentUser[badge:public] — Carga el usuario por id y rechaza cualquier cuenta que no estéactive.
Profile and password
updateUserProfile[badge:public] — Permitename,username,bio,photo,coverPhoto,website,locationyseo. También normalizausernamey rechaza duplicados. No aceptaplan.changeUserPassword[badge:public] — Verifica la contraseña actual y reemplazapasswordHash.createPasswordForGoogleOnlyUser[badge:public] — Crea la primera contraseña de una cuenta Google-only. Requiere ungoogleReauthTokenreciente y de un solo uso.createGoogleAccountDeletionReauth[badge:public] — Emite un reauth scoped para eliminación, solo en cuentas Google-only.createGooglePasswordCreateReauth[badge:public] — Emite un reauth scoped para crear contraseña, solo en cuentas Google-only.
Verification and recovery
verifyEmail[badge:public] — Verifica el token hash, marcaemailVerifiedAt, limpia el token y emiteWELCOME.resendEmailVerification[badge:public] — Reenvía la verificación solo para cuentas activas y respeta el cooldown de 60 segundos.maybeSendVerificationReminder[badge:public] — Intenta reemitir la verificación de forma silenciosa. Nunca debe afectar al caller.requestPasswordReset[badge:public] — Crea un token aleatorio, guarda expiración de una hora y emite el correo de reseteo.resetUserPassword[badge:public] — Valida token y expiración, actualiza la contraseña y limpia los campos de recuperación.
Account lifecycle
requestAccountDeletion[badge:public] — Verifica identidad por contraseña o reauth Google, bloquea suscripciones activas, mueve la cuenta apending_deletion, protege listings publicados, incrementatokenVersiony emiteACCOUNT_DELETION_REQUESTED.findUserByValidRestorationToken[badge:public] — Busca una cuentapending_deletioncon token vigente sin consumirlo.restoreUserAccount[badge:public] — Reactiva la cuenta y restaura solo los listings que quedaron enlifecycle.status === 'protection'.resendAccountRestorationLink[badge:public] — Reemplaza el token de restauración para cuentaspending_deletiony reenvía el enlace.
Digest preferences
buildDigestUnsubscribeUrl[badge:public] — Firma un token scoped conpurpose = digest-unsubscribey construye una URL de API.checkDigestUnsubscribeToken[badge:public] — Solo valida el token. No modifica datos.unsubscribeFromListingPerformanceDigest[badge:public] — Apaga únicamentenotificationPreferences.listingPerformanceDigest.
Workflows
Estos flujos muestran el recorrido real de las operaciones principales. Cada uno combina validación, persistencia y notificación. Las acciones sensibles se ejecutan con una sola escritura o con consumo atómico.
1. Registro local | `registerUser` normaliza datos, evita duplicados, crea la cuenta y emite verificación.
2. Inicio de sesión local | `signInUser` valida correo y contraseña, luego emite una nueva sesión.
3. Google auth | `signInOrRegisterWithGoogle` enlaza o crea la cuenta y devuelve sesión.
4. Verificación de correo | `verifyEmail` confirma el token, activa el correo y dispara `WELCOME`.
5. Eliminación de cuenta | `requestAccountDeletion` mueve la cuenta a `pending_deletion` y protege listings publicados.
6. Restauración de cuenta | `restoreUserAccount` vuelve a `active` y restaura listings protegidos.
7. Digest unsubscribe | `buildDigestUnsubscribeUrl` genera el enlace y `unsubscribeFromListingPerformanceDigest` aplica la mutación.
El archivo usa notificaciones como efecto secundario. Esa entrega ocurre después de persistir el estado principal. Por eso, un fallo de notificación no revierte la operación de dominio .
Gotchas & edge cases
[!WARNING]
normalizeUsernameejecuta la limpieza antes de validar. Un valor como@Usuariotermina comousuario.
[!WARNING]
signInUserysignInOrRegisterWithGoogleno revelan estados internos de cuentas no activas. Usan errores genéricos para reducir enumeración.
[!CAUTION]
createPasswordForGoogleOnlyUseryrequestAccountDeletiondependen degoogleReauthTokenJti. Un reauth nuevo invalida los anteriores.
[!WARNING]
requestAccountDeletionbloquea la operación si existe una suscripción activa. La lista de estados bloqueantes está fija enACTIVE_SUBSCRIPTION_STATUSES.
[!IMPORTANT]
restoreUserAccountsolo revierte listings que ya estaban enprotection. No reactiva borradores ni archivados.
[!NOTE]
checkDigestUnsubscribeTokenno modifica la base de datos. La mutación real vive enunsubscribeFromListingPerformanceDigest.
[!NOTE]
maybeSendVerificationReminderes best-effort. Nunca debe interrumpir una respuesta 403 ya emitida por otro guard.
Related
Este archivo se conecta con varios módulos concretos del backend. El controlador importa casi toda su superficie pública desde aquí . Las pruebas del repositorio ejercitan verificación, resends, password reset, eliminación y restauración alrededor de estas funciones .
services/api/src/modules/auth/auth.controller.js— Llama a estas funciones y devuelve respuestas HTTP.services/api/src/modules/auth/google-identity.client.js— Verifica los ID tokens de Google.services/api/src/modules/auth/account-export.service.js— Construye la URL de exportación usada en eliminación.services/api/src/modules/listings/source-documents.js— AportacanAttachSourceDocumentparasanitizeUser.services/api/src/modules/notifications/notification.events.js— Recibe eventos comoWELCOMEyACCOUNT_DELETION_REQUESTED.services/api/src/models/User.js— Persiste identidad, lifecycle y preferencias.services/api/src/models/Listing.js— Recibe actualizaciones de protección y restauración.services/api/src/utils/passwords.js— AportahashPasswordycomparePassword.services/api/src/utils/jwt.js— Firma y verifica tokens de acceso y tokens scoped.services/api/src/utils/emailVerification.js— Genera y hashea tokens de verificación.services/api/src/utils/phone.js— Normaliza teléfonos a formato E.164.services/api/src/config/env.js— ProveeappUrlyapiUrlpara enlaces absolutos.
[!NOTE] El frontend depende de la forma exacta devuelta por
sanitizeUser. El contrato de campos se usa como referencia de sesión en pruebas de UI relacionadas con autenticación.