Action
The following Pre User Registration action denies signups from a forbidden email domain, requires a full name to be submitted, and otherwise stores that name inuser_metadata along with a randomly generated identifier in app_metadata.
- JavaScript
- TypeScript
mock-dependencies.js
/** @import {Event, PreUserRegistrationAPI} from "@auth0/actions/pre-user-registration/v2" */
const { randomUUID } = require('crypto');
/**
* Handler that will be called during the execution of a PreUserRegistration flow.
*
* @param {Event} event - Details about the context and user that is attempting to register.
* @param {PreUserRegistrationAPI} api - Interface whose methods can be used to change the behavior of the signup.
*/
exports.onExecutePreUserRegistration = async (event, api) => {
const user = event.user;
if (user.email?.endsWith('@example.com')) {
api.access.deny('forbidden', 'Forbidden email domain')
return;
}
const fullName = event.request.body['ulp-fullName'];
if (fullName === undefined) {
api.validation.error('invalid_payload', 'Missing full name');
return;
}
api.user.setUserMetadata('full_name', fullName);
api.user.setAppMetadata('custom_id', randomUUID());
}
mock-dependencies.ts
import type { Event, PreUserRegistrationAPI } from '@auth0/actions/pre-user-registration/v2';
import { randomUUID } from 'crypto';
/**
* Handler that will be called during the execution of a PreUserRegistration flow.
*
* @param {Event} event - Details about the context and user that is attempting to register.
* @param {PreUserRegistrationAPI} api - Interface whose methods can be used to change the behavior of the signup.
*/
exports.onExecutePreUserRegistration = async (event: Event, api: PreUserRegistrationAPI) => {
const user = event.user;
if (user.email?.endsWith('@example.com')) {
api.access.deny('forbidden', 'Forbidden email domain')
return;
}
const fullName = event.request.body['ulp-fullName'];
if (fullName === undefined) {
api.validation.error('invalid_payload', 'Missing full name');
return;
}
api.user.setUserMetadata('full_name', fullName);
api.user.setAppMetadata('custom_id', randomUUID());
};
Unit Test
The unit tests mock theevent and api objects to verify that the forbidden domain is denied, that a missing full name triggers a validation error, and that a valid submission stores the full name and a correctly formatted UUID.
Jest
Jest
- JavaScript
- TypeScript
mock-dependencies.spec.js
const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
const path = require('path');
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DIRNAME = path.dirname('../../../');
const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.js');
describe('onExecutePreUserRegistration', () => {
let loader;
let event;
let api;
beforeEach(async () => {
loader = await loadAction(ACTION_PATH);
[event, api] = getDefaultArguments();
jest.spyOn(api.access, 'deny');
jest.spyOn(api.validation, 'error');
jest.spyOn(api.user, 'setUserMetadata');
jest.spyOn(api.user, 'setAppMetadata');
});
afterEach(() => {
jest.resetAllMocks();
});
it('forbids email domain', async () => {
event.user.email = 'johndoe@example.com';
await loader.execute('onExecutePreUserRegistration', event, api);
expect(api.access.deny).toHaveBeenCalledWith('forbidden', 'Forbidden email domain');
expect(api.validation.error).not.toHaveBeenCalled();
expect(api.user.setUserMetadata).not.toHaveBeenCalled();
});
it('allows email domain without full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {};
await loader.execute('onExecutePreUserRegistration', event, api);
expect(api.access.deny).not.toHaveBeenCalled();
expect(api.validation.error).toHaveBeenCalledWith('invalid_payload', 'Missing full name');
expect(api.user.setUserMetadata).not.toHaveBeenCalled();
expect(api.user.setAppMetadata).not.toHaveBeenCalled();
});
it('allows email domain with full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {
'ulp-fullName': 'John Doe',
};
await loader.execute('onExecutePreUserRegistration', event, api);
expect(api.access.deny).not.toHaveBeenCalled();
expect(api.validation.error).not.toHaveBeenCalled();
expect(api.user.setUserMetadata).toHaveBeenCalledWith('full_name', 'John Doe');
expect(api.user.setAppMetadata).toHaveBeenCalledWith('custom_id', expect.stringMatching(UUID_V4_PATTERN));
});
});
package.json
{
"name": "actions-npm-example-js-jest",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "commonjs",
"main": "module-usage.js",
"scripts": {
"test": "jest"
},
"devDependencies": {
"@auth0/actions": "^0.32.0",
"jest": "^30.4.2"
},
"jest": {
"testEnvironment": "node"
}
}
mock-dependencies.test.ts
const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
const path = require('path');
const { compileActionModules } = require('./test-utils/load-compiled-action');
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DIRNAME = path.dirname('../../../');
const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.ts');
describe('onExecutePreUserRegistration', () => {
let loader: any;
let event: any;
let api: any;
beforeEach(async () => {
const { compiledActionPath } = compileActionModules(ACTION_PATH);
loader = await loadAction(compiledActionPath);
[event, api] = getDefaultArguments();
jest.spyOn(api.access, 'deny');
jest.spyOn(api.validation, 'error');
jest.spyOn(api.user, 'setUserMetadata');
jest.spyOn(api.user, 'setAppMetadata');
});
afterEach(() => {
jest.resetAllMocks();
});
it('forbids email domain', async () => {
event.user.email = 'johndoe@example.com';
await loader.execute('onExecutePreUserRegistration', event, api);
expect(api.access.deny).toHaveBeenCalledWith('forbidden', 'Forbidden email domain');
expect(api.validation.error).not.toHaveBeenCalled();
expect(api.user.setUserMetadata).not.toHaveBeenCalled();
});
it('allows email domain without full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {};
await loader.execute('onExecutePreUserRegistration', event, api);
expect(api.access.deny).not.toHaveBeenCalled();
expect(api.validation.error).toHaveBeenCalledWith('invalid_payload', 'Missing full name');
expect(api.user.setUserMetadata).not.toHaveBeenCalled();
expect(api.user.setAppMetadata).not.toHaveBeenCalled();
});
it('allows email domain with full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {
'ulp-fullName': 'John Doe',
};
await loader.execute('onExecutePreUserRegistration', event, api);
expect(api.access.deny).not.toHaveBeenCalled();
expect(api.validation.error).not.toHaveBeenCalled();
expect(api.user.setUserMetadata).toHaveBeenCalledWith('full_name', 'John Doe');
expect(api.user.setAppMetadata).toHaveBeenCalledWith('custom_id', expect.stringMatching(UUID_V4_PATTERN));
});
});
package.json
{
"name": "actions-npm-example-ts-jest",
"version": "1.0.0",
"description": "Actions TS",
"main": "example.ts",
"scripts": {
"test": "jest"
},
"author": "John Doe",
"license": "ISC",
"devDependencies": {
"@auth0/actions": "^0.32.0",
"@types/jest": "^29.5.12",
"@types/node": "22.14.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"typescript": "^5.9.2"
}
}
Mocha
Mocha
- JavaScript
- TypeScript
mock-dependencies.spec.js
const sinon = require('sinon');
const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
const path = require('path');
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DIRNAME = path.dirname('../../../');
const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.js');
describe('onExecutePreUserRegistration', () => {
let loader;
let event;
let api;
beforeEach(async () => {
loader = await loadAction(ACTION_PATH);
[event, api] = getDefaultArguments();
sinon.spy(api.access, 'deny');
sinon.spy(api.validation, 'error');
sinon.spy(api.user, 'setUserMetadata');
sinon.spy(api.user, 'setAppMetadata');
});
afterEach(() => {
sinon.restore();
});
it('forbids email domain', async () => {
event.user.email = 'johndoe@example.com';
await loader.execute('onExecutePreUserRegistration', event, api);
sinon.assert.calledWith(api.access.deny, 'forbidden', 'Forbidden email domain');
sinon.assert.notCalled(api.validation.error);
sinon.assert.notCalled(api.user.setUserMetadata);
});
it('allows email domain without full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {};
await loader.execute('onExecutePreUserRegistration', event, api);
sinon.assert.notCalled(api.access.deny);
sinon.assert.calledWith(api.validation.error, 'invalid_payload', 'Missing full name');
sinon.assert.notCalled(api.user.setUserMetadata);
sinon.assert.notCalled(api.user.setAppMetadata);
});
it('allows email domain with full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {
'ulp-fullName': 'John Doe',
};
await loader.execute('onExecutePreUserRegistration', event, api);
sinon.assert.notCalled(api.access.deny);
sinon.assert.notCalled(api.validation.error);
sinon.assert.calledWith(api.user.setUserMetadata, 'full_name', 'John Doe');
sinon.assert.calledWith(api.user.setAppMetadata, 'custom_id', sinon.match(UUID_V4_PATTERN));
});
});
package.json
{
"name": "actions-npm-example-js-mocha",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "commonjs",
"main": "module-usage.js",
"scripts": {
"test": "mocha"
},
"devDependencies": {
"@auth0/actions": "^0.32.0",
"chai": "^4.5.0",
"mocha": "^11.0.0",
"sinon": "^19.0.0"
}
}
.mocharc.json
{
"spec": "src/**/*.spec.js"
}
mock-dependencies.test.ts
import * as path from 'path';
import sinon from 'sinon';
import { compileActionModules } from './test-utils/load-compiled-action';
const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DIRNAME = path.dirname('../../../');
const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.ts');
describe('onExecutePreUserRegistration', () => {
let loader: any;
let event: any;
let api: any;
beforeEach(async () => {
const { compiledActionPath } = compileActionModules(ACTION_PATH);
loader = await loadAction(compiledActionPath);
[event, api] = getDefaultArguments();
sinon.spy(api.access, 'deny');
sinon.spy(api.validation, 'error');
sinon.spy(api.user, 'setUserMetadata');
sinon.spy(api.user, 'setAppMetadata');
});
afterEach(() => {
sinon.restore();
});
it('forbids email domain', async () => {
event.user.email = 'johndoe@example.com';
await loader.execute('onExecutePreUserRegistration', event, api);
sinon.assert.calledWith(api.access.deny, 'forbidden', 'Forbidden email domain');
sinon.assert.notCalled(api.validation.error);
sinon.assert.notCalled(api.user.setUserMetadata);
});
it('allows email domain without full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {};
await loader.execute('onExecutePreUserRegistration', event, api);
sinon.assert.notCalled(api.access.deny);
sinon.assert.calledWith(api.validation.error, 'invalid_payload', 'Missing full name');
sinon.assert.notCalled(api.user.setUserMetadata);
sinon.assert.notCalled(api.user.setAppMetadata);
});
it('allows email domain with full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {
'ulp-fullName': 'John Doe',
};
await loader.execute('onExecutePreUserRegistration', event, api);
sinon.assert.notCalled(api.access.deny);
sinon.assert.notCalled(api.validation.error);
sinon.assert.calledWith(api.user.setUserMetadata, 'full_name', 'John Doe');
sinon.assert.calledWith(api.user.setAppMetadata, 'custom_id', sinon.match(UUID_V4_PATTERN));
});
});
package.json
{
"name": "actions-npm-example-ts-mocha",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"scripts": {
"test": "NODE_OPTIONS=--no-experimental-strip-types mocha"
},
"devDependencies": {
"@auth0/actions": "^0.32.0",
"@types/chai": "^4.3.16",
"@types/mocha": "^10.0.6",
"@types/node": "22.14.0",
"@types/sinon": "^17.0.3",
"chai": "^4.5.0",
"mocha": "^11.0.0",
"sinon": "^19.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
}
}
Node.js Test Runner
Node.js Test Runner
- JavaScript
- TypeScript
mock-dependencies.spec.js
const assert = require('node:assert');
const { describe, it, beforeEach, afterEach, mock } = require('node:test');
const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
const path = require('path');
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DIRNAME = path.dirname('../../../');
const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.js');
describe('onExecutePreUserRegistration', () => {
let loader;
let event;
let api;
beforeEach(async () => {
loader = await loadAction(ACTION_PATH);
[event, api] = getDefaultArguments();
mock.method(api.access, 'deny');
mock.method(api.validation, 'error');
mock.method(api.user, 'setUserMetadata');
mock.method(api.user, 'setAppMetadata');
});
afterEach(() => {
mock.reset();
});
it('forbids email domain', async () => {
event.user.email = 'johndoe@example.com';
await loader.execute('onExecutePreUserRegistration', event, api);
assert.deepEqual(api.access.deny.mock.calls[0].arguments, ['forbidden', 'Forbidden email domain']);
assert.strictEqual(api.validation.error.mock.calls.length, 0);
assert.strictEqual(api.user.setUserMetadata.mock.calls.length, 0);
});
it('allows email domain without full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {};
await loader.execute('onExecutePreUserRegistration', event, api);
assert.strictEqual(api.access.deny.mock.calls.length, 0);
assert.deepEqual(api.validation.error.mock.calls[0].arguments, ['invalid_payload', 'Missing full name']);
assert.strictEqual(api.user.setUserMetadata.mock.calls.length, 0);
assert.strictEqual(api.user.setAppMetadata.mock.calls.length, 0);
});
it('allows email domain with full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {
'ulp-fullName': 'John Doe',
};
await loader.execute('onExecutePreUserRegistration', event, api);
assert.strictEqual(api.access.deny.mock.calls.length, 0);
assert.strictEqual(api.validation.error.mock.calls.length, 0);
assert.deepEqual(api.user.setUserMetadata.mock.calls[0].arguments, ['full_name', 'John Doe']);
const appMetadataCall = api.user.setAppMetadata.mock.calls[0];
assert.strictEqual(appMetadataCall.arguments[0], 'custom_id');
assert.match(appMetadataCall.arguments[1], UUID_V4_PATTERN);
});
});
package.json
{
"name": "actions-npm-example-js-node-test",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "commonjs",
"main": "module-usage.js",
"scripts": {
"test": "node --test src/*.spec.js"
},
"devDependencies": {
"@auth0/actions": "^0.32.0"
}
}
mock-dependencies.test.ts
const assert = require('node:assert');
const { describe, it, beforeEach, afterEach, mock } = require('node:test');
const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
const path = require('path');
const { compileActionModules } = require('./test-utils/load-compiled-action.ts');
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DIRNAME = path.dirname('../../../');
const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.ts');
describe('onExecutePreUserRegistration', () => {
let loader;
let event;
let api;
beforeEach(async () => {
const { compiledActionPath } = compileActionModules(ACTION_PATH);
loader = await loadAction(compiledActionPath);
[event, api] = getDefaultArguments();
mock.method(api.access, 'deny');
mock.method(api.validation, 'error');
mock.method(api.user, 'setUserMetadata');
mock.method(api.user, 'setAppMetadata');
});
afterEach(() => {
mock.reset();
});
it('forbids email domain', async () => {
event.user.email = 'johndoe@example.com';
await loader.execute('onExecutePreUserRegistration', event, api);
assert.deepEqual(api.access.deny.mock.calls[0].arguments, ['forbidden', 'Forbidden email domain']);
assert.strictEqual(api.validation.error.mock.calls.length, 0);
assert.strictEqual(api.user.setUserMetadata.mock.calls.length, 0);
});
it('allows email domain without full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {};
await loader.execute('onExecutePreUserRegistration', event, api);
assert.strictEqual(api.access.deny.mock.calls.length, 0);
assert.deepEqual(api.validation.error.mock.calls[0].arguments, ['invalid_payload', 'Missing full name']);
assert.strictEqual(api.user.setUserMetadata.mock.calls.length, 0);
assert.strictEqual(api.user.setAppMetadata.mock.calls.length, 0);
});
it('allows email domain with full name', async () => {
event.user.email = 'johndoe@test.com';
event.request.body = {
'ulp-fullName': 'John Doe',
};
await loader.execute('onExecutePreUserRegistration', event, api);
assert.strictEqual(api.access.deny.mock.calls.length, 0);
assert.strictEqual(api.validation.error.mock.calls.length, 0);
assert.deepEqual(api.user.setUserMetadata.mock.calls[0].arguments, ['full_name', 'John Doe']);
const appMetadataCall = api.user.setAppMetadata.mock.calls[0];
assert.strictEqual(appMetadataCall.arguments[0], 'custom_id');
assert.match(appMetadataCall.arguments[1], UUID_V4_PATTERN);
});
});
package.json
{
"name": "actions-npm-example-ts-node-test",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"scripts": {
"test": "node --test src/*.test.ts"
},
"devDependencies": {
"@auth0/actions": "^0.32.0",
"@types/node": "22.14.0",
"typescript": "^5.9.2"
}
}