UNPKG

@node-saml/passport-saml

Version:

SAML 2.0 authentication strategy for Passport

222 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Strategy = exports.AbstractStrategy = void 0; const passport_strategy_1 = require("passport-strategy"); const assert_1 = require("assert"); const url = require("url"); const node_saml_1 = require("@node-saml/node-saml"); class AbstractStrategy extends passport_strategy_1.Strategy { constructor(options, signonVerify, logoutVerify) { super(); if (typeof options === "function") { throw new Error("Mandatory SAML options missing"); } if (!signonVerify || typeof signonVerify != "function") { throw new Error("SAML authentication strategy requires a verify function"); } // Customizing the name can be useful to support multiple SAML configurations at the same time. // Unlike other options, this one gets deleted instead of passed along. if (options.name) { this.name = options.name; } else { this.name = "saml"; } this._signonVerify = signonVerify; this._logoutVerify = logoutVerify; if (this.constructor.newSamlProviderOnConstruct) { this._saml = new node_saml_1.SAML(options); } this._passReqToCallback = !!options.passReqToCallback; } authenticate(req, options) { var _a, _b, _c, _d, _e; if (this._saml == null) { throw new Error("Can't get authenticate without a SAML provider defined."); } options.samlFallback = options.samlFallback || "login-request"; const validateCallback = async ({ profile, loggedOut, }) => { if (loggedOut) { if (profile != null) { // When logging out a user, use the consumer's `validate` function to check that // the `profile` associated with the logout request resolves to the same user // as the `profile` associated with the current session. const verified = async (logoutUser) => { var _a, _b; let userMatch = true; try { // Check to see if we are logging out the user that is currently logged in to craft a proper IdP response // It is up to the caller to return the same `User` as we have currently recorded as logged in for a successful logout assert_1.strict.deepStrictEqual(req.user, logoutUser); } catch (err) { userMatch = false; } const RelayState = ((_a = req.query) === null || _a === void 0 ? void 0 : _a.RelayState) || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.RelayState); if (this._saml == null) { return this.error(new Error("Can't get logout response URL without a SAML provider defined.")); } else { this._saml.getLogoutResponseUrl(profile, RelayState, options, userMatch, redirectIfSuccess); } // Log out the current user no matter if we can verify the logged in user === logout requested user await new Promise((resolve, reject) => { req.logout((err) => { if (err) { return reject(err); } resolve(undefined); }); }); }; let logoutUser; try { logoutUser = await new Promise((resolve, reject) => { const verifedCallback = (err, logoutUser) => { if (err) { return reject(err); } resolve(logoutUser); }; if (this._passReqToCallback) { this._logoutVerify(req, profile, verifedCallback); } else { this._logoutVerify(profile, verifedCallback); } }); } catch (err) { return this.error(err); } await verified(logoutUser); } else { // If the `profile` object was null, this is just a logout acknowledgment, so we take no action return this.pass(); } } else { const verified = (err, user, info) => { if (err) { return this.error(err); } if (!user) { return this.fail(info, 401); } this.success(user, info); }; if (this._passReqToCallback) { this._signonVerify(req, profile, verified); } else { this._signonVerify(profile, verified); } } }; const redirectIfSuccess = (err, url) => { if (err) { this.error(err); } else if (url == null) { this.error(new Error("Invalid logout redirect URL.")); } else { this.redirect(url); } }; if (((_a = req.query) === null || _a === void 0 ? void 0 : _a.SAMLResponse) || ((_b = req.query) === null || _b === void 0 ? void 0 : _b.SAMLRequest)) { const originalQuery = (_c = url.parse(req.url).query) !== null && _c !== void 0 ? _c : ""; this._saml .validateRedirectAsync(req.query, originalQuery) .then(validateCallback) .catch((err) => this.error(err)); } else if ((_d = req.body) === null || _d === void 0 ? void 0 : _d.SAMLResponse) { this._saml .validatePostResponseAsync(req.body) .then(validateCallback) .catch((err) => this.error(err)); } else if ((_e = req.body) === null || _e === void 0 ? void 0 : _e.SAMLRequest) { this._saml .validatePostRequestAsync(req.body) .then(validateCallback) .catch((err) => this.error(err)); } else { const requestHandler = { "login-request": async () => { try { if (this._saml == null) { throw new Error("Can't process login request without a SAML provider defined."); } const RelayState = (req.query && req.query.RelayState) || (req.body && req.body.RelayState); const host = req.headers && req.headers.host; if (this._saml.options.authnRequestBinding === "HTTP-POST") { const data = await this._saml.getAuthorizeFormAsync(RelayState, host); const res = req.res; res === null || res === void 0 ? void 0 : res.send(data); } else { // Defaults to HTTP-Redirect this.redirect(await this._saml.getAuthorizeUrlAsync(RelayState, host, options)); } } catch (err) { this.error(err); } }, "logout-request": async () => { if (this._saml == null) { throw new Error("Can't process logout request without a SAML provider defined."); } try { const RelayState = (req.query && req.query.RelayState) || (req.body && req.body.RelayState); // Defaults to HTTP-Redirect this.redirect(await this._saml.getLogoutUrlAsync(req.user, RelayState, options)); } catch (err) { this.error(err); } }, }[options.samlFallback]; requestHandler(); } } logout(req, callback) { if (this._saml == null) { throw new Error("Can't logout without a SAML provider defined."); } const RelayState = (req.query && req.query.RelayState) || (req.body && req.body.RelayState); this._saml .getLogoutUrlAsync(req.user, RelayState, {}) .then((url) => callback(null, url)) .catch((err) => callback(err)); } _generateServiceProviderMetadata(decryptionCert, signingCert) { if (this._saml == null) { throw new Error("Can't generate service provider metadata without a SAML provider defined."); } return this._saml.generateServiceProviderMetadata(decryptionCert, signingCert); } // This is redundant, but helps with testing error(err) { super.error(err); } redirect(url, status) { super.redirect(url, status); } success(user, info) { super.success(user, info); } } exports.AbstractStrategy = AbstractStrategy; class Strategy extends AbstractStrategy { generateServiceProviderMetadata(decryptionCert, signingCert) { return this._generateServiceProviderMetadata(decryptionCert, signingCert); } } exports.Strategy = Strategy; Strategy.newSamlProviderOnConstruct = true; //# sourceMappingURL=strategy.js.map