@simulacrum/auth0-simulator
Version:
Run local instance of Auth0 API for local development and integration testing
207 lines (205 loc) • 7.26 kB
JavaScript
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
const require_login_redirect = require('./login-redirect.cjs');
const require_web_message = require('./web-message.cjs');
const require_utils = require('./utils.cjs');
const require_login = require('../views/login.cjs');
const require_oauth_handlers = require('./oauth-handlers.cjs');
const require_username_password = require('../views/username-password.cjs');
let querystring = require("querystring");
let assert_ts = require("assert-ts");
let base64_url = require("base64-url");
let jsonwebtoken = require("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 = require_utils.createPersonQuery(simulationStore);
let authorizeHandlers = {
query: require_login_redirect.createLoginRedirectHandler(options),
web_message: require_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;
(0, assert_ts.assert)(!!req.session, "no session");
if (currentUser) req.session.username = currentUser;
let responseMode = req.query.response_mode ?? "query";
(0, assert_ts.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;
(0, assert_ts.assert)(!!responseClientId, `no clientID assigned`);
let html = require_login.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;
(0, assert_ts.assert)(!!username, "no username in /usernamepassword/login");
(0, assert_ts.assert)(!!nonce, "no nonce in /usernamepassword/login");
(0, assert_ts.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;
(0, assert_ts.assert)(!!clientID, `no clientID assigned`);
let html = require_login.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(require_username_password.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}?${(0, querystring.stringify)({
code: (0, base64_url.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;
(0, assert_ts.assert)(!!responseClientId, "500::no clientID in options or request body");
let tokens = await require_oauth_handlers.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;
(0, assert_ts.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;
(0, assert_ts.assert)(!!token, "no authorization header or access_token");
let { sub } = (0, jsonwebtoken.decode)(token, { json: true });
let user = personQuery((person) => {
(0, assert_ts.assert)(!!person.id, `no email defined on person scenario`);
return person.id === sub;
});
(0, assert_ts.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
exports.createAuth0Handlers = createAuth0Handlers;