UNPKG

@pagopa/io-spid-commons

Version:

Common code for integrating SPID authentication

146 lines 9.69 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.withSpid = exports.withSpidAuthMiddleware = exports.noopCacheProvider = void 0; /** * Exports a decorator function that applies * a SPID authentication middleware to an express application. * * Setups the endpoint to generate service provider metadata * and a scheduled process to refresh IDP metadata from providers. */ const express_1 = require("@pagopa/ts-commons/lib/express"); const responses_1 = require("@pagopa/ts-commons/lib/responses"); const function_1 = require("fp-ts/lib/function"); const O = require("fp-ts/lib/Option"); const T = require("fp-ts/lib/Task"); const t = require("io-ts"); const passport = require("passport"); const xml2js_1 = require("xml2js"); const E = require("fp-ts/Either"); const config_1 = require("./config"); const redis_cache_provider_1 = require("./strategy/redis_cache_provider"); Object.defineProperty(exports, "noopCacheProvider", { enumerable: true, get: function () { return redis_cache_provider_1.noopCacheProvider; } }); const logger_1 = require("./utils/logger"); const metadata_1 = require("./utils/metadata"); const middleware_1 = require("./utils/middleware"); const response_1 = require("./utils/response"); const saml_1 = require("./utils/saml"); const saml_2 = require("./utils/saml"); const samlUtils_1 = require("./utils/samlUtils"); /** * Wraps assertion consumer service handler * with SPID authentication and redirects. */ const withSpidAuthMiddleware = (acs, clientLoginRedirectionUrl, clientErrorRedirectionUrl, extraRequestParamsCodec) => (req, res, next) => { const maybeDoc = (0, saml_1.getXmlFromSamlResponse)(req.body); // in case the SAMLResponse is missing // we redirect the user to a specific error if (O.isNone(maybeDoc)) { logger_1.logger.error("Spid Authentication|Authentication Error|ERROR=%s|ISSUER=UNKNOWN", samlUtils_1.ERROR_SAML_RESPONSE_MISSING); return res.redirect(clientErrorRedirectionUrl + `?errorMessage=${samlUtils_1.ERROR_SAML_RESPONSE_MISSING}`); } passport.authenticate("spid", (err, user) => __awaiter(void 0, void 0, void 0, function* () { const issuer = (0, function_1.pipe)(maybeDoc, O.chain(saml_1.getSamlIssuer), O.getOrElse(() => "UNKNOWN")); if (err) { const redirectionUrl = clientErrorRedirectionUrl + (0, function_1.pipe)(maybeDoc, O.chain(saml_1.getErrorCodeFromResponse), O.map((errorCode) => `?errorCode=${errorCode}`), O.getOrElse(() => `?errorMessage=${err}`)); logger_1.logger.error("Spid Authentication|Authentication Error|ERROR=%s|ISSUER=%s|REDIRECT_TO=%s", err, issuer, redirectionUrl); return res.redirect(redirectionUrl); } if (!user) { logger_1.logger.error("Spid Authentication|Authentication Error|ERROR=user_not_found|ISSUER=%s", issuer); return res.redirect(clientLoginRedirectionUrl); } const _a = user, { extraLoginRequestParams } = _a, userBaseProps = __rest(_a, ["extraLoginRequestParams"]); const response = yield acs(Object.assign(Object.assign({}, userBaseProps), { getAcsOriginalRequest: () => req }), (0, function_1.pipe)(extraRequestParamsCodec, E.fromNullable(undefined), E.chainW((codec) => codec.decode(extraLoginRequestParams)), E.getOrElseW(() => undefined))); response.apply(res); }))(req, res, next); }; exports.withSpidAuthMiddleware = withSpidAuthMiddleware; /** * Apply SPID authentication middleware * to an express application. */ // eslint-disable-next-line max-params const withSpid = ({ acs, app, appConfig, doneCb = function_1.constVoid, logout, redisClient, samlConfig, serviceProviderConfig, lollipopMiddleware = (_, __, next) => next(), }) => { const loadSpidStrategyOptions = (0, middleware_1.getSpidStrategyOptionsUpdater)(samlConfig, serviceProviderConfig); const metadataTamperer = (0, saml_2.getMetadataTamperer)(new xml2js_1.Builder(), serviceProviderConfig, samlConfig); const authorizeRequestTamperer = (0, saml_1.getAuthorizeRequestTamperer)( // spid-testenv does not accept an xml header with utf8 encoding new xml2js_1.Builder({ xmldec: { encoding: undefined, version: "1.0" } }), samlConfig); const maybeStartupIdpsMetadata = O.fromNullable(appConfig.startupIdpsMetadata); // If `startupIdpsMetadata` is provided, IDP metadata // are initially taken from its value when the backend starts return (0, function_1.pipe)(maybeStartupIdpsMetadata, O.map(metadata_1.parseStartupIdpsMetadata), O.map((idpOptionsRecord) => T.of((0, middleware_1.makeSpidStrategyOptions)(samlConfig, serviceProviderConfig, idpOptionsRecord))), O.getOrElse(loadSpidStrategyOptions), T.map((spidStrategyOptions) => { (0, middleware_1.upsertSpidStrategyOption)(app, spidStrategyOptions); return (0, middleware_1.makeSpidStrategy)(spidStrategyOptions, saml_1.getSamlOptions, redisClient, authorizeRequestTamperer, metadataTamperer, (0, saml_1.getPreValidateResponse)(serviceProviderConfig.strictResponseValidation, appConfig.eventTraker, appConfig.hasClockSkewLoggingEvent), doneCb, appConfig.extraLoginRequestParamConfig); }), T.map((spidStrategy) => { var _a; // Even when `startupIdpsMetadata` is provided, we try to load // IDP metadata from the remote registries (0, function_1.pipe)(maybeStartupIdpsMetadata, O.map(() => { (0, function_1.pipe)(loadSpidStrategyOptions(), T.map((opts) => (0, middleware_1.upsertSpidStrategyOption)(app, opts)))().catch((e) => { logger_1.logger.error("loadSpidStrategyOptions|error:%s", e); }); })); // Fetch IDPs metadata from remote URL and update SPID passport strategy options const idpMetadataRefresher = () => (0, function_1.pipe)(loadSpidStrategyOptions(), T.map((opts) => (0, middleware_1.upsertSpidStrategyOption)(app, opts))); // Initializes SpidStrategy for passport passport.use("spid", spidStrategy); const spidAuth = passport.authenticate("spid", { session: false, }); // Setup SPID login handler app.get(appConfig.loginPath, (0, response_1.middlewareCatchAsInternalError)((req, res, next) => { (0, function_1.pipe)(O.fromNullable(req.query), O.chainNullableK((q) => q.authLevel), O.filter(t.keyof(config_1.SPID_LEVELS).is), O.chain(O.fromPredicate((authLevel) => appConfig.spidLevelsWhitelist.includes(authLevel))), O.fold(() => { var _a; logger_1.logger.error(`Missing or invalid authLevel [${(_a = req === null || req === void 0 ? void 0 : req.query) === null || _a === void 0 ? void 0 : _a.authLevel}]`); return (0, responses_1.ResponseErrorValidation)("Bad Request", "Missing or invalid authLevel").apply(res); }, (_) => next())); }), (0, response_1.middlewareCatchAsInternalError)(lollipopMiddleware), (0, response_1.middlewareCatchAsInternalError)(spidAuth)); // Setup SPID metadata handler app.get(appConfig.metadataPath, (0, express_1.toExpressHandler)((req) => __awaiter(void 0, void 0, void 0, function* () { return new Promise((resolve) => spidStrategy.generateServiceProviderMetadataAsync(req, null, // certificate used for encryption / decryption serviceProviderConfig.publicCert, (err, metadata) => { if (err || !metadata) { resolve((0, responses_1.ResponseErrorInternal)(err ? err.message : `Error generating service provider metadata ${err}.`)); } else { resolve((0, responses_1.ResponseSuccessXml)(metadata)); } })); }))); // Setup SPID assertion consumer service. // This endpoint is called when the SPID IDP // redirects the authenticated user to our app app.post(appConfig.assertionConsumerServicePath, (0, response_1.middlewareCatchAsInternalError)((0, exports.withSpidAuthMiddleware)(acs, appConfig.clientLoginRedirectionUrl, appConfig.clientErrorRedirectionUrl, (_a = appConfig.extraLoginRequestParamConfig) === null || _a === void 0 ? void 0 : _a.codec))); // Setup logout handler app.post(appConfig.sloPath, (0, express_1.toExpressHandler)(logout)); return { app, idpMetadataRefresher }; })); }; exports.withSpid = withSpid; //# sourceMappingURL=index.js.map