Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send Phone Number #17

Merged
merged 5 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ This library provides you with the setup to test complex actions. Customise test

The following [Flows](https://auth0.com/docs/customize/actions/flows-and-triggers) are supported:

| Flow | Support |
| ---------------------- | ------- |
| Login | ✓ |
| Machine to Machine | ✓ |
| Password Reset | ✓ |
| Pre User Registration | ✓ |
| Post User Registration | planned |
| Post Change Password | planned |
| Send Phone Message | planned |

| Flow | Support |
| ---------------------- | ----------------- |
| Login | ✓ from v0.1.0 |
| Machine to Machine | ✓ pending release |
| Password Reset | ✓ pending release |
| Pre User Registration | ✓ pending release |
| Post User Registration | ✓ pending release |
| Post Change Password | ✓ pending release |
| Send Phone Message | ✓ pending release |

## Getting started

Expand Down
21 changes: 21 additions & 0 deletions examples/custom-send-phone-message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Send an SMS message using a custom API.
*/
exports.onExecuteSendPhoneMessage = async (event) => {
const { API_BASE_URL, API_SECRET } = event.secrets;
const url = new URL("/send-sms", API_BASE_URL);

const payload = {
message: event.message_options.text,
phoneNumber: event.message_options.recipient,
};

await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${API_SECRET}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
};
43 changes: 43 additions & 0 deletions examples/custom-send-phone-message.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const test = require("node:test");
const { strictEqual, deepStrictEqual } = require("node:assert");
const { onExecuteSendPhoneMessage } = require("./custom-send-phone-message");
const { nodeTestRunner } = require("@kilterset/auth0-actions-testing");

test("send phone message", async (t) => {
const { auth0, fetchMock } = await nodeTestRunner.actionTestSetup(t);

await t.test("is handled by a custom API", async (t) => {
fetchMock.mock("https://sms-provider/send-sms", 200);

const action = auth0.mock.actions.postChangePassword({
secrets: { API_BASE_URL: "https://sms-provider", API_SECRET: "shh" },
message_options: auth0.mock.phoneMessageOptions({
recipient: "+17762323",
text: "Here is your code",
}),
});

await action.simulate(onExecuteSendPhoneMessage);

const calls = fetchMock.calls();

strictEqual(calls.length, 1, "Expected 1 fetch call to be made.");

const [url, options] = calls[0];

strictEqual(url, "https://sms-provider/send-sms", "Unexpected URL");
strictEqual(options.method, "POST", "Unexpected request method");

deepStrictEqual(
options.headers,
{ Authorization: "Bearer shh", "Content-Type": "application/json" },
"Unexpected headers"
);

deepStrictEqual(
JSON.parse(options.body),
{ phoneNumber: "+17762323", message: "Here is your code" },
"Unexpected body"
);
});
});
14 changes: 14 additions & 0 deletions examples/notify-slack-post-user-registration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Notify a Slack channel when a new user registers.
*/
exports.onExecutePostUserRegistration = async (event) => {
const payload = {
text: `New User: ${event.user.email}`,
};

await fetch(event.secrets.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
};
42 changes: 42 additions & 0 deletions examples/notify-slack-post-user-registration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const test = require("node:test");
const { strictEqual, deepStrictEqual } = require("node:assert");
const {
onExecutePostUserRegistration,
} = require("./notify-slack-post-user-registration");
const { nodeTestRunner } = require("@kilterset/auth0-actions-testing");

test("post user registration", async (t) => {
const { auth0, fetchMock } = await nodeTestRunner.actionTestSetup(t);

await t.test("service is notified when user logs in", async (t) => {
fetchMock.mock("https://slack/hook", 201);

const action = auth0.mock.actions.postUserRegistration({
secrets: { SLACK_WEBHOOK_URL: "https://slack/hook" },
user: auth0.mock.user({ email: "ellie@example.com" }),
});

await action.simulate(onExecutePostUserRegistration);

const calls = fetchMock.calls();

strictEqual(calls.length, 1, "Expected 1 fetch call to be made.");

const [url, options] = calls[0];

strictEqual(url, "https://slack/hook", "Unexpected URL");
strictEqual(options.method, "POST", "Unexpected request method");

deepStrictEqual(
options.headers,
{ "Content-Type": "application/json" },
"Unexpected headers"
);

deepStrictEqual(
JSON.parse(options.body),
{ text: "New User: ellie@example.com" },
"Unexpected body"
);
});
});
16 changes: 16 additions & 0 deletions examples/revoke-session-post-change-password.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Revoke a session from a remote API after a user changes their password.
*/
exports.onExecutePostChangePassword = async (event) => {
const { API_BASE_URL, API_SECRET } = event.secrets;
const url = new URL("/revoke-session", API_BASE_URL);

await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${API_SECRET}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ userId: event.user.id }),
});
};
42 changes: 42 additions & 0 deletions examples/revoke-session-post-change-password.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const test = require("node:test");
const { strictEqual, deepStrictEqual } = require("node:assert");
const {
onExecutePostChangePassword,
} = require("./revoke-session-post-change-password");
const { nodeTestRunner } = require("@kilterset/auth0-actions-testing");

