diff --git a/lib/response.ts b/lib/response.ts index ad21add..852f1b4 100644 --- a/lib/response.ts +++ b/lib/response.ts @@ -180,7 +180,10 @@ const validateInternal = async (rawAssertion, options, cb) => { return; } - if (options.audience && !tokenHandler.validateAudience(assertion, options.audience)) { + if ( + options.audience && + !tokenHandler.validateAudience(assertion, options.audience, options.strictAudienceValidation) + ) { cb(new Error('Invalid audience.')); return; } diff --git a/lib/saml20.ts b/lib/saml20.ts index a2ef87e..59e8701 100644 --- a/lib/saml20.ts +++ b/lib/saml20.ts @@ -104,19 +104,27 @@ const parse = (assertion) => { }; }; -const validateAudience = (assertion, realm) => { +const audienceCheck = (audience, expectedAudience, strictValidation) => { + if (strictValidation) { + return audience === expectedAudience; + } + + return audience.startsWith(expectedAudience); +}; + +const validateAudience = (assertion, realm, strictValidation = false) => { const audience = getProp(assertion, 'Conditions.AudienceRestriction.Audience'); if (audience) { try { if (Array.isArray(realm)) { for (let i = 0; i < realm.length; i++) { - if (audience.startsWith(realm[i])) { + if (audienceCheck(audience, realm[i], strictValidation)) { return true; } } return false; } - return audience.startsWith(realm); + return audienceCheck(audience, realm, strictValidation); // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { return false; diff --git a/package-lock.json b/package-lock.json index 67b781d..7e4f5aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@types/xml2js": "0.4.14", "@typescript-eslint/eslint-plugin": "8.57.1", "@typescript-eslint/parser": "8.57.1", - "eslint": "10.0.3", + "eslint": "10.1.0", "eslint-config-prettier": "10.1.8", "globals": "17.4.0", "mocha": "11.7.5", @@ -424,13 +424,13 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", - "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", + "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.0" + "@eslint/core": "^1.1.1" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -2726,16 +2726,16 @@ } }, "node_modules/eslint": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz", - "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz", + "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", - "@eslint/config-helpers": "^0.5.2", + "@eslint/config-helpers": "^0.5.3", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", @@ -2748,7 +2748,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", - "espree": "^11.1.1", + "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2882,9 +2882,9 @@ } }, "node_modules/espree": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", - "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6156,9 +6156,9 @@ } }, "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6245,13 +6245,13 @@ } }, "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", + "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=18.17" + "node": ">=20.18.1" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index a4d9595..32df29e 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@types/xml2js": "0.4.14", "@typescript-eslint/eslint-plugin": "8.57.1", "@typescript-eslint/parser": "8.57.1", - "eslint": "10.0.3", + "eslint": "10.1.0", "eslint-config-prettier": "10.1.8", "globals": "17.4.0", "mocha": "11.7.5", @@ -64,5 +64,10 @@ "tsconfig-paths": "4.2.0", "typescript": "5.9.3" }, + "overrides": { + "release-it": { + "undici": ">=6.24.0" + } + }, "readmeFilename": "README.md" } diff --git a/test/lib/response.spec.ts b/test/lib/response.spec.ts index fac7acc..48876d6 100644 --- a/test/lib/response.spec.ts +++ b/test/lib/response.spec.ts @@ -18,6 +18,7 @@ const certificate = const inResponseTo = 'ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685'; const issuerName = 'https://identity.kidozen.com/'; const audience = 'http://demoscope.com'; +const suffixAudience = 'http://demoscope'; const validToken = fs.readFileSync('./test/assets/saml20.validToken.xml').toString(); const invalidToken = fs.readFileSync('./test/assets/saml20.invalidToken.xml').toString(); const invalidWrappedToken = fs.readFileSync('./test/assets/saml20.invalidWrappedToken.xml').toString(); @@ -172,6 +173,29 @@ describe('response.ts', function () { assert.strictEqual(response.audience, audience); }); + it('Should validate saml 2.0 token and check audience with suffix', async function () { + const response = await validate(validToken, { + publicKey: certificate, + audience: suffixAudience, + bypassExpiration: true, + }); + assert.strictEqual(response.audience, audience); + }); + + it('Should validate saml 2.0 token and check strict audience with suffix', async function () { + try { + await validate(validToken, { + publicKey: certificate, + audience: suffixAudience, + bypassExpiration: true, + strictAudienceValidation: true, + }); + } catch (error) { + const result = (error as Error).message; + assert.strictEqual(result, 'Invalid audience.'); + } + }); + it('Should fail with invalid audience', async function () { try { await validate(validToken, { diff --git a/test/lib/saml20.spec.ts b/test/lib/saml20.spec.ts index 599099e..656d718 100644 --- a/test/lib/saml20.spec.ts +++ b/test/lib/saml20.spec.ts @@ -88,6 +88,15 @@ describe('saml20.ts', function () { } }); + it('validateAudience with Suffix not ok with strict check', async function () { + try { + const value = saml20.validateAudience(assertion1, 'https://saml.boxyhq.com', true); + assert.strictEqual(value, false); + } catch (error) { + assert(error); + } + }); + it('validateAudience with Suffix Array ok', async function () { try { const value = saml20.validateAudience(assertion1, [...validateOptsArray, 'https://saml.boxyhq.com']); @@ -97,6 +106,19 @@ describe('saml20.ts', function () { } }); + it('validateAudience with Suffix Array not ok with strict check', async function () { + try { + const value = saml20.validateAudience( + assertion1, + [...validateOptsArray, 'https://saml.boxyhq.com'], + true + ); + assert.strictEqual(value, false); + } catch (error) { + assert(error); + } + }); + it('validateAudience with Suffix Array not ok', async function () { try { const value = saml20.validateAudience(assertion1, validateOptsArray);