API del Actions Engine
Las acciones son fragmentos de código JavaScript personalizado que se ejecutan en puntos específicos durante los flujos de autenticación. Permiten extender Auris con lógica personalizada sin modificar la plataforma central: enriquecer tokens con datos externos, bloquear registros sospechosos, aplicar políticas de contraseñas personalizadas o sincronizar datos de usuarios con sistemas externos.
Las acciones se ejecutan en un entorno sandbox con alcance restringido. Cada acción está asociada a un punto de disparo (p. ej., post_login) y se ejecuta en orden de prioridad. Se pueden adjuntar múltiples acciones al mismo disparador.
Todos los endpoints de acciones requieren el encabezado x-tenant. Crear y gestionar acciones requiere acceso de nivel administrador (implícito en el permiso admin:all o en el permiso manage:actions).
Ciclo de Vida de una Acción
- Crea una acción con un tipo de disparador y código JavaScript.
- Prueba la acción revisando los registros de ejecución.
- Establece el estado de la acción en
activepara habilitarla en producción. - Monitorea la ejecución a través del endpoint de registros.
CRUD de Acciones
Listar Acciones
/api/actionsRequires: manage:actionsLista todas las acciones del tenant. Devuelve metadatos de la acción incluyendo tipo de
disparador, estado, estadísticas de ejecución y orden. Las acciones se ejecutan en orden
ascendente del campo order para cada tipo de disparador.
Parámetros de consulta
| Parámetro | Tipo | Descripción |
|---|---|---|
page | integer | Número de página (por defecto: 1) |
limit | integer | Elementos por página (por defecto: 20) |
trigger | string | Filtrar por tipo de disparador (p. ej., post_login) |
status | active | inactive | Filtrar por estado |
Respuesta exitosa
{
"ok": true,
"data": {
"data": [
{
"id": "act_abc123",
"name": "Enrich Token with CRM Data",
"trigger": "post_login",
"status": "active",
"order": 1,
"timeout": 5000,
"executionCount": 14523,
"errorCount": 12,
"lastExecutedAt": "2025-02-18T09:50:00Z",
"createdAt": "2025-01-10T10:00:00Z",
"updatedAt": "2025-02-15T14:00:00Z"
},
{
"id": "act_def456",
"name": "Block Disposable Emails",
"trigger": "pre_signup",
"status": "active",
"order": 1,
"timeout": 3000,
"executionCount": 892,
"errorCount": 0,
"lastExecutedAt": "2025-02-18T08:30:00Z",
"createdAt": "2025-01-20T10:00:00Z",
"updatedAt": "2025-01-20T10:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 5,
"totalPages": 1
}
}
}Crear Acción
/api/actionsRequires: manage:actionsCrea una nueva acción. La acción se crea con estado inactive por defecto. Cámbiala a
active a través del endpoint de alternancia después de probarla. El código JavaScript
se valida en busca de patrones bloqueados antes de almacenarse.
Cuerpo de la solicitud
{
"name": "Enrich Token with CRM Data",
"trigger": "post_login",
"code": "async function handler(context, api) {\n const response = await api.fetch('https://crm.example.com/api/user', {\n headers: { 'X-User-Email': context.user.email }\n });\n const crmData = await response.json();\n api.setCustomClaim('crm_id', crmData.id);\n api.setCustomClaim('account_tier', crmData.tier);\n}",
"order": 1,
"timeout": 5000
}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
name | string | Sí | Nombre legible por humanos |
trigger | string | Sí | Punto de disparo (ver Tipos de Disparador) |
code | string | Sí | Cuerpo de la función JavaScript |
order | integer | No | Orden de ejecución dentro del disparador (por defecto: 0, menor = primero) |
timeout | integer | No | Tiempo máximo de ejecución en milisegundos (por defecto: 5000, máx: 10000) |
Respuesta exitosa
{
"ok": true,
"data": {
"id": "act_ghi789",
"name": "Enrich Token with CRM Data",
"trigger": "post_login",
"status": "inactive",
"code": "async function handler(context, api) { ... }",
"order": 1,
"timeout": 5000,
"executionCount": 0,
"errorCount": 0,
"createdAt": "2025-02-18T10:00:00Z"
}
}Códigos de error
| Código | HTTP | Descripción |
|---|---|---|
VALIDATION_ERROR | 400 | Faltan campos requeridos o el tipo de disparador es inválido |
BLOCKED_PATTERN | 400 | El código contiene un patrón bloqueado (ver Restricciones del Sandbox) |
CODE_TOO_LARGE | 400 | El código supera el tamaño máximo permitido |
Obtener Acción
/api/actions/[id]Requires: manage:actionsRecupera una acción por ID, incluyendo su código completo y estadísticas de ejecución.
Respuesta exitosa
{
"ok": true,
"data": {
"id": "act_abc123",
"name": "Enrich Token with CRM Data",
"trigger": "post_login",
"status": "active",
"code": "async function handler(context, api) {\n const response = await api.fetch('https://crm.example.com/api/user', {\n headers: { 'X-User-Email': context.user.email }\n });\n const crmData = await response.json();\n api.setCustomClaim('crm_id', crmData.id);\n api.setCustomClaim('account_tier', crmData.tier);\n}",
"order": 1,
"timeout": 5000,
"executionCount": 14523,
"errorCount": 12,
"lastExecutedAt": "2025-02-18T09:50:00Z",
"createdAt": "2025-01-10T10:00:00Z",
"updatedAt": "2025-02-15T14:00:00Z"
}
}Actualizar Acción
/api/actions/[id]Requires: manage:actionsActualiza el nombre, código, disparador, orden o tiempo de espera de una acción. Todos los campos son opcionales — solo se actualizan los campos proporcionados. El código actualizado se revalida en busca de patrones bloqueados.
Cuerpo de la solicitud
{
"name": "Enrich Token with CRM Data v2",
"code": "async function handler(context, api) {\n // Lógica actualizada\n const data = await api.fetch('https://crm.example.com/v2/user/' + context.user.id);\n const user = await data.json();\n api.setCustomClaim('crm_id', user.id);\n}",
"timeout": 8000
}Respuesta exitosa
{
"ok": true,
"data": {
"id": "act_abc123",
"name": "Enrich Token with CRM Data v2",
"trigger": "post_login",
"status": "active",
"code": "async function handler(context, api) { ... }",
"order": 1,
"timeout": 8000,
"updatedAt": "2025-02-18T11:00:00Z"
}
}Eliminar Acción
/api/actions/[id]Requires: manage:actionsElimina una acción de forma permanente. La acción se elimina inmediatamente de la cadena de ejecución. Los registros de ejecución de esta acción se conservan.
Respuesta exitosa
{
"ok": true,
"data": { "deleted": true }
}Alternar Estado de Acción
/api/actions/[id]Requires: manage:actionsAlterna una acción entre los estados active e inactive. Solo las acciones activas se
ejecutan durante los flujos de autenticación.
Cuerpo de la solicitud
{
"status": "active"
}Valores válidos: active, inactive.
Respuesta exitosa
{
"ok": true,
"data": {
"id": "act_abc123",
"status": "active",
"updatedAt": "2025-02-18T11:30:00Z"
}
}Registros de Ejecución
/api/actions/[id]/logsRequires: manage:actionsRecupera los registros de ejecución de una acción específica. Cada entrada de registro indica si la ejecución fue exitosa o fallida, la duración y cualquier mensaje de error. Los registros se ordenan por marca de tiempo descendente.
Parámetros de consulta
| Parámetro | Tipo | Descripción |
|---|---|---|
page | integer | Número de página (por defecto: 1) |
limit | integer | Elementos por página (por defecto: 20) |
Respuesta exitosa
{
"ok": true,
"data": {
"data": [
{
"id": "log_abc123",
"actionId": "act_abc123",
"trigger": "post_login",
"status": "success",
"duration": 234,
"userId": "usr_xyz789",
"createdAt": "2025-02-18T09:50:00Z"
},
{
"id": "log_def456",
"actionId": "act_abc123",
"trigger": "post_login",
"status": "error",
"duration": 5001,
"error": "Action timed out after 5000ms",
"userId": "usr_abc123",
"createdAt": "2025-02-18T09:48:00Z"
},
{
"id": "log_ghi789",
"actionId": "act_abc123",
"trigger": "post_login",
"status": "error",
"duration": 112,
"error": "TypeError: Cannot read property 'email' of undefined",
"userId": "usr_def456",
"createdAt": "2025-02-18T09:45:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 14535,
"totalPages": 727
}
}
}Valores de estado del registro: success, error.
Tipos de Disparador
Las acciones pueden adjuntarse a uno de los seis puntos de disparo en la cadena de autenticación:
| Disparador | Se activa cuando | Casos de uso |
|---|---|---|
pre_login | Antes de que se intente la autenticación en Keycloak | Bloquear login desde IPs o dominios de email específicos, limitación de velocidad personalizada |
post_login | Después de una autenticación exitosa, antes de emitir tokens | Enriquecer tokens con datos externos, registrar analíticas personalizadas, sincronizar con CRM |
pre_signup | Antes de crear una nueva cuenta de usuario | Bloquear emails desechables, aplicar validaciones personalizadas, verificar listas de bloqueo externas |
post_signup | Después de crear una nueva cuenta de usuario | Enviar notificación de bienvenida, crear registros en sistemas externos, asignar roles predeterminados |
post_change_password | Después de que un usuario cambia su contraseña | Invalidar credenciales en caché, notificar sistemas externos, registro de auditoría |
pre_m2m_token | Antes de emitir un token M2M | Validar scopes del cliente, agregar claims personalizados, aplicar restricciones de horario |
Orden de Ejecución
Cuando múltiples acciones activas comparten el mismo disparador, se ejecutan secuencialmente en orden ascendente de order. Si una acción falla (lanza un error o supera el tiempo de espera), las acciones siguientes para ese disparador aún se ejecutan, a menos que la acción fallida deniegue explícitamente la solicitud.
Objeto ActionContext
Cada acción recibe un objeto context como primer argumento. La forma varía según el tipo de disparador.
pre_login / post_login
{
user: {
id: "usr_abc123",
email: "[email protected]",
username: "alice",
firstName: "Alice",
lastName: "Smith",
roles: ["editor", "viewer"],
emailVerified: true,
phoneNumber: "+39021234567",
phoneNumberVerified: true,
metadata: {}
},
connection: {
method: "password", // "password" | "magic_link" | "social" | "sso"
provider: null, // nombre del proveedor social (p. ej., "google") o null
ipAddress: "203.0.113.50",
userAgent: "Mozilla/5.0 ...",
timestamp: "2025-02-18T10:00:00Z"
},
tenant: "acme-corp"
}Para pre_login, el objeto user puede ser null si el usuario aún no ha sido resuelto (p. ej., email incorrecto). Los campos connection.method y connection.ipAddress siempre están disponibles.
pre_signup / post_signup
{
user: {
email: "[email protected]",
username: "newuser",
firstName: "New",
lastName: "User"
},
connection: {
method: "password",
ipAddress: "203.0.113.50",
userAgent: "Mozilla/5.0 ...",
timestamp: "2025-02-18T10:00:00Z"
},
tenant: "acme-corp"
}Para post_signup, el objeto user también incluye id y roles.
post_change_password
{
user: {
id: "usr_abc123",
email: "[email protected]"
},
tenant: "acme-corp"
}pre_m2m_token
{
application: {
id: "app_xyz789",
name: "Backend Service",
clientId: "m2m-client-id",
type: "M2M"
},
requestedScopes: ["read:users", "manage:roles"],
tenant: "acme-corp"
}ActionResult (Objeto API)
El segundo argumento que se pasa a las acciones es el objeto api, que provee métodos para influir en el flujo de autenticación:
| Método | Disponible en | Descripción |
|---|---|---|
api.setCustomClaim(key, value) | post_login, pre_m2m_token | Añade un claim personalizado al token de acceso |
api.setMetadata(key, value) | post_login, post_signup | Establece metadatos del usuario (persistidos en la base de datos) |
api.deny(reason) | pre_login, pre_signup, pre_m2m_token | Deniega el intento de autenticación con una razón |
api.log(message) | Todos los disparadores | Escribe un mensaje en el registro de ejecución de la acción |
api.fetch(url, options) | Todos los disparadores | Realiza una solicitud HTTP (fetch restringido con tiempo de espera) |
Ejemplo: Denegar Registro por Emails Desechables
async function handler(context, api) {
const disposableDomains = ['tempmail.com', 'throwaway.email', 'guerrillamail.com'];
const domain = context.user.email.split('@')[1];
if (disposableDomains.includes(domain)) {
api.deny('Las direcciones de email desechables no están permitidas');
return;
}
api.log('Registro permitido para el dominio: ' + domain);
}Ejemplo: Enriquecer Token Después del Login
async function handler(context, api) {
try {
const response = await api.fetch('https://crm.example.com/api/lookup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer crm-api-key'
},
body: JSON.stringify({ email: context.user.email })
});
if (response.ok) {
const data = await response.json();
api.setCustomClaim('crm_id', data.customerId);
api.setCustomClaim('plan', data.subscriptionPlan);
api.log('Token enriquecido: plan=' + data.subscriptionPlan);
} else {
api.log('Consulta CRM fallida: ' + response.status);
}
} catch (error) {
api.log('Error en consulta CRM: ' + error.message);
// No denegar el login si falla el enriquecimiento
}
}Ejemplo: Bloquear Token M2M Fuera del Horario Laboral
async function handler(context, api) {
var hour = new Date().getUTCHours();
if (hour < 6 || hour > 22) {
api.deny('Los tokens M2M no pueden emitirse fuera del horario laboral (06:00-22:00 UTC)');
return;
}
api.log('Token M2M emitido para ' + context.application.name + ' a la hora UTC ' + hour);
}Restricciones del Sandbox
Las acciones se ejecutan en un entorno sandbox con alcance restringido. Los siguientes patrones se detectan y bloquean durante la validación del código (en creación y actualización). El código que contiene alguno de estos patrones se rechaza con un error BLOCKED_PATTERN:
require(— sin importaciones de módulos CommonJSimport— sin importaciones de módulos ESprocess.— sin acceso al objeto process de Node.jschild_process— sin ejecución de shellfs./fs/promises— sin acceso al sistema de archivosglobal./globalThis.— sin acceso al alcance global- Patrones de evaluación dinámica de código — sin generación de código en tiempo de ejecución a partir de cadenas
El método api.fetch() se proporciona como alternativa segura a las bibliotecas HTTP externas. Soporta los métodos GET, POST, PUT, PATCH y DELETE con cuerpos JSON o de texto. El tiempo de espera se hereda de la configuración timeout de la acción.
Límites de Ejecución
| Límite | Valor |
|---|---|
| Tiempo máximo de ejecución | Configurable por acción (por defecto 5000ms, máx 10000ms) |
| Tamaño máximo del código | 64 KB |
Tamaño máximo de respuesta de api.fetch() | 1 MB |
| Globales disponibles | JSON, Date, Math, String, Number, Array, Object, Map, Set, Promise, RegExp, console.log (redirigido a api.log) |
Manejo de Errores
Cuando una acción lanza un error no manejado o supera el tiempo de espera:
- El error se registra en el registro de ejecución de la acción.
- El
errorCountde la acción se incrementa. - El flujo de autenticación continúa (las acciones no bloquean la autenticación por defecto a menos que se llame a
api.deny()). - Si la acción es crítica, usa
api.deny()explícitamente en tu manejador de errores.
Los errores de las acciones no bloquean la autenticación por defecto. Si necesitas que una acción fallida impida el login (p. ej., una verificación de cumplimiento), debes llamar a api.deny() explícitamente en tu bloque catch. De lo contrario, el usuario será autenticado aunque la acción falle.
Editor Visual Blueprint
La Consola de Auris incluye un editor visual de nodos (Blueprint Editor) para crear acciones mediante una interfaz de arrastrar y soltar en lugar de escribir JavaScript. El Blueprint Editor genera definiciones de reglas en JSON que se compilan a JavaScript equivalente en tiempo de ejecución.
El Blueprint Editor es una alternativa al editor de código — ambos producen el mismo resultado. Las acciones creadas con el Blueprint Editor pueden verse y editarse como código, y viceversa.
Consulta la documentación de la Consola para obtener detalles sobre el Blueprint Editor.
Relacionado
- Acciones y Ejecución en Sandbox — Cómo funciona el motor de ejecución en sandbox
- Crear Acciones Personalizadas — Guía paso a paso para crear acciones
- Actions Engine — Crea y gestiona acciones desde la Consola
- API de Webhooks — Entrega de eventos externos que complementa las acciones