Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
};

class WrapError extends Error {
inner?: any;

Check warning on line 33 in lib/response.ts

View workflow job for this annotation

GitHub Actions / build (24)

Unexpected any. Specify a different type
}

/**@deprecated Use parseIssuer instead */
Expand Down Expand Up @@ -180,7 +180,10 @@
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;
}
Expand Down Expand Up @@ -328,7 +331,7 @@
audience: string;
issuer: string;
acsUrl: string;
claims: Record<string, any>;

Check warning on line 334 in lib/response.ts

View workflow job for this annotation

GitHub Actions / build (24)

Unexpected any. Specify a different type
requestId: string;
privateKey: string;
publicKey: string;
Expand Down
14 changes: 11 additions & 3 deletions lib/saml20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
40 changes: 20 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -64,5 +64,10 @@
"tsconfig-paths": "4.2.0",
"typescript": "5.9.3"
},
"overrides": {
"release-it": {
"undici": ">=6.24.0"
}
},
"readmeFilename": "README.md"
}
24 changes: 24 additions & 0 deletions test/lib/response.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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, {
Expand Down
22 changes: 22 additions & 0 deletions test/lib/saml20.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand All @@ -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);
Expand Down
Loading