test("post change password", async (t) => {
const { auth0, fetchMock } = await nodeTestRunner.actionTestSetup(t);

await t.test("revokes remote session", async (t) => {
fetchMock.mock("https://api.example/revoke-session", 200);

const action = auth0.mock.actions.postChangePassword({
secrets: { API_BASE_URL: "https://api.example", API_SECRET: "shh" },
user: auth0.mock.user({ id: 42 }),
});

await action.simulate(onExecutePostChangePassword);

const calls = fetchMock.calls();

strictEqual(calls.length, 1, "Expected 1 fetch call to be made.");

const [url, options] = calls[0];

strictEqual(url, "https://api.example/revoke-session", "Unexpected URL");
strictEqual(options.method, "POST", "Unexpected request method");

deepStrictEqual(
options.headers,
{ Authorization: "Bearer shh", "Content-Type": "application/json" },
"Unexpected headers"
);

deepStrictEqual(
JSON.parse(options.body),
{ userId: 42 },
"Unexpected body"
);
});
});
3 changes: 3 additions & 0 deletions src/mock/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export * from "./credentials-exchange";
export * from "./post-challenge";
export * from "./post-login";
export * from "./post-change-password";
export * from "./post-user-registration";
export * from "./pre-user-registration";
export * from "./send-phone-message";
44 changes: 44 additions & 0 deletions src/mock/actions/post-change-password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { api, events } from "..";
import Auth0 from "../../types";
import { PostChallengeOptions } from "../api";

type Handler = (
event: Auth0.Events.PostChangePassword,
api: Auth0.API.PostChangePassword
) => Promise<void>;

export function postChangePassword({
cache,
...attributes
}: Parameters<typeof events.postChangePassword>[0] &
Omit<PostChallengeOptions, "user" | "request"> = {}) {
const event = events.postChangePassword(attributes);

const { implementation, state } = api.postChangePassword({ cache });

async function simulate(handler: Handler) {
await handler(event, implementation);
}

return new Proxy(
{
event,
simulate,
},
{
get(target, prop) {
if (typeof prop !== "string") {
return;
}

if (prop in target) {
return target[prop as keyof typeof target];
}

if (prop in state) {
return state[prop as keyof typeof state];
}
},
}
);
}
44 changes: 44 additions & 0 deletions src/mock/actions/post-user-registration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { api, events } from "..";
import Auth0 from "../../types";
import { PostChallengeOptions } from "../api";

type Handler = (
event: Auth0.Events.PostUserRegistration,
api: Auth0.API.PostUserRegistration
) => Promise<void>;

export function postUserRegistration({
cache,
...attributes
}: Parameters<typeof events.postUserRegistration>[0] &
Omit<PostChallengeOptions, "user" | "request"> = {}) {
const event = events.postUserRegistration(attributes);

const { implementation, state } = api.postUserRegistration({ cache });

async function simulate(handler: Handler) {
await handler(event, implementation);
}

return new Proxy(
{
event,
simulate,
},
{
get(target, prop) {
if (typeof prop !== "string") {
return;
}

if (prop in target) {
return target[prop as keyof typeof target];
}

if (prop in state) {
return state[prop as keyof typeof state];
}
},
}
);
}
44 changes: 44 additions & 0 deletions src/mock/actions/send-phone-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { api, events } from "..";
import Auth0 from "../../types";
import { PostChallengeOptions } from "../api";

type Handler = (
event: Auth0.Events.SendPhoneMessage,
api: Auth0.API.SendPhoneMessage
) => Promise<void>;

export function sendPhoneMessage({
cache,
...attributes
}: Parameters<typeof events.sendPhoneMessage>[0] &
Omit<PostChallengeOptions, "user" | "request"> = {}) {
const event = events.sendPhoneMessage(attributes);

const { implementation, state } = api.sendPhoneMessage({ cache });

async function simulate(handler: Handler) {
await handler(event, implementation);
}

return new Proxy(
{
event,
simulate,
},
{
get(target, prop) {
if (typeof prop !== "string") {
return;
}

if (prop in target) {
return target[prop as keyof typeof target];
}

if (prop in state) {
return state[prop as keyof typeof state];
}
},
}
);
}
3 changes: 3 additions & 0 deletions src/mock/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ export * from "./cache";
export * from "./credentials-exchange";
export * from "./post-challenge";
export * from "./post-login";
export * from "./post-change-password";
export * from "./post-user-registration";
export * from "./pre-user-registration";
export * from "./send-phone-message";
27 changes: 27 additions & 0 deletions src/mock/api/post-change-password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Auth0 from "../../types";
import { cache as mockCache } from "./cache";

export interface PostChangePasswordOptions {
cache?: Record<string, string>;
}

export interface PostChangePasswordState {
cache: Auth0.API.Cache;
}

export function postChangePassword({ cache }: PostChangePasswordOptions = {}) {
const apiCache = mockCache(cache);

const state: PostChangePasswordState = {
cache: apiCache,
};

const api: Auth0.API.PostChangePassword = {
cache: apiCache,
};

return {
implementation: api,
state,
};
}
Loading
Loading