Actions y ejecución en sandbox
El problema: extensibilidad sin bifurcar
Cada plataforma de identidad eventualmente se enfrenta a la misma tensión: los clientes necesitan lógica personalizada en sus flujos de autenticación, pero la plataforma no puede anticipar cada requisito. Sin un mecanismo de extensibilidad, las opciones son limitadas.
Lo que los clientes realmente necesitan es la capacidad de ejecutar código personalizado en puntos específicos del flujo de autenticación — antes del inicio de sesión (para bloquear intentos sospechosos), después del inicio de sesión (para enriquecer tokens con claims personalizados), después del registro (para activar flujos de onboarding), y más.
El desafío es hacerlo de forma segura. El código del cliente se ejecuta dentro del servidor de autorización, que es el componente más crítico para la seguridad de toda la infraestructura.
Cómo funciona el sandbox de Auris
Las Actions de Auris ejecutan JavaScript personalizado dentro de un entorno controlado usando el constructor Function de JavaScript. Este mecanismo crea una función a partir de una cadena de código con un scope explícito — el código de la action solo ve las variables que Auris proporciona explícitamente.
Paso 1: Análisis estático
Antes de ejecutar cualquier código, Auris escanea el código fuente de la action buscando patrones bloqueados:
const BLOCKED_PATTERNS = [
'require', // Sin carga de módulos
'import', // Sin importaciones de módulos ES
'process', // Sin acceso a process.env, process.exit, etc.
'global', // Sin acceso al objeto global
'child_process', // Sin creación de subprocesos
'fs', // Sin acceso al sistema de ficheros
]Paso 2: Construcción de la función
El código de la action se usa para construir una función con context como único parámetro:
const fn = new Function('context', actionCode)
fn(contextObject)El contextObject contiene solo lo que Auris decide exponer:
const context = {
user: { id, email, roles, metadata },
tenant: { id, plan },
event: { type, timestamp, ipAddress },
// No hay: fetch, XMLHttpRequest, process, global, require, etc.
}Paso 3: Ejecución con timeout
La función se ejecuta con un timeout de 50ms. Si la action no retorna dentro del límite de tiempo, se cancela y se usa el comportamiento predeterminado (permitir).
Tipos de Actions disponibles
| Tipo | Cuándo se ejecuta | Puede bloquear |
|---|---|---|
post-login | Después de la autenticación exitosa, antes de emitir tokens | Sí |
post-register | Después del registro de un nuevo usuario | No |
pre-token-issue | Inmediatamente antes de emitir tokens | Sí |
post-logout | Después de que el usuario cierra sesión | No |
La API del contexto
context.user
{
id: string // ID del usuario (usr_xxx)
email: string // Dirección de email
emailVerified: boolean // Si el email ha sido verificado
roles: string[] // Roles asignados
metadata: Record<string, unknown> // Metadatos personalizados del usuario
}context.event
{
type: 'login' | 'register' | 'token-issue' | 'logout'
timestamp: string // ISO 8601
ipAddress: string // IP del solicitante
userAgent: string // User agent del navegador/cliente
riskScore: number // Puntuación de riesgo calculada (0-100)
}context.access — enriquecer tokens
// Añadir claims personalizados al access token
context.access.setCustomClaim('department', 'engineering')
context.access.setCustomClaim('subscription_tier', 'pro')context.deny — bloquear autenticación
// Bloquear el inicio de sesión con un mensaje
context.deny('Acceso bloqueado: tu cuenta ha sido suspendida.')Ejemplo: enriquecimiento de tokens
// Action: añadir departamento del usuario al token
async function handler(context) {
const dept = context.user.metadata.department
if (dept) {
context.access.setCustomClaim('department', dept)
}
// No llamar a context.deny() = permitir
}Ejemplo: control de acceso basado en reglas
// Action: bloquear inicio de sesión desde países de alto riesgo
async function handler(context) {
const allowedCountries = ['IT', 'DE', 'FR', 'US', 'GB']
const country = context.event.geo?.countryCode
if (country && !allowedCountries.includes(country)) {
context.deny(`Inicio de sesión desde ${country} no está permitido.`)
}
}El análisis estático no es por sí mismo un límite de seguridad. Un atacante determinado podría ofuscar palabras clave bloqueadas. El análisis estático existe para detectar el mal uso accidental y proporcionar mensajes de error claros. La seguridad real proviene del objeto de scope restringido proporcionado a la función construida.