diff --git a/package-lock.json b/package-lock.json index 8036d137..603850a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "passport-saml", - "version": "1.3.5", + "version": "1.4.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -92,30 +92,113 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/debug": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", "dev": true }, + "@types/express": { + "version": "4.17.8", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", + "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz", + "integrity": "sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/json-schema": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "@types/mime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", + "dev": true + }, "@types/node": { "version": "14.14.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.5.tgz", "integrity": "sha512-H5Wn24s/ZOukBmDn03nnGTp18A60ny9AmCwnEcgJiTgSGsCO7k+NWP7zjCCbhlcnVCoI+co52dUAt9GMhOSULw==", "dev": true }, - "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "@types/passport": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.4.tgz", + "integrity": "sha512-h5OfAbfBBYSzjeU0GTuuqYEk9McTgWeGQql9g3gUw2/NNCfD7VgExVRYJVVeU13Twn202Mvk9BT0bUrl30sEgA==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*" + } + }, + "@types/qs": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", + "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", "dev": true }, + "@types/serve-static": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.6.tgz", + "integrity": "sha512-nuRJmv7jW7VmCVTn+IgYDkkbbDGyIINOeu/G0d74X3lm6E5KfMeQPJhxIt1ayQeQB3cSxvYs1RA/wipYoFB4EA==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, "@types/xml-crypto": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.1.tgz", @@ -2194,11 +2277,6 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", diff --git a/package.json b/package.json index efabf206..ca52aded 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "dependencies": { "debug": "^3.1.0", "passport-strategy": "*", - "q": "^1.5.0", "xml-crypto": "^2.0.0", "xml-encryption": "1.2.1", "xml2js": "0.4.x", @@ -47,7 +46,6 @@ "@types/debug": "^4.1.5", "@types/node": "^14.14.5", "@types/passport-strategy": "^0.2.35", - "@types/q": "^1.5.4", "@types/xml-crypto": "^1.4.1", "@types/xml2js": "^0.4.5", "@types/xmldom": "^0.1.30", diff --git a/src/passport-saml/saml.ts b/src/passport-saml/saml.ts index 90f3baf9..37254a90 100644 --- a/src/passport-saml/saml.ts +++ b/src/passport-saml/saml.ts @@ -9,12 +9,11 @@ import url from 'url'; import querystring from 'querystring'; import xmlbuilder from 'xmlbuilder'; import xmlenc from 'xml-encryption'; -import util from 'util'; +import util, { promisify } from 'util'; import {CacheProvider as InMemoryCacheProvider} from './inmemory-cache-provider'; import * as algorithms from './algorithms'; import { signAuthnRequestPost } from './saml-post-signing'; import type { Request } from 'express'; -import Q from 'q'; function processValidlySignedPostRequest(self: SAML, doc, dom, callback) { const request = doc.LogoutRequest; @@ -100,6 +99,8 @@ interface SAMLOptions { decryptionPvk: string; logoutCallbackUrl: string; validateInResponseTo: boolean; + requestIdExpirationPeriodMs: number; + audience: string; } @@ -238,13 +239,13 @@ class SAML { const instant = this.generateInstant(); const forceAuthn = this.options.forceAuthn || false; - Q.fcall(() => { + (async () => { if(this.options.validateInResponseTo) { - return Q.ninvoke(this.cacheProvider, 'save', id, instant); + return promisify(this.cacheProvider.save).bind(this.cacheProvider)(id, instant); } else { - return Q(); + return; } - }) + })() .then(() => { const request = { 'samlp:AuthnRequest': { @@ -310,10 +311,9 @@ class SAML { } callback(null, stringRequest); }) - .fail(function(err){ + .catch(function(err){ callback(err); - }) - .done(); + }); } generateLogoutRequest(req) { @@ -354,7 +354,7 @@ class SAML { }; } - return Q.ninvoke(this.cacheProvider, 'save', id, instant) + return util.promisify(this.cacheProvider.save).bind(this.cacheProvider)(id, instant) .then(function() { return xmlbuilder.create(request).end(); }); @@ -662,7 +662,7 @@ class SAML { validatePostResponse(container, callback) { let xml, doc, inResponseTo; - Q.fcall(() => { + (async() => { xml = Buffer.from(container.SAMLResponse, 'base64').toString('utf8'); doc = new xmldom.DOMParser({ }).parseFromString(xml); @@ -677,7 +677,7 @@ class SAML { return this.validateInResponseTo(inResponseTo); } - }) + })() .then(() => this.certsToCheck()) .then(certs => { // Check if this document has a valid top-level signature @@ -712,7 +712,7 @@ class SAML { const encryptedAssertionXml = encryptedAssertions[0].toString(); const xmlencOptions = { key: this.options.decryptionPvk }; - return Q.ninvoke(xmlenc, 'decrypt', encryptedAssertionXml, xmlencOptions) + return util.promisify(xmlenc.decrypt).bind(xmlenc)(encryptedAssertionXml, xmlencOptions) .then(decryptedXml => { const decryptedDoc = new xmldom.DOMParser().parseFromString(decryptedXml); const decryptedAssertions = xpath(decryptedDoc, "/*[local-name()='Assertion']"); @@ -797,31 +797,30 @@ class SAML { .catch(err => { debug('validatePostResponse resulted in an error: %s', err); if (this.options.validateInResponseTo) { - Q.ninvoke(this.cacheProvider, 'remove', inResponseTo) + util.promisify(this.cacheProvider.remove).bind(this.cacheProvider)(inResponseTo) .then(function() { callback(err); }); } else { callback(err); } - }) - .done(); + }); } validateInResponseTo(inResponseTo) { if (this.options.validateInResponseTo) { if (inResponseTo) { - return Q.ninvoke(this.cacheProvider, 'get', inResponseTo) + return util.promisify(this.cacheProvider.get).bind(this.cacheProvider)(inResponseTo) .then(result => { if (!result) throw new Error('InResponseTo is not valid'); - return Q(); + return Promise.resolve(); }); } else { throw new Error('InResponseTo is missing from response'); } } else { - return Q(); + return Promise.resolve(); } } @@ -846,13 +845,13 @@ class SAML { return callback(err); } - Q.fcall(() => { + (async () => { return samlMessageType === 'SAMLResponse' ? this.verifyLogoutResponse(doc) : this.verifyLogoutRequest(doc); - }) + })() .then(() => this.hasValidSignatureForRedirect(container, originalQuery)) .then(() => processValidlySignedSamlLogout(this, doc, dom, callback)) - .fail(err => callback(err)); + .catch(err => callback(err)); }); }); } @@ -886,7 +885,7 @@ class SAML { } }); } else { - return Q(true); + return Promise.resolve(true); } } @@ -926,7 +925,7 @@ class SAML { } verifyLogoutResponse(doc) { - return Q.fcall(() => { + return (async () => { const statusCode = doc.LogoutResponse.Status[0].StatusCode[0].$.Value; if (statusCode !== "urn:oasis:names:tc:SAML:2.0:status:Success") throw 'Bad status code: ' + statusCode; @@ -937,8 +936,8 @@ class SAML { return this.validateInResponseTo(inResponseTo); } - return Q(true); - }); + return Promise.resolve(true); + })(); } verifyIssuer(samlMessage) { @@ -953,7 +952,7 @@ class SAML { } } - processValidlySignedAssertion = function(xml, samlResponseXml, inResponseTo, callback) { + processValidlySignedAssertion(xml, samlResponseXml, inResponseTo, callback) { let msg; const parserConfig = { explicitRoot: true, @@ -1030,34 +1029,34 @@ class SAML { if (confirmData && confirmData.$) { const subjectInResponseTo = confirmData.$.InResponseTo; if (inResponseTo && subjectInResponseTo && subjectInResponseTo != inResponseTo) { - return Q.ninvoke(this.cacheProvider, 'remove', inResponseTo) + return util.promisify(this.cacheProvider.remove).bind(this.cacheProvider)(inResponseTo) .then(() => { throw new Error('InResponseTo is not valid'); }); } else if (subjectInResponseTo) { let foundValidInResponseTo = false; - return Q.ninvoke(this.cacheProvider, 'get', subjectInResponseTo) + return util.promisify(this.cacheProvider.get).bind(this.cacheProvider)(subjectInResponseTo) .then(result => { if (result) { const createdAt = new Date(result); if (nowMs < createdAt.getTime() + this.options.requestIdExpirationPeriodMs) foundValidInResponseTo = true; } - return Q.ninvoke(this.cacheProvider, 'remove', inResponseTo ); + return util.promisify(this.cacheProvider.remove).bind(this.cacheProvider)(inResponseTo); }) .then(() => { if (!foundValidInResponseTo) { throw new Error('InResponseTo is not valid'); } - return Q(); + return Promise.resolve(); }); } } } else { - return Q.ninvoke(this.cacheProvider, 'remove', inResponseTo); + return util.promisify(this.cacheProvider.remove).bind(this.cacheProvider)(inResponseTo); } } else { - return Q(); + return Promise.resolve(); } }) .then(() => { @@ -1215,7 +1214,7 @@ class SAML { const encryptedDataXml = encryptedDatas[0].toString(); const xmlencOptions = { key: self.options.decryptionPvk }; - return Q.ninvoke(xmlenc, 'decrypt', encryptedDataXml, xmlencOptions) + return util.promisify(xmlenc.decrypt).bind(xmlenc)(encryptedDataXml, xmlencOptions) .then(function (decryptedXml) { const decryptedDoc = new xmldom.DOMParser().parseFromString(decryptedXml); const decryptedIds = xpath(decryptedDoc, "/*[local-name()='NameID']");