Docs

Seguridad y Autenticacion

Tabla de Contenidos

  1. Autenticacion con Clerk
  2. Autenticacion Movil (nvito-client)
  3. Cadena de Guards
  4. Sistema RBAC
  5. Decoradores de Seguridad
  6. Seguridad HTTP
  7. Aislamiento de Datos (Multi-Tenancy)
  8. Manejo de Secretos
  9. Seguridad en nvito-admin (Frontend)

1. Autenticacion con Clerk

Nvito delega la autenticacion de usuarios a Clerk, un servicio externo de identidad. El frontend obtiene un JWT a traves del Clerk SDK y lo envia en cada request al API como Bearer token en el header Authorization.

Flujo de Verificacion del JWT

El ClerkAuthGuard es el guard global principal que intercepta todas las peticiones (excepto las marcadas con @Public()). Su proceso es:

  1. Extraccion del token: Se obtiene el JWT del header Authorization eliminando el prefijo Bearer .
  2. Verificacion con Clerk: authService.verifyJWT(token) valida firma y expiracion contra las claves publicas de Clerk.
  3. Busqueda del usuario en BD: Con el sub (clerkUserId) del token decodificado, se busca el usuario en la tabla users.
  4. Validacion de seguridad: Se verifica que el usuario no este eliminado (deletedAt === null) y que este activo (isActive === true).
  5. Resolucion de Super Admin: Se consulta user_roles buscando role=SUPER_ADMIN, scope=GLOBAL, isActive=true (sin expirar).
  6. Resolucion de Platform Admin: Si no es Super Admin, se busca role=PLATFORM_ADMIN, scope=GLOBAL, isActive=true. Si existe, se obtienen las organizaciones asignadas (roles con scope=ORGANIZATION) y se lee el header X-Organization-Id para la org activa.
  7. Resolucion de contexto organizacional: Si no es Super Admin ni Platform Admin, se busca el rol mas reciente en user_roles con scope=ORGANIZATION.
  8. Construccion del AuthenticatedUser: Se arma el objeto con userId, clerkUserId, organizationId, role, email, isSuperAdmin, isPlatformAdmin, assignedOrganizationIds, activeOrganizationId y claims.

Throttling de lastLoginAt

Para evitar escrituras excesivas a la BD, la actualizacion de lastLoginAt usa un throttle en memoria:

  • Un Map<string, number> registra el timestamp de la ultima actualizacion por usuario.
  • Solo se actualiza si han pasado mas de 5 minutos (LOGIN_UPDATE_INTERVAL_MS = 300000ms).
  • La actualizacion es fire-and-forget: los errores se loguean pero no bloquean la autenticacion.

Diagrama de Secuencia: Flujo Completo de Autenticacion

Interface AuthenticatedUser

CampoTipoDescripcion
userIdstringUUID interno del usuario en la BD
clerkUserIdstringID del usuario en Clerk
organizationIdstring | nullUUID de la organizacion (null para SA/PA)
role'owner' | 'admin' | 'member' | 'super_admin' | 'platform_admin'Rol simplificado del usuario
emailstringEmail del usuario
isSuperAdminbooleanIndicador rapido de Super Admin
isPlatformAdminbooleanIndicador rapido de Platform Admin
superAdminIdstring?ID del registro en user_roles (si aplica)
assignedOrganizationIdsstring[]?Orgs donde PA puede escribir
activeOrganizationIdstring | null?Org activa desde header X-Organization-Id
claimsRecord<string, any>?Claims adicionales del JWT

2. Autenticacion Movil (nvito-client)

La app movil utiliza un sistema de autenticacion independiente de Clerk, diseƱado para permitir que tanto anfitriones como invitados accedan sin necesidad de crear una cuenta. El sistema usa JWT firmado con un secreto dedicado (MOBILE_JWT_SECRET).

Diferencias con Autenticacion Web (Clerk)

