UNPKG

@simulacrum/auth0-simulator

Version:

Run local instance of Auth0 API for local development and integration testing

207 lines (205 loc) 7.26 kB
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;