UNPKG

@simulacrum/auth0-simulator

Version:

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

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