AspectoWeb (Clerk)Movil (Mobile Auth)
ProveedorClerk (servicio externo)Sistema propio (MobileAuthModule)
JWT SecretClaves publicas de ClerkMOBILE_JWT_SECRET (simetrico)
GuardClerkAuthGuardMobileAuthGuard
IdentidaduserId (tabla users)userId (HOST) o guestId (GUEST)
RegistroRequiere cuenta ClerkNo requiere cuenta
AccesoEmail + passwordCodigo evento + PIN / Access token
Rolesowner, admin, member, super_admin, platform_adminHOST, GUEST
Prefijo/v1/*/v1/mobile/*

Flujo de Login del Anfitrion

  1. Desde la pagina "Acceso App Movil" en nvito-admin, el organizador genera un EventAccessCode con un codigo alfanumerico de 8 caracteres y un PIN de 6 digitos hasheado con bcrypt. El evento debe estar en estado ACTIVE. Maximo 3 codigos activos por evento.
  2. El organizador comparte el codigo y PIN con el anfitrion. El PIN solo es visible al momento de crear o regenerar (one-time reveal).
  3. El anfitrion abre la app movil e ingresa el codigo + PIN.
  4. POST /v1/mobile/auth/login valida el codigo y el PIN contra bcrypt.
  5. Si es valido, se crea una MobileSession con role=HOST y se genera:
    • JWT (15 min de vida) con payload { sub: eventId, role: "HOST", userId, sessionId }
    • Refresh token (30 dias) almacenado en la tabla MobileSession

Flujo de Login del Invitado

  1. Se crea un GuestAccessToken automaticamente por cada invitado en dos momentos: al generar pases QR (individual o masivo via ensureGuestAccessToken()) y al enviar comunicaciones (email/WhatsApp via invitation-dispatcher.service.ts). Cada token es un string aleatorio de 64 caracteres (crypto.randomBytes(32).toString('hex')).
  2. El invitado recibe un deep link (nvito://join/{token}) o escanea un QR con el token.
  3. POST /v1/mobile/auth/guest-access valida el token y lo vincula al invitado y evento.
  4. Si es valido, se crea una MobileSession con role=GUEST y se genera:
    • JWT (15 min de vida) con payload { sub: eventId, role: "GUEST", guestId, sessionId }
    • Refresh token (30 dias)

MobileAuthGuard

El MobileAuthGuard protege todos los endpoints bajo /v1/mobile/ (excepto auth):

  1. Extrae el JWT del header Authorization: Bearer <token>
  2. Verifica firma y expiracion usando MOBILE_JWT_SECRET
  3. Extrae eventId, role (HOST/GUEST), userId/guestId y sessionId
  4. Verifica que la MobileSession este activa y no expirada
  5. Construye MobileAuthenticatedUser y lo adjunta al request

Interface MobileAuthenticatedUser

CampoTipoDescripcion
sessionIdstringUUID de la sesion movil activa
eventIdstringUUID del evento asociado
role'HOST' | 'GUEST'Rol movil del usuario
userIdstring | nullUUID del usuario (solo HOST)
guestIdstring | nullUUID del invitado (solo GUEST)
deviceIdstringIdentificador del dispositivo

Refresh Token (Rotacion)

La renovacion del JWT sigue el patron de rotacion de refresh tokens:

  1. POST /v1/mobile/auth/refresh con el refresh token actual
  2. El API valida el refresh token contra la tabla MobileSession
  3. Se genera un nuevo JWT (15 min) y un nuevo refresh token
  4. El refresh token anterior se invalida (cada token solo se usa una vez)
  5. Si el refresh token ya fue usado, se invalida toda la sesion (posible robo)

Almacenamiento Seguro

Los tokens en la app movil se almacenan usando expo-secure-store, que utiliza:

  • iOS: Keychain Services (almacenamiento encriptado por hardware)
  • Android: Android Keystore System (almacenamiento encriptado)

Diagrama de Secuencia: Login Movil

Modelos de Datos para Auth Movil

ModeloProposito
EventAccessCodeCodigo + PIN (bcrypt) para login de anfitrion
GuestAccessTokenToken aleatorio de 64 chars para acceso de invitado
MobileSessionSesion activa con refreshToken, pushToken, deviceId

Push Token Registration

Tras un login exitoso, la app registra el push token de Expo:

  1. La app solicita permisos de notificacion al usuario
  2. Si se otorgan, obtiene el Expo Push Token via getExpoPushTokenAsync()
  3. POST /v1/mobile/auth/register-push almacena el token en MobileSession.pushToken
  4. El backend usa este token para enviar push notifications via Expo Push API

3. Cadena de Guards

El orden de ejecucion de los guards es critico. NestJS ejecuta los guards globales en el orden en que se registran como APP_GUARD, seguidos por los guards declarados a nivel de controlador o metodo.

Guards Globales (app.module.ts)

  1. ClerkAuthGuard - Autenticacion y resolucion de identidad
  2. UserThrottlerGuard - Rate limiting por usuario/IP
  3. RoleGuard - Verificacion de roles basicos (owner/admin/member)

Guards Opcionales por Endpoint

  1. RolesGuard - Roles RBAC avanzados (RoleType enum) via PermissionsService.hasRole()
  2. PermissionsGuard - Permisos granulares (resource:action:scope)
  3. EventAccessGuard - Acceso a un evento especifico
  4. AdminPanelGuard - Acceso al panel admin (Super Admins + Platform Admins)
  5. SuperAdminGuard - Restriccion exclusiva para Super Admins (endpoints destructivos)
  6. InvitationStateGuard - Bloqueo de edicion segun estado de invitacion
  7. MobileRoleGuard - Verificacion de rol movil (HOST/GUEST) en endpoints /v1/mobile/*

Diagrama de la Cadena de Guards

Detalle de Cada Guard

GuardPropositoBypass Super Admin
ClerkAuthGuardVerifica JWT, busca usuario en BD, resuelve roles desde user_rolesN/A
UserThrottlerGuardRate limiting: trackea por user:{userId} o ip:{ip}No
RoleGuardValida roles basicos (owner, admin, member) del decorator @Roles()Si
RolesGuardValida roles RBAC avanzados (RoleType enum) via PermissionsServiceImplicito
PermissionsGuardValida permisos granulares (resource:action:scope)Implicito
EventAccessGuardVerifica acceso al evento especifico via user_rolesSi
AdminPanelGuardPermite acceso si isSuperAdmin || isPlatformAdmin. Usado a nivel de clase en controllers del panel adminN/A
SuperAdminGuardRestringe acceso exclusivamente a Super Admins. Usado a nivel de metodo para endpoints destructivos (DELETE)N/A
InvitationStateGuardBloquea escrituras si la invitacion esta PUBLISHED o CLOSEDNo
MobileAuthGuardVerifica JWT movil (MOBILE_JWT_SECRET), resuelve sesion y rol (HOST/GUEST). Protege endpoints /v1/mobile/*N/A (sistema independiente)
MobileRoleGuardVerifica que el usuario movil tenga el rol requerido (HOST o GUEST) via @RequireMobileRole(). Se usa despues de MobileAuthGuardN/A (sistema independiente)

4. Sistema RBAC

El sistema RBAC utiliza la tabla user_roles como fuente unica de verdad. Cada registro vincula un usuario con un rol, un scope y opcionalmente una organizacion o evento.

RoleType Enum

RolDescripcion
SUPER_ADMINAcceso completo al sistema. Gestiona todas las organizaciones.
PLATFORM_ADMINAdministrador de plataforma. Lectura cross-org, escritura en orgs asignadas. No puede eliminar usuarios/orgs ni gestionar SA/PA.
ORGANIZATION_OWNERDueno de la organizacion. Control total sobre su organizacion y usuarios.
ORGANIZATION_ADMINAdministrador. Gestion operativa completa de eventos y recursos.
EVENT_MANAGERGestor de eventos. Control completo sobre eventos asignados.
EVENT_EDITOREditor. Puede editar eventos e invitados, pero no eliminar.
EVENT_COLLABORATORColaborador. Acceso limitado: agregar/editar invitados y ver info del evento.
EVENT_VIEWERVisualizador. Solo lectura de los eventos asignados.

RoleScope Enum

ScopeSignificadoEjemplo de uso
GLOBALAplica a todo el sistemaSuper Admin, Platform Admin
ORGANIZATIONAplica a toda una organizacionOwner, Admin (y PA con asignacion de org)
EVENTAplica a un evento especificoManager, Editor, Collaborator, Viewer

Formato de Permisos

Los permisos siguen el formato {recurso}:{accion}:{alcance}. Ejemplo: events:create:organization.

18 recursos: organizations, events, event_services, guests, invitations, templates, media, qr_passes, tables, music, itinerary, locations, users, roles, settings, analytics, ai_generation, audit_logs.

8 acciones: create, read, update, delete, manage (todas), view_all, export, import.

5 alcances: global, organization, event, assigned, own.

Tabla de Permisos por Rol

RecursoSUPER_ADMINPLATFORM_ADMINORG_OWNERORG_ADMINEVT_MANAGEREVT_EDITOREVT_COLLABEVT_VIEWER
Organizations*RR/U/DRR---
Events*CRUD/M/E (1)CRUD/M/ECRUD/M/ER/U/M/ER/URR
Guests*CRUD/I/E (1)CRUD/I/ECRUD/I/ECRUD/I/ECRUD/I/EC/R/UR
Invitations*CRUD/P/S (1)CRUD/P/SCRUD/P/SCRUD/P/SC/R/U/PRR
Templates*CRUDCRUDCRUDRR--
Media*CRUD/M (1)CRUD/MCRUD/MCRUDCRUDRR
QR Passes*CRUD/V (1)CRUD/VCRUD/VCRUD/VR/VRR
Tables*CRUD/A (1)CRUD/ACRUD/ACRUD/AR/U/ARR
Music*CRUD (1)CRUDCRUDCRUDC/R/URR
Itinerary*CRUD (1)CRUDCRUDCRUDC/R/URR
Locations*CRUD (1)CRUDCRUDCRUDC/R/URR
Users*R/U (2)CRUDR----
Roles*R/A (3)R/AR/A(ev)A(ev)---
Settings*RR/UR----
Analytics*R/ER/ER/ERRRR
AI Generation*C/R (1)C/RC/RC/RC/R--
Event Services*CRUD/M (1)CRUD/MCRUD/MCRUD/MR/URR
Audit Logs*RR-----

Leyenda: C=Create, R=Read, U=Update, D=Delete, M=Manage, E=Export, I=Import, P=Publish, S=Send, V=Validate, A=Assign, *=Wildcard (todos los permisos). Los roles de evento operan solo sobre recursos asignados.

Notas PLATFORM_ADMIN:

  1. (1) Escritura solo en organizaciones asignadas explicitamente. Lectura cross-org (todas las organizaciones).
  2. (2) No puede editar Super Admins ni otros Platform Admins. No puede cambiar isSuperAdmin. No puede eliminar usuarios.
  3. (3) Puede asignar roles org/event en sus orgs asignadas. No puede asignar SUPER_ADMIN ni PLATFORM_ADMIN.

Restricciones de negocio

  • SA y PA son mutuamente excluyentes: Un usuario no puede tener ambos roles simultaneamente. El backend valida esta restriccion y el frontend desactiva un toggle al activar el otro.
  • Usuarios inactivos son de solo lectura: No se puede editar ni asignar roles a un usuario inactivo. La unica accion permitida es reactivarlo (isActive: true).

Wildcards

  • * (wildcard completo): Coincide con cualquier permiso. Asignado al SUPER_ADMIN.
  • PLATFORM_ADMIN no tiene wildcard -- sus permisos son explicitos y acotados.
  • La funcion matchesPermission() descompone permisos en {resource}:{action}:{scope} y evalua coincidencia componente por componente, soportando wildcards en cada segmento.

5. Decoradores de Seguridad

Ubicados en src/common/decorators/. Se combinan con los guards correspondientes.

DecoradorGuard asociadoProposito
@Public()ClerkAuthGuard (skip)Marca endpoint como publico, omite autenticacion
@CurrentUser()Ninguno (param)Inyecta AuthenticatedUser o una propiedad especifica en el controller
@Roles('owner','admin')RoleGuardRequiere uno de los roles basicos listados
@RequireRole(RoleType)RolesGuardRequiere roles RBAC avanzados (enum RoleType de Prisma)
@RequirePermission(p)PermissionsGuardRequiere permiso granular resource:action:scope
@RequirePermissions(p)PermissionsGuardRequiere TODOS los permisos listados
(AdminPanelGuard)AdminPanelGuardPermite acceso a SA + PA. Se aplica a nivel de clase en controllers admin
@RequireSuperAdmin()SuperAdminGuardRestringe a Super Admins exclusivamente. Se aplica a nivel de metodo para DELETE y endpoints destructivos
@RequireEventAccess()EventAccessGuardValida acceso al evento del parametro eventId
@CheckInvitationState()InvitationStateGuardBloquea escrituras segun estado de la invitacion
@RequireMobileRole()MobileRoleGuardRequiere rol movil especifico (HOST o GUEST) en endpoints mobile

Ejemplo de Uso Combinado

@UseGuards(ClerkAuthGuard, PermissionsGuard, EventAccessGuard, InvitationStateGuard)
@RequirePermission('guests:create:assigned')
@RequireEventAccess()
@CheckInvitationState('block_if_published_or_closed', 'invitados')
@Post(':eventId/guests')
async createGuest(
  @CurrentUser() user: AuthenticatedUser,
  @Param('eventId') eventId: string,
  @Body() dto: CreateGuestDto,
) { /* ... */ }

