@simulacrum/auth0-simulator
Version:
Run local instance of Auth0 API for local development and integration testing
207 lines (205 loc) • 6.83 kB
JavaScript
import { createLoginRedirectHandler } from "./login-redirect.mjs";
import { createWebMessageHandler } from "./web-message.mjs";
import { createPersonQuery } from "./utils.mjs";
import { loginView } from "../views/login.mjs";
import { createTokens } from "./oauth-handlers.mjs";
import { userNamePasswordForm } from "../views/username-password.mjs";
import { stringify } from "querystring";
import { assert } from "assert-ts";
import { encode } from "base64-url";
import { decode as decode$1 } from "jsonwebtoken";
//#region src/handlers/auth0-handlers.ts
const createLogger = (debug) => ({ log: (...args) => {
if (!debug) return;
console.dir(...args);
} });
const createAuth0Handlers = (simulationStore, serviceURL, options, debug) => {
let { audience, scope, clientID, rulesDirectory } = options;
let personQuery = createPersonQuery(simulationStore);
let authorizeHandlers = {
query: createLoginRedirectHandler(options),
web_message: createWebMessageHandler()
};
let logger = createLogger(debug);
return {
["/heartbeat"]: function(_, res) {
res.status(200).json({ ok: true });
},
["/authorize"]: function(req, res, next) {
logger.log({ "/authorize": {
body: req.body,
query: req.query,
session: req.session
} });
let currentUser = req.query.currentUser;
assert(!!req.session, "no session");
if (currentUser) req.session.username = currentUser;
let responseMode = req.query.response_mode ?? "query";
assert(["query", "web_message"].includes(responseMode), `unknown response_mode ${responseMode}`);
let handler = authorizeHandlers[responseMode];
handler(req, res, next);
},
["/login"]: function(req, res) {
logger.log({ "/login": {
body: req.body,
query: req.query
} });
let query = req.query;
let responseClientId = query.client_id ?? clientID;
let responseAudience = query.audience ?? audience;
assert(!!responseClientId, `no clientID assigned`);
let html = loginView({
domain: new URL(serviceURL(req)).host,
scope,
redirectUri: query.redirect_uri,
clientID: responseClientId,
audience: responseAudience,
loginFailed: false
});
res.set("Content-Type", "text/html");
res.status(200).send(Buffer.from(html));
},
["/usernamepassword/login"]: function(req, res) {
logger.log({ "/usernamepassword/login": {
body: req.body,
query: req.query
} });
let { username, nonce, password } = req.body;
assert(!!username, "no username in /usernamepassword/login");
assert(!!nonce, "no nonce in /usernamepassword/login");
assert(!!req.session, "no session");
if (!personQuery((person) => person.email?.toLowerCase() === username.toLowerCase() && person.password === password)) {
let query = req.query;
let responseClientId = query.client_id ?? clientID;
let responseAudience = query.audience ?? audience;
assert(!!clientID, `no clientID assigned`);
let html = loginView({
domain: new URL(serviceURL(req)).host,
scope,
redirectUri: query.redirect_uri,
clientID: responseClientId,
audience: responseAudience,
loginFailed: true
});
res.set("Content-Type", "text/html");
res.status(400).send(html);
return;
}
req.session.username = username;
simulationStore.store.dispatch(simulationStore.actions.batchUpdater([simulationStore.schema.sessions.patch({ [nonce]: {
username,
nonce
} })]));
res.status(200).send(userNamePasswordForm(req.body));
},
["/login/callback"]: function(req, res) {
let wctx = JSON.parse(req.body.wctx);
logger.log({ "/login/callback": {
body: req.body,
query: req.query,
wctx
} });
let { redirect_uri, nonce } = wctx;
const { username } = simulationStore.schema.sessions.selectById(simulationStore.store.getState(), { id: nonce }) ?? {};
let routerUrl = `${redirect_uri}?${stringify({
code: encode(`${nonce}:${username}`),
...wctx
})}`;
res.redirect(302, routerUrl);
},
["/oauth/token"]: async function(req, res, next) {
logger.log({ "/oauth/token": {
body: req.body,
query: req.query
} });
try {
let iss = serviceURL(req);
let responseClientId = req?.body?.client_id ?? clientID;
let responseAudience = req?.body?.audience ?? audience;
assert(!!responseClientId, "500::no clientID in options or request body");
let tokens = await createTokens({
simulationStore,
body: req.body,
iss,
clientID: responseClientId,
audience: responseAudience,
rulesDirectory,
scope
});
res.status(200).json({
...tokens,
expires_in: 86400,
token_type: "Bearer"
});
} catch (error) {
next(error);
}
},
["/v2/logout"]: function(req, res) {
req.session = null;
let returnToUrl = req.query.returnTo ?? req.headers.referer;
assert(typeof returnToUrl === "string", `no logical returnTo url`);
res.redirect(returnToUrl);
},
["/userinfo"]: function(req, res) {
let token = null;
if (req.headers.authorization) token = req.headers.authorization?.split(" ")?.[1];
else token = req?.query?.access_token;
assert(!!token, "no authorization header or access_token");
let { sub } = decode$1(token, { json: true });
let user = personQuery((person) => {
assert(!!person.id, `no email defined on person scenario`);
return person.id === sub;
});
assert(!!user, "no user in /userinfo");
let userinfo = {
sub,
name: user.name,
given_name: user.name,
family_name: user.name,
email: user.email,
email_verified: true,
locale: "en",
hd: "okta.com"
};
res.status(200).json(userinfo);
},
["/passwordless/start"]: function(req, res, next) {
logger.log({ "/passwordless/start": { body: req.body } });
try {
const { client_id, connection, email, phone_number } = req.body;
if (!client_id) {
res.status(400).json({ error: "client_id is required" });
return;
}
if (!connection || connection !== "email" && connection !== "sms") {
res.status(400).json({ error: "connection must be 'email' or 'sms'" });
return;
}
if (connection === "email" && !email) {
res.status(400).json({ error: "email is required when connection is 'email'" });
return;
}
if (connection === "sms" && !phone_number) {
res.status(400).json({ error: "phone_number is required when connection is 'sms'" });
return;
}
if (connection === "email") res.status(200).json({
_id: "000000000000000000000000",
email,
email_verified: false
});
else res.status(200).json({
_id: "000000000000000000000000",
phone_number,
phone_verified: false
});
} catch (error) {
next(error);
}
}
};
};
//#endregion
export { createAuth0Handlers };
//# sourceMappingURL=auth0-handlers.mjs.map