'use strict';

var crypto = require('crypto');
var url = require('url');
var errors = require('@backstage/errors');
var state = require('./state.cjs.js');
var sendWebMessageResponse = require('../flow/sendWebMessageResponse.cjs.js');
var prepareBackstageIdentityResponse = require('../identity/prepareBackstageIdentityResponse.cjs.js');
require('jose');
var OAuthCookieManager = require('./OAuthCookieManager.cjs.js');
var config = require('@backstage/config');
var CookieScopeManager = require('./CookieScopeManager.cjs.js');

function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }

var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);

function createOAuthRouteHandlers(options) {
  const {
    authenticator,
    config: config$1,
    baseUrl,
    appUrl,
    providerId,
    isOriginAllowed,
    cookieConfigurer,
    resolverContext,
    signInResolver
  } = options;
  const defaultAppOrigin = new url.URL(appUrl).origin;
  const callbackUrl = config$1.getOptionalString("callbackUrl") ?? `${baseUrl}/${providerId}/handler/frame`;
  const sessionDuration = config$1.has("sessionDuration") ? config.readDurationFromConfig(config$1, { key: "sessionDuration" }) : void 0;
  const stateTransform = options.stateTransform ?? ((state) => ({ state }));
  const profileTransform = options.profileTransform ?? authenticator.defaultProfileTransform;
  const authenticatorCtx = authenticator.initialize({ config: config$1, callbackUrl });
  const cookieManager = new OAuthCookieManager.OAuthCookieManager({
    baseUrl,
    callbackUrl,
    defaultAppOrigin,
    providerId,
    cookieConfigurer,
    sessionDuration
  });
  const scopeManager = CookieScopeManager.CookieScopeManager.create({
    config: config$1,
    authenticator,
    cookieManager,
    additionalScopes: options.additionalScopes
  });
  return {
    async start(req, res) {
      const env = req.query.env?.toString();
      const origin = req.query.origin?.toString();
      const redirectUrl = req.query.redirectUrl?.toString();
      const flow = req.query.flow?.toString();
      if (!env) {
        throw new errors.InputError("No env provided in request query parameters");
      }
      const nonce = crypto__default.default.randomBytes(16).toString("base64");
      cookieManager.setNonce(res, nonce, origin);
      const { scope, scopeState } = await scopeManager.start(req);
      const state$1 = { nonce, env, origin, redirectUrl, flow, ...scopeState };
      const { state: transformedState } = await stateTransform(state$1, { req });
      const { url, status } = await options.authenticator.start(
        {
          req,
          scope,
          state: state.encodeOAuthState(transformedState)
        },
        authenticatorCtx
      );
      res.statusCode = status || 302;
      res.setHeader("Location", url);
      res.setHeader("Content-Length", "0");
      res.end();
    },
    async frameHandler(req, res) {
      let origin = defaultAppOrigin;
      let state$1;
      try {
        state$1 = state.decodeOAuthState(req.query.state?.toString() ?? "");
        if (state$1.origin) {
          try {
            origin = new url.URL(state$1.origin).origin;
          } catch {
            throw new errors.NotAllowedError("App origin is invalid, failed to parse");
          }
          if (!isOriginAllowed(origin)) {
            throw new errors.NotAllowedError(`Origin '${origin}' is not allowed`);
          }
        }
        const cookieNonce = cookieManager.getNonce(req);
        const stateNonce = state$1.nonce;
        if (!cookieNonce) {
          throw new errors.NotAllowedError("Auth response is missing cookie nonce");
        }
        if (cookieNonce !== stateNonce) {
          throw new errors.NotAllowedError("Invalid nonce");
        }
        const result = await authenticator.authenticate(
          { req },
          authenticatorCtx
        );
        const { profile } = await profileTransform(result, resolverContext);
        const signInResult = signInResolver && await signInResolver({ profile, result }, resolverContext);
        const grantedScopes = await scopeManager.handleCallback(req, {
          result,
          state: state$1,
          origin
        });
        const response = {
          profile,
          providerInfo: {
            idToken: result.session.idToken,
            accessToken: result.session.accessToken,
            scope: grantedScopes,
            expiresInSeconds: result.session.expiresInSeconds
          },
          ...signInResult && {
            backstageIdentity: prepareBackstageIdentityResponse.prepareBackstageIdentityResponse(signInResult)
          }
        };
        if (result.session.refreshToken) {
          cookieManager.setRefreshToken(
            res,
            result.session.refreshToken,
            origin
          );
        }
        if (state$1.flow === "redirect") {
          if (!state$1.redirectUrl) {
            throw new errors.InputError(
              "No redirectUrl provided in request query parameters"
            );
          }
          res.redirect(state$1.redirectUrl);
          return;
        }
        sendWebMessageResponse.sendWebMessageResponse(res, origin, {
          type: "authorization_response",
          response
        });
      } catch (error) {
        const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
        if (state$1?.flow === "redirect" && state$1?.redirectUrl) {
          const redirectUrl = new url.URL(state$1.redirectUrl);
          redirectUrl.searchParams.set("error", message);
          res.redirect(redirectUrl.toString());
        } else {
          sendWebMessageResponse.sendWebMessageResponse(res, origin, {
            type: "authorization_response",
            error: { name, message }
          });
        }
      }
    },
    async logout(req, res) {
      if (req.header("X-Requested-With") !== "XMLHttpRequest") {
        throw new errors.AuthenticationError("Invalid X-Requested-With header");
      }
      if (authenticator.logout) {
        const refreshToken = cookieManager.getRefreshToken(req);
        await authenticator.logout({ req, refreshToken }, authenticatorCtx);
      }
      cookieManager.removeRefreshToken(res, req.get("origin"));
      await scopeManager.clear(req);
      res.status(200).end();
    },
    async refresh(req, res) {
      if (req.header("X-Requested-With") !== "XMLHttpRequest") {
        throw new errors.AuthenticationError("Invalid X-Requested-With header");
      }
      try {
        const refreshToken = cookieManager.getRefreshToken(req);
        if (!refreshToken) {
          throw new errors.InputError("Missing session cookie");
        }
        const scopeRefresh = await scopeManager.refresh(req);
        const result = await authenticator.refresh(
          {
            req,
            scope: scopeRefresh.scope,
            scopeAlreadyGranted: scopeRefresh.scopeAlreadyGranted,
            refreshToken
          },
          authenticatorCtx
        );
        const grantedScope = await scopeRefresh.commit(result);
        const { profile } = await profileTransform(result, resolverContext);
        const newRefreshToken = result.session.refreshToken;
        if (newRefreshToken && newRefreshToken !== refreshToken) {
          cookieManager.setRefreshToken(
            res,
            newRefreshToken,
            req.get("origin")
          );
        }
        const response = {
          profile,
          providerInfo: {
            idToken: result.session.idToken,
            accessToken: result.session.accessToken,
            scope: grantedScope,
            expiresInSeconds: result.session.expiresInSeconds
          }
        };
        if (signInResolver) {
          const identity = await signInResolver(
            { profile, result },
            resolverContext
          );
          response.backstageIdentity = prepareBackstageIdentityResponse.prepareBackstageIdentityResponse(identity);
        }
        res.status(200).json(response);
      } catch (error) {
        throw new errors.AuthenticationError("Refresh failed", error);
      }
    }
  };
}

exports.createOAuthRouteHandlers = createOAuthRouteHandlers;
//# sourceMappingURL=createOAuthRouteHandlers.cjs.js.map