Modos de @CheckInvitationState

  • block_if_published - Bloquea solo si la invitacion esta PUBLISHED.
  • block_if_closed - Bloquea solo si esta CLOSED.
  • block_if_published_or_closed - Bloquea en ambos estados.

Soporta resolucion indirecta de eventId desde recursos: location, itineraryItem, media, giftRegistryItem, giftBankAccount, musicTrack, accommodationHotel.

6. Seguridad HTTP

Configuracion aplicada en main.ts antes de inicializar la aplicacion.

Helmet (Headers de Seguridad)

Header / DirectivaConfiguracion
Content-Security-Policydefault-src 'self', object-src 'none', frame-src 'none'
Strict-Transport-Security (HSTS)max-age=31536000; includeSubDomains; preload
X-Frame-OptionsDENY
X-Content-Type-Optionsnosniff
X-Permitted-Cross-Domain-Policiesnone
Referrer-Policystrict-origin-when-cross-origin
Permissions-Policygeolocation=(), microphone=(), camera=()

CORS

  • Origenes: Configurables via CORS_ORIGINS (array de dominios). Wildcard * prohibido en produccion.
  • Credenciales: Habilitadas (credentials: true).
  • Cache preflight: 24 horas (maxAge: 86400).
  • Metodos: GET, POST, PUT, PATCH, DELETE.
  • Headers: Content-Type, Authorization.

