{"version":3,"file":"auth0-handlers.mjs","names":["authorizeHandlers: Record<ResponseModes, RequestHandler>","responseClientId: string","responseAudience: string","token: string | null","decodeToken"],"sources":["../../src/handlers/auth0-handlers.ts"],"sourcesContent":["import type { ExtendedSimulationStore } from \"../store/index.ts\";\nimport type { Request, RequestHandler } from \"express\";\nimport type {\n  Auth0Configuration,\n  QueryParams,\n  ResponseModes,\n} from \"../types.ts\";\nimport { createLoginRedirectHandler } from \"./login-redirect.ts\";\nimport { createWebMessageHandler } from \"./web-message.ts\";\nimport { loginView } from \"../views/login.ts\";\nimport { createTokens } from \"./oauth-handlers.ts\";\nimport { assert } from \"assert-ts\";\nimport { stringify } from \"querystring\";\nimport { encode } from \"base64-url\";\nimport { userNamePasswordForm } from \"../views/username-password.ts\";\nimport { decode as decodeToken } from \"jsonwebtoken\";\nimport { createPersonQuery } from \"./utils.ts\";\n\nexport type Routes =\n  | \"/heartbeat\"\n  | \"/authorize\"\n  | \"/login\"\n  | \"/usernamepassword/login\"\n  | \"/login/callback\"\n  | \"/oauth/token\"\n  | \"/v2/logout\"\n  | \"/userinfo\"\n  | \"/passwordless/start\";\n\nexport type AuthSession = { username: string; nonce: string };\n\ntype LoggerArgs = Parameters<typeof console.dir>;\n\nconst createLogger = (debug: boolean) => ({\n  log: (...args: LoggerArgs): void => {\n    if (!debug) {\n      return;\n    }\n\n    console.dir(...args);\n  },\n});\n\nexport const createAuth0Handlers = (\n  simulationStore: ExtendedSimulationStore,\n  serviceURL: (request: Request) => string,\n  options: Auth0Configuration,\n  debug: boolean\n): Record<Routes, RequestHandler> => {\n  let { audience, scope, clientID, rulesDirectory } = options;\n  let personQuery = createPersonQuery(simulationStore);\n\n  let authorizeHandlers: Record<ResponseModes, RequestHandler> = {\n    query: createLoginRedirectHandler(options),\n    web_message: createWebMessageHandler(),\n  };\n\n  let logger = createLogger(debug);\n\n  return {\n    [\"/heartbeat\"]: function (_, res) {\n      res.status(200).json({ ok: true });\n    },\n\n    [\"/authorize\"]: function (req, res, next) {\n      logger.log({\n        \"/authorize\": {\n          body: req.body,\n          query: req.query,\n          session: req.session,\n        },\n      });\n      let currentUser = req.query.currentUser as string | undefined;\n\n      assert(!!req.session, \"no session\");\n\n      if (currentUser) {\n        // the request is a silent login.\n        // We fake an existing login by\n        // adding the user to the session\n        req.session.username = currentUser;\n      }\n\n      let responseMode = (req.query.response_mode ?? \"query\") as ResponseModes;\n\n      assert(\n        [\"query\", \"web_message\"].includes(responseMode),\n        `unknown response_mode ${responseMode}`\n      );\n\n      let handler = authorizeHandlers[responseMode];\n\n      handler(req, res, next);\n    },\n\n    [\"/login\"]: function (req, res) {\n      logger.log({ \"/login\": { body: req.body, query: req.query } });\n      let query = req.query as QueryParams;\n      let responseClientId = query.client_id ?? clientID;\n      let responseAudience = query.audience ?? audience;\n      assert(!!responseClientId, `no clientID assigned`);\n\n      let html = loginView({\n        domain: new URL(serviceURL(req)).host,\n        scope,\n        redirectUri: query.redirect_uri,\n        clientID: responseClientId,\n        audience: responseAudience,\n        loginFailed: false,\n      });\n\n      res.set(\"Content-Type\", \"text/html\");\n\n      res.status(200).send(Buffer.from(html));\n    },\n\n    [\"/usernamepassword/login\"]: function (req, res) {\n      logger.log({\n        \"/usernamepassword/login\": { body: req.body, query: req.query },\n      });\n      let { username, nonce, password } = req.body;\n\n      assert(!!username, \"no username in /usernamepassword/login\");\n      assert(!!nonce, \"no nonce in /usernamepassword/login\");\n      assert(!!req.session, \"no session\");\n\n      let user = personQuery(\n        (person) =>\n          person.email?.toLowerCase() === username.toLowerCase() &&\n          person.password === password\n      );\n\n      if (!user) {\n        let query = req.query as QueryParams;\n        let responseClientId = query.client_id ?? clientID;\n        let responseAudience = query.audience ?? audience;\n\n        assert(!!clientID, `no clientID assigned`);\n\n        let html = loginView({\n          domain: new URL(serviceURL(req)).host,\n          scope,\n          redirectUri: query.redirect_uri,\n          clientID: responseClientId,\n          audience: responseAudience,\n          loginFailed: true,\n        });\n\n        res.set(\"Content-Type\", \"text/html\");\n\n        res.status(400).send(html);\n        return;\n      }\n\n      req.session.username = username;\n\n      simulationStore.store.dispatch(\n        simulationStore.actions.batchUpdater([\n          simulationStore.schema.sessions.patch({\n            [nonce]: { username, nonce },\n          }),\n        ])\n      );\n\n      res.status(200).send(userNamePasswordForm(req.body));\n    },\n\n    [\"/login/callback\"]: function (req, res) {\n      let wctx = JSON.parse(req.body.wctx);\n      logger.log({\n        \"/login/callback\": { body: req.body, query: req.query, wctx },\n      });\n\n      let { redirect_uri, nonce } = wctx;\n\n      const session = simulationStore.schema.sessions.selectById(\n        simulationStore.store.getState(),\n        { id: nonce }\n      );\n\n      const { username } = session ?? {};\n\n      let encodedNonce = encode(`${nonce}:${username}`);\n\n      let qs = stringify({ code: encodedNonce, ...wctx });\n\n      let routerUrl = `${redirect_uri}?${qs}`;\n\n      res.redirect(302, routerUrl);\n    },\n\n    [\"/oauth/token\"]: async function (req, res, next) {\n      logger.log({ \"/oauth/token\": { body: req.body, query: req.query } });\n      try {\n        let iss = serviceURL(req);\n\n        let responseClientId: string =\n          (req?.body?.client_id as string) ?? clientID;\n        let responseAudience: string =\n          (req?.body?.audience as string) ?? audience;\n\n        assert(\n          !!responseClientId,\n          \"500::no clientID in options or request body\"\n        );\n\n        let tokens = await createTokens({\n          simulationStore,\n          body: req.body,\n          iss,\n          clientID: responseClientId,\n          audience: responseAudience,\n          rulesDirectory,\n          scope,\n        });\n\n        res.status(200).json({\n          ...tokens,\n          expires_in: 86400,\n          token_type: \"Bearer\",\n        });\n      } catch (error) {\n        next(error);\n      }\n    },\n\n    [\"/v2/logout\"]: function (req, res) {\n      req.session = null;\n\n      let returnToUrl = req.query.returnTo ?? req.headers.referer;\n\n      assert(typeof returnToUrl === \"string\", `no logical returnTo url`);\n\n      res.redirect(returnToUrl);\n    },\n\n    [\"/userinfo\"]: function (req, res) {\n      let token: string | null = null;\n      if (req.headers.authorization) {\n        let authorizationHeader = req.headers.authorization;\n        token = authorizationHeader?.split(\" \")?.[1];\n      } else {\n        token = req?.query?.access_token as string;\n      }\n\n      assert(!!token, \"no authorization header or access_token\");\n      let { sub } = decodeToken(token, { json: true }) as { sub: string };\n\n      let user = personQuery((person) => {\n        assert(!!person.id, `no email defined on person scenario`);\n\n        return person.id === sub;\n      });\n\n      assert(!!user, \"no user in /userinfo\");\n\n      let userinfo = {\n        sub,\n        name: user.name,\n        given_name: user.name,\n        family_name: user.name,\n        email: user.email,\n        email_verified: true,\n        locale: \"en\",\n        hd: \"okta.com\",\n      };\n\n      res.status(200).json(userinfo);\n    },\n\n    [\"/passwordless/start\"]: function (req, res, next) {\n      logger.log({ \"/passwordless/start\": { body: req.body } });\n\n      try {\n        const { client_id, connection, email, phone_number } = req.body;\n\n        // Validate required fields\n        if (!client_id) {\n          res.status(400).json({ error: \"client_id is required\" });\n          return;\n        }\n\n        if (!connection || (connection !== \"email\" && connection !== \"sms\")) {\n          res.status(400).json({\n            error: \"connection must be 'email' or 'sms'\",\n          });\n          return;\n        }\n\n        if (connection === \"email\" && !email) {\n          res.status(400).json({\n            error: \"email is required when connection is 'email'\",\n          });\n          return;\n        }\n\n        if (connection === \"sms\" && !phone_number) {\n          res.status(400).json({\n            error: \"phone_number is required when connection is 'sms'\",\n          });\n          return;\n        }\n\n        // Return appropriate response based on connection type\n        if (connection === \"email\") {\n          res.status(200).json({\n            _id: \"000000000000000000000000\",\n            email: email,\n            email_verified: false,\n          });\n        } else {\n          res.status(200).json({\n            _id: \"000000000000000000000000\",\n            phone_number: phone_number,\n            phone_verified: false,\n          });\n        }\n      } catch (error) {\n        next(error);\n      }\n    },\n  };\n};\n"],"mappings":"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"}