'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 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, baseUrl, appUrl, providerId, isOriginAllowed, cookieConfigurer, resolverContext, signInResolver } = options; const defaultAppOrigin = new url.URL(appUrl).origin; const callbackUrl = config.getOptionalString("callbackUrl") ?? `${baseUrl}/${providerId}/handler/frame`; const stateTransform = options.stateTransform ?? ((state) => ({ state })); const profileTransform = options.profileTransform ?? authenticator.defaultProfileTransform; const authenticatorCtx = authenticator.initialize({ config, callbackUrl }); const cookieManager = new OAuthCookieManager.OAuthCookieManager({ baseUrl, callbackUrl, defaultAppOrigin, providerId, cookieConfigurer }); const scopeManager = CookieScopeManager.CookieScopeManager.create({ config, 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, 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