Rate Limiting

ParametroValor
Limite100 requests por minuto
Ventana60,000 ms (1 minuto)
Trackinguser:{userId} si autenticado, ip:{ip} si publico
StorageRedisThrottlerStorage (Redis)
Fail-openSi Redis no esta disponible, permite el request

Body y Compresion

  • Limite de body: 20 MB para JSON y URL-encoded (permite subida de imagenes grandes).
  • Compresion gzip: Nivel 6, umbral 1 KB. Reduccion estimada de 70-90% de bandwidth. Desactivable con header X-No-Compression.

7. Aislamiento de Datos (Multi-Tenancy)

Nvito implementa un modelo multi-tenant donde cada organizacion es un tenant. El aislamiento se logra mediante Row-Level Security (RLS) de PostgreSQL.

TenantMiddleware

Se ejecuta despues de la autenticacion y antes de los controladores:

  1. Obtiene el AuthenticatedUser del request (resuelto por ClerkAuthGuard).
  2. Si el usuario tiene organizationId, ejecuta: SELECT set_config('app.current_tenant_id', '{orgId}', true).
  3. El parametro true indica configuracion local a la transaccion, evitando contaminacion entre requests.

Prevencion de SQL injection: Se usa $executeRaw con tagged template literals de Prisma para parametrizacion automatica.

Politicas RLS

CREATE POLICY tenant_isolation ON events
  USING (organization_id = current_setting('app.current_tenant_id')::uuid);

Garantias:

  • Un usuario de la Organizacion A nunca puede ver datos de la Organizacion B.
  • No se requieren filtros manuales por organizationId en el codigo.
  • La proteccion opera a nivel de base de datos, debajo de la capa de aplicacion.

Bypass para Super Admins y Platform Admins

Los Super Admins tienen organizationId = null, por lo que el TenantMiddleware no establece app.current_tenant_id. Las politicas RLS no se aplican y pueden ver datos de todas las organizaciones.

Los Platform Admins tambien tienen lectura cross-org. El TenantMiddleware usa user.activeOrganizationId || user.organizationId para el contexto RLS. Cuando el PA selecciona una organizacion via el header X-Organization-Id, el middleware establece ese contexto y el PA opera dentro de esa org. Sin organizacion activa, el comportamiento es similar al de un Super Admin (sin filtro RLS).

8. Manejo de Secretos

Nvito utiliza Bitwarden Secrets Manager como boveda centralizada, evitando almacenar credenciales en el codigo fuente.

Estructura de Proyectos en Bitwarden

ProyectoEntornoUso
localDesarrolloSecretos para desarrollo local
devDevelopmentSecretos del servidor de dev
testTestingSecretos del entorno de pruebas

Inyeccion por Entorno

  • Local: Se usa el CLI bws para inyectar secretos como variables de entorno (bws secret get <id>). El archivo .env local nunca se sube al repositorio.
  • GitLab CI/CD: El service account de Bitwarden se configura como variable protegida. Un paso del pipeline ejecuta bws y exporta los secretos sin registrarlos en logs.
  • Coolify: Los secretos se copian manualmente como variables de entorno del servicio en la interfaz de Coolify. Requiere actualizacion manual al rotar secretos.

Variables de Entorno Criticas

VariableDescripcion
CLERK_SECRET_KEYClave secreta de Clerk para verificar JWT
DATABASE_URLConnection string de PostgreSQL
REDIS_URLConnection string de Redis
CORS_ORIGINSOrigenes permitidos para CORS
ENCRYPTION_KEYClave de encriptacion de campos sensibles
MOBILE_JWT_SECRETClave secreta para firmar JWT de la app movil

9. Seguridad en nvito-admin (Frontend)

El panel de administracion implementa multiples capas de seguridad complementarias al backend:

Autenticacion y Proteccion de Rutas

MecanismoImplementacion
Clerk ProviderContexto de autenticacion a nivel de layout raiz
auth() en Server ActionsCada server action obtiene el token JWT de Clerk y valida autenticacion antes de ejecutar
Layout guardEl layout del dashboard verifica que el usuario este autenticado
RBAC frontendusePermissionsQuery + condicionales para mostrar/ocultar funcionalidad segun permisos

Validacion en la Frontera

Todas las 15 server actions validan inputs con Zod antes de llamar a la API:

// Ejemplo de validacion en server action
const parsed = uploadUrlSchema.safeParse(input);
if (!parsed.success) {
  return { success: false, error: 'Datos invalidos' };
}

Esto proporciona validacion tanto en el cliente (formularios con React Hook Form + Zod) como en el servidor (server actions con Zod safeParse).

Content Security Policy (CSP)

Headers CSP configurados en next.config.ts restringen los origenes de scripts, estilos, imagenes y conexiones, protegiendo contra XSS y data exfiltration.

Response Validation en Runtime

Los servicios criticos (eventos, invitados, organizaciones) usan apiClient.getValidated() con schemas Zod para validar las respuestas del API en runtime, proporcionando defense-in-depth contra responses malformadas.

Error Handling Seguro

El patron ActionResult<T> garantiza que los errores se manejan de forma explicita sin exponer stack traces ni detalles internos al usuario:

type ActionResult<T> =
  | { success: true; data: T }
  | { success: false; error: string };

Ultima actualizacion: Febrero 2026

Esta pagina fue util?