aloux-iam
Librería Node.js para gestión de identidad y acceso (IAM) en APIs Express + MongoDB.
Autenticación
JWT, bcrypt, 2FA TOTP, rate limiting, bloqueo por intentos fallidos.
Control de acceso
Funciones → Permisos por endpoint. Cuentas de servicio con API tokens.
Multi-tenant
Estructura Empresas → Negocios → Usuarios con roles independientes.
Integraciones
AWS S3, SES, SNS · Google BigQuery · Templates de email personalizables.
Instalación y configuración
1. Instalar
npm install aloux-iam
2. Registrar en Express
const express = require('express')
const mongoose = require('mongoose')
const cookieParser = require('cookie-parser')
const { IAMRouter } = require('aloux-iam')
const app = express()
app.use(express.json())
app.use(cookieParser())
app.use(IAMRouter) // monta todos los endpoints /iam/*
mongoose.connect(process.env.MONGO_URI)
app.listen(3000)
3. Usar el middleware en tus propias rutas
const { IAMAuth } = require('aloux-iam')
// Protege cualquier ruta tuya con el mismo middleware
app.get('/api/mi-recurso', IAMAuth, (req, res) => {
// req.user → usuario autenticado con funciones/permisos
// req.token → JWT string
res.json({ user: req.user })
})
permissions que coincida con el método y la ruta de cada endpoint. Si no existe, responde 403. Asegúrate de dar de alta tus endpoints en esa colección.
4. Usar los modelos de Mongoose
const { IAMUserModel, IAMFunctionsModel, IAMPermissionModel, IAMMenuModel } = require('aloux-iam')
// Consulta directa al modelo de usuario
const users = await IAMUserModel.find({ status: 'Activo' })
Variables de entorno
Configura estas variables en tu archivo .env antes de iniciar el servidor.
Autenticación y sesión
| Variable | Requerida | Descripción | Ejemplo |
|---|---|---|---|
AUTH_SECRET | Sí | Clave secreta para firmar y verificar JWT | s3cr3t-muy-largo |
SESSION_TIME | Opc | Duración de sesión en minutos | 30 |
MAX_TOKENS | Opc | Máximo de sesiones activas simultáneas por usuario | 5 |
FAILED_ATTEMPS | Opc | Intentos de login fallidos antes de bloquear la cuenta y activar rate limit. Default: 5 | 5 |
SESSION_INTERRUPTOR | Opc | Si true, valida la expiración de sesión en cada request | true |
MASTER_PWD | Opc | Contraseña maestra para entornos DEBUG (omite bcrypt). No usar en producción. | solo-desarrollo |
DEBUG | Opc | Activa login con MASTER_PWD y URL de Swagger en DEV | true |
Emails y notificaciones
| Variable | Requerida | Descripción | Ejemplo |
|---|---|---|---|
AWS_REGION | Sí* | Región AWS (necesaria para SES/SNS) | us-east-1 |
AWS_EMAIL_SENDER | Sí* | Email verificado en AWS SES usado como remitente | noreply@tuapp.com |
SEND_EMAIL_USER | Opc | Si true, envía email de bienvenida al crear usuario | true |
SUBJECT_EMAIL | Opc | Asunto por defecto para emails de la plataforma | Bienvenido |
TEMPLATE_ACCOUNT | Opc | Path absoluto al template HTML de cuenta nueva | /templates/account.html |
TEMPLATE_RECOVER_PASSWORD | Opc | Path al template HTML para recuperar contraseña | /templates/recover.html |
VERIFY_ACCOUNT_URL | Opc | URL base para verificar cuenta de email | https://tuapp.com/verify |
URL_VERIFY_EMAIL | Opc | URL para confirmar cambio de email | https://tuapp.com/confirm-email |
CHANGE_PWD | Opc | Si true, obliga al usuario a cambiar contraseña en primer login | true |
AWS Storage
| Variable | Descripción | Ejemplo |
|---|---|---|
AWS_BUCKET | Nombre del bucket S3 para archivos (fotos, favicons) | mi-app-bucket |
Google Cloud / BigQuery
| Variable | Descripción | Ejemplo |
|---|---|---|
PROJECT_ID | ID del proyecto en Google Cloud | mi-proyecto-gcp |
DATASET_ID | ID del dataset en BigQuery | mi_dataset |
UPLOAD_CUSTOMER | Si true, sube datos de nuevos clientes a BigQuery | true |
UPLOAD_CUSTOMER_TABLE | Nombre de la tabla destino en BigQuery | mi_tabla |
Historial y observabilidad
| Variable | Descripción | Ejemplo |
|---|---|---|
HISTORY | Si true, registra historial de acciones en los endpoints configurados | true |
HISTORY_ENDPOINTS | Lista separada por comas de paths a registrar | /iam/user,/iam/auth/login |
SWAGGER_SERVER | URL del servidor mostrado en Swagger cuando DEBUG=true | https://api-dev.tuapp.com |
Multi-app y branding
| Variable | Descripción | Ejemplo |
|---|---|---|
APP | Identificador de la aplicación. Permite cargar variables con sufijo, ej. TEMPLATE_ACCOUNT_web | web / mobile |
PROJECT_NAME | Nombre de la app. Se inyecta en templates como +++brandName+++ | Mi App |
BRAND_COLOR | Color principal de marca para templates de email | #6c8ef7 |
BRAND_LOGO | URL del logo para templates de email | https://cdn.tuapp.com/logo.png |
FUNCTION_NAME | Nombre de la función asignada por defecto al crear usuario desde signup | Cliente |
Exportaciones de la librería
| Export | Tipo | Descripción |
|---|---|---|
IAMRouter | Express Router | Router con todos los endpoints /iam/*. Montarlo con app.use(IAMRouter). |
IAMAuth | Middleware | Middleware de autenticación JWT. Usarlo en tus propias rutas privadas. |
IAMSwagger | Object | Objeto OpenAPI/Swagger listo para usar con swagger-ui-express. |
IAMUserModel | Mongoose Model | Modelo User. |
IAMUserBusiness | Mongoose Model | Modelo Business. |
IAMFunctionsModel | Mongoose Model | Modelo Functions. |
IAMPermissionModel | Mongoose Model | Modelo Permission. |
IAMMenuModel | Mongoose Model | Modelo Menu. |
AlouxAWS | Service | Operaciones S3 / SES / SNS. |
AlouxBQ | Service | Operaciones Google BigQuery. |
AlouxHistory | Service | Controlador de historial de acciones. |
Configurar control de acceso
Para proteger tus propios endpoints con aloux-iam, sigue estos pasos:
permissions
Cada endpoint tuyo necesita un documento en MongoDB con el método HTTP y el path exacto de Express.
Una Function actúa como rol. Crea una con POST /iam/functions y agrega el _id del permiso en su array _permissions.
Actualiza el usuario con PATCH /iam/user/:USER_ID agregando el _id de la función en su array _functions.
IAMAuth
Usa el middleware exportado en tu app. El IAM verificará automáticamente que el usuario tenga el permiso.
Ejemplo completo
// 1. Crear el permiso
POST /iam/permission
{
"description": "Ver productos",
"method": "GET",
"api": "productos",
"endpoint": "/api/productos",
"auth": 1,
"status": "Activo"
}
// → { _id: "abc123", ... }
// 2. Crear la función con ese permiso
POST /iam/functions
{
"name": "Catálogo",
"_permissions": ["abc123"],
"_menus": []
}
// → { _id: "fun456", ... }
// 3. Asignar función al usuario
PATCH /iam/user/:USER_ID
{ "_functions": ["fun456"] }
// 4. Tu endpoint protegido
const { IAMAuth } = require('aloux-iam')
app.get('/api/productos', IAMAuth, (req, res) => {
res.json({ productos: [] })
})
Permisos default
Si un permiso tiene "default": true, cualquier usuario autenticado puede acceder al endpoint sin importar sus funciones. Útil para endpoints compartidos como perfil propio, listado de menú, etc.
POST /iam/permission
{
"description": "Ver perfil propio",
"method": "GET",
"api": "perfil",
"endpoint": "/api/mi-perfil",
"auth": 1,
"default": true,
"status": "Activo"
}
Flujo de autenticación
Cada request a un endpoint protegido pasa por el siguiente flujo:
Se busca en el header Authorization: Bearer <token> o en la cookie token. Si no existe → 401.
Se verifica la firma del token con AUTH_SECRET y se extrae el _id del payload.
Se hace User.findOne({ _id, "tokens.token": token, status: "Activo" }) con populate de funciones y permisos. Si no existe → 401.
Si SESSION_INTERRUPTOR=true, verifica token.dateEnd > Date.now(). Si venció, elimina el token y devuelve 401.
Busca un Permission con el método HTTP y el path exacto del endpoint. Si no existe → 403. Si auth=true y no es default, verifica que el usuario tenga acceso.
req.user (usuario completo), req.token (JWT string), req.permission (descripción del permiso).
Respuesta de error estándar
{
"code": 401,
"title": "Error de autenticación",
"detail": "No se encontró el usuario",
"suggestion": "Vuelve a iniciar sesión"
}
Endpoints de autenticación — sin token
/iam/auth/emailVerificar si un email existe · Rate limit activo/iam/auth/loginIniciar sesión · Rate limit activo/iam/auth/logoObtener logo de la empresa por email/iam/auth/forgot/passwordEnviar código de recuperación de contraseña/iam/auth/validate/codeValidar código de recuperación/iam/auth/reset/passwordResetear contraseña con código válido/iam/auth/verify/mailEnviar email de verificación de cuenta/iam/auth/verify/mail/token/:tokenActivar cuenta mediante token de email/iam/auth/signupRegistro de nuevo usuario/iam/generatePasswordGenerar contraseña aleatoria seguraEjemplo: Login
// Request
POST /iam/auth/login
{
"email": "usuario@email.com",
"pwd": "MiContraseña123!"
}
// Response 200 — login exitoso
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"changePwd": false
}
// Response 200 — requiere 2FA
{
"requires2FA": true,
"needsSetup": false,
"tempToken": "eyJ..."
}
Ejemplo: Verificar email
POST /iam/auth/email
{ "email": "usuario@email.com" }
// Response 200 — email existe
{ "email": true, "name": "Juan", "lastName": "Pérez" }
// Response 201 — email nuevo (se creó UserProvisional)
{ "email": false, "name": null, "_id": "..." }
Endpoints de autenticación — con token
Authorization: Bearer <token>/iam/auth/mePerfil del usuario autenticado/iam/auth/profileActualizar perfil/iam/auth/profile/picturaActualizar foto de perfil (sube a S3)/iam/auth/reset/passwordCambiar contraseña estando autenticado/iam/auth/send/verify/phoneEnviar código SMS para verificar teléfono/iam/auth/verify/phoneValidar código de teléfono/iam/auth/logoutCerrar sesión actual/iam/auth/mailIniciar cambio de email/iam/auth/validate/mailConfirmar nuevo email2FA / TOTP
aloux-iam soporta autenticación de dos factores compatible con Google Authenticator, Authy y cualquier app TOTP.
Flujo de activación
GET /iam/totp/setup — Devuelve un código QR (base64) y el secreto TOTP. El usuario lo escanea con su app.
POST /iam/totp/activate con { "token": "123456" } — Valida el primer código TOTP y activa el 2FA en la cuenta.
Flujo de login con 2FA activo
POST /iam/auth/login — Si el usuario tiene 2FA activo, responde { requires2FA: true, tempToken: "..." } en lugar del token real.
POST /iam/totp/verify con { "token": "123456", "tempToken": "..." } — Si el código es correcto, devuelve el JWT real.
Endpoints TOTP
/iam/totp/setupGenera QR y secreto TOTP/iam/totp/activateActiva 2FA tras escanear QR/iam/totp/verifyVerifica código TOTP en el login/iam/user/:USER_ID/totpAdmin habilita/deshabilita 2FA de un usuarioRate limiting
Los endpoints de login están protegidos con un rate limiter in-memory sin dependencias externas.
Endpoints protegidos: POST /iam/auth/email · POST /iam/auth/login
Límite: FAILED_ATTEMPS intentos por IP (default 5)
Ventana: 15 minutos
Respuesta al superar el límite: HTTP 429
// Response 429
{
"code": 429,
"title": "Demasiados intentos",
"detail": "Has superado el límite de intentos permitidos.",
"suggestion": "Espera unos minutos antes de intentarlo nuevamente."
}
Además, si un usuario falla FAILED_ATTEMPS veces su contraseña, su cuenta cambia a estado Bloqueado en la base de datos. Para desbloquearlo se usa la opción de recuperación de contraseña.
Usuarios
/iam/userCrear usuario/iam/userListar todos los usuarios/iam/user/pagesListar con paginación y filtros/iam/user/:USER_IDObtener usuario por ID/iam/user/:USER_IDActualizar usuario/iam/user/:USER_ID/statusCambiar estado/iam/user/password/:USER_IDActualizar contraseña/iam/user/:USER_IDEliminar usuario/iam/user/count/allContar usuarios/iam/business/userUsuarios del negocio actual/iam/user/by/my/companiesUsuarios de mis empresas/iam/add/time/:TOKENExtender duración de una sesiónPaginación (request)
POST /iam/user/pages
{
"config": {
"page": 1,
"itemsPerPage": 10,
"sort": { "createdAt": -1 }
},
"filter": {
"search": "juan" // busca en name y lastName
}
}
// Response
{
"currentPage": 1,
"totalPages": 5,
"perPage": 10,
"count": 48,
"remainingPages": 4,
"items": [ ... ]
}
Tipos de usuario
Tiene email y contraseña. Inicia sesión mediante el flujo /iam/auth/email → /iam/auth/login. El token de sesión expira según SESSION_TIME. Los permisos se asignan a través de Funciones.
Sin email ni contraseña. Diseñada para comunicación entre servicios (machine-to-machine). Recibe un token permanente al crearse. Los permisos se asignan directamente por ID de permiso en data.apis.
Crear una cuenta de servicio
Omite email y pwd en el body. El sistema detecta automáticamente que es una cuenta de servicio y genera el token en la respuesta:
POST /iam/user
Authorization: Bearer <tu-token-admin>
{
"name": "mi-servicio",
"_functions": [],
"_company": ["<COMPANY_ID>"]
}
// Response 201
{
"_id": "64a1...",
"name": "mi-servicio",
"tokens": [
{
"token": "eyJhbGci...", // guárdalo, no se muestra de nuevo
"type": "api"
}
]
}
Usar el token de servicio
Incluye el token en el header Authorization de cada request, igual que un usuario normal:
Authorization: Bearer eyJhbGci...
Asignar permisos a una cuenta de servicio
En lugar de Funciones, las cuentas de servicio usan el campo data.apis: un arreglo de IDs de Permiso a los que tiene acceso. Actualiza el usuario con PATCH /iam/user/:USER_ID:
PATCH /iam/user/<SERVICE_USER_ID>
Authorization: Bearer <tu-token-admin>
{
"data": {
"apis": [
"<PERMISSION_ID_1>",
"<PERMISSION_ID_2>"
]
}
}
Funciones
Las Funciones agrupan conjuntos de Permisos y Menús. Se asignan a usuarios para controlar su acceso.
/iam/functionsCrear función/iam/functionsListar funciones/iam/functions/pagesListar con paginación/iam/functions/:FUNCTION_IDObtener función (incluye permisos y menús)/iam/functions/:FUNCTION_IDActualizar función/iam/functions/:FUNCTION_ID/statusCambiar estado/iam/functions/:FUNCTION_IDEliminar función/iam/functions/count/allContar funcionesPermisos
Cada permiso representa un endpoint específico. El middleware los valida en cada request.
/iam/permissionCrear permiso/iam/permissionListar permisos/iam/permission/pagesListar con paginación y filtros/iam/permission/:PERMISSION_IDObtener permiso/iam/permission/:PERMISSION_IDActualizar permiso/iam/permission/:PERMISSION_ID/statusCambiar estado/iam/permission/:PERMISSION_IDEliminar permiso/iam/permission/count/allContar permisosEstructura de un permiso
{
"description": "Ver lista de usuarios",
"method": "GET",
"api": "user",
"endpoint": "/iam/user",
"auth": 1, // 1 = requiere autenticación
"default": false, // true = accesible sin verificar función
"status": "Activo"
}
permissions. Si el middleware no encuentra el permiso, responde 403.
Empresas (Companies)
/iam/companyCrear empresa/iam/companyListar empresas/iam/company/myMis empresas/iam/company/:COMPANY_IDDetalle de empresa/iam/company/:COMPANY_IDActualizar empresa/iam/company/:COMPANY_ID/pictureActualizar logo/iam/company/:COMPANY_ID/faviconActualizar favicon/iam/company/:ID/identityIdentidad pública (sin auth)/iam/company/:COMPANY_ID/gkeyConfigurar Google Key/iam/company/:COMPANY_ID/gkeyEliminar Google Key/iam/company/:COMPANY_IDEliminar empresaNegocios (Businesses)
Los negocios pertenecen a una empresa y pueden tener su propia Google Key para BigQuery/Storage.
/iam/businessCrear negocio/iam/businessListar negocios/iam/business/pagesListar con paginación/iam/business/companyNegocios de una empresa/iam/business/myMis negocios/iam/business/my/company/:COMPANY_IDMis negocios de una empresa/iam/business/:BUSINESS_IDDetalle del negocio/iam/business/:BUSINESS_IDActualizar negocio/iam/business/:BUSINESS_ID/pictureActualizar logo/iam/business/:BUSINESS_ID/faviconActualizar favicon/iam/business/:ID/identityIdentidad pública (sin auth)/iam/business/:BUSINESS_ID/useCompanyKeyUsar clave de empresa/iam/business/:BUSINESS_ID/inheritKeyHeredar clave de empresa/iam/business/:BUSINESS_IDEliminar negocioEtiquetas (Labels)
/iam/labelCrear etiqueta/iam/labelListar etiquetas/iam/label/:LABEL_IDObtener etiqueta/iam/label/:LABEL_IDActualizar etiqueta/iam/label/:LABEL_ID/statusCambiar estado/iam/label/:LABEL_IDEliminar etiqueta/iam/label/count/allContar etiquetasLogs
Sistema de log para registrar eventos de negocio asociados a usuarios, empresas y negocios.
/iam/logCrear log (asocia automáticamente al usuario autenticado)/iam/log/retrieveRecuperar logs con filtros/iam/log/:LOG_IDObtener log/iam/log/:LOG_IDActualizar log/iam/log/:LOG_ID/statusCambiar estado/iam/log/:LOG_IDEliminar log/iam/log/count/allContar logsHistorial de acciones
Registro automático de requests. Se activa por endpoint mediante variables de entorno.
HISTORY=true y lista de endpoints en HISTORY_ENDPOINTS.
/iam/retrieve/historyListar historial con filtros (método, descripción, usuario, fecha)/iam/history/:HISTORY_IDDetalle de una entrada del historialCada registro contiene: método HTTP, path, payload enviado, respuesta del endpoint, usuario responsable y timestamp.
Modelo: User
Campos principales
Activo · Inactivo · Bloqueadotoken, date, dateEnd, type.changePwd, totp, apis.Modelo: Functions
Activo · InactivoModelo: Permission
GET, POST, PATCH, PUT, DELETE*1 = requiere autenticación. Default: 1.true, cualquier usuario autenticado tiene acceso.Activo · InactivoModelo: Company
Activo · InactivoModelo: Business
dev · qa · prodActivo · InactivoAWS — S3, SES, SNS
Disponible como AlouxAWS en las exportaciones. Requiere credenciales AWS configuradas en el entorno (AWS_REGION, AWS_BUCKET, AWS_EMAIL_SENDER).
S3 — Subir archivos al bucket
El router incluye express-fileupload de forma automática. Desde tus endpoints puedes acceder a los archivos via req.files y pasarlos directamente a AlouxAWS.upload().
const { AlouxAWS } = require('aloux-iam')
app.post('/api/upload', IAMAuth, async (req, res) => {
const file = req.files?.archivo // campo del form-data
if (!file) return res.status(400).send({ error: 'No se recibió archivo' })
// Ruta dentro del bucket (sin slash inicial)
const path = `uploads/${Date.now()}-${file.name}`
const url = await AlouxAWS.upload(path, file)
// url → URL pública del archivo en S3
res.json({ url })
})
S3 — Eliminar archivos
// Eliminar un archivo (pasa la ruta dentro del bucket)
await AlouxAWS.delete('uploads/archivo.jpg')
// Eliminar varios archivos de una sola llamada
await AlouxAWS.deleteMany([
'uploads/foto1.jpg',
'uploads/foto2.png'
])
AlouxAWS.upload() son públicos (ACL: public-read). Úsalo solo para assets que deben ser accesibles sin autenticación (fotos de perfil, logos, etc.).
SES — Enviar email con template
El método send() lee un archivo HTML desde la ruta definida en la variable de entorno indicada, reemplaza los placeholders de marca y lo envía via AWS SES.
const { AlouxAWS } = require('aloux-iam')
// Leer template y enviar
const html = fs.readFileSync(process.env.TEMPLATE_ACCOUNT, 'utf8')
await AlouxAWS.send(
'destinatario@email.com', // para
html, // contenido HTML
'Bienvenido a la plataforma' // asunto
)
SES — Enviar email personalizado
Usa sendCustom() cuando quieras enviar HTML propio sin usar el sistema de templates de la librería.
const html = `
<h1>Hola, </h1>
<p>Tu pedido #12345 fue confirmado.</p>
`
await AlouxAWS.sendCustom(
'cliente@email.com',
html,
'Confirmación de pedido'
)
SNS — Enviar SMS
await AlouxAWS.sendMessagePhone(
'+521234567890', // número en formato E.164
'Tu código es: 4821' // mensaje de texto
)
Templates de email — placeholders
Los archivos HTML de templates pueden usar estos tokens que se reemplazan automáticamente al enviar:
| Placeholder en el HTML | Se reemplaza con | Variable de entorno |
|---|---|---|
+++brandName+++ | Nombre de la aplicación | PROJECT_NAME |
+++brandColor+++ | Color principal de marca | BRAND_COLOR |
+++brandLogo+++ | URL del logo | BRAND_LOGO |
+++user+++ | Nombre del destinatario | — |
Variables de entorno requeridas para AWS
| Variable | Descripción | Ejemplo |
|---|---|---|
AWS_REGION | Región donde están los servicios | us-east-1 |
AWS_BUCKET | Nombre del bucket S3 | mi-app-bucket |
AWS_EMAIL_SENDER | Email verificado en SES usado como remitente | noreply@tuapp.com |
AWS_ACCESS_KEY_ID y AWS_SECRET_ACCESS_KEY) deben configurarse en el entorno del servidor o mediante un IAM Role si estás en EC2/ECS. La librería las toma automáticamente del SDK.
Google BigQuery
Disponible como AlouxBQ.
const { AlouxBQ } = require('aloux-iam')
// Insertar fila en una tabla
await AlouxBQ.callAppendRows({ name: 'Juan', email: 'juan@email.com' }, 'mi_tabla')
// Convertir timestamp a formato BigQuery
const dt = AlouxBQ.factoryDateTime(Date.now()) // "2026-05-25 12:00:00"
UPLOAD_CUSTOMER=true, al crear un usuario desde POST /iam/auth/signup se inserta automáticamente una fila en la tabla UPLOAD_CUSTOMER_TABLE.
Generador de contraseñas
GET /iam/generatePassword?length=16
// Response
{ "password": "aB3!xK9@mN2#pQ7$" }
Garantiza al menos una letra minúscula, una mayúscula, un número y un carácter especial.
Utils
const utils = require('aloux-iam/lib/config/utils')
// Escapa metacaracteres de regex en un string
utils.escapeRegex('hola (mundo)')
// Genera objeto de respuesta paginada
utils.generatePaginationResponse(count, page, itemsPerPage, items)
// Formatea y envía respuesta de error estándar
utils.responseError(res, error)
// Resuelve Google Key del negocio
utils.resolveGkey(business) // → { gkey, source }
Seguridad
Medidas aplicadas en la versión actual:
Headers HTTP
Todos los endpoints devuelven automáticamente:
| Header | Valor |
|---|---|
X-Content-Type-Options | nosniff |
X-Frame-Options | SAMEORIGIN |
X-XSS-Protection | 0 |
Strict-Transport-Security | max-age=15552000; includeSubDomains |
X-Powered-By | Eliminado |
Contraseñas
Hash con bcrypt. Mínimo 8 caracteres. Salting automático en pre-save.
Tokens JWT
Firmados con AUTH_SECRET. Se almacenan en DB para poder invalidarlos.
OTP criptográfico
Códigos de 4 dígitos generados con crypto.randomInt (nativo Node), sin Math.random.
Timing-safe comparison
Comparación de tokens en logout usando crypto.timingSafeEqual.
Regex seguro (ReDoS)
Búsquedas sanitizadas con String() + escapeRegex() antes de usarse en $regex, previniendo patrones maliciosos e inyección de objetos.
Bloqueo de cuenta
Tras FAILED_ATTEMPS intentos fallidos, la cuenta cambia a estado Bloqueado.