Socket
Socket
Sign inDemoInstall

@zaptic-external/saml

Package Overview
Dependencies
71
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.8 to 3.0.0

2

CHANGELOG.md

@@ -5,2 +5,4 @@ # Changelog

## [3.0.0] - 2023/06/14
## [2.0.8] - 2023/04/07

@@ -7,0 +9,0 @@

export declare function decodePostResponse(message: string): string;
/**
* See https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf 3.4.4.1 DEFLATE Encoding
* TLDR: xml -> deflate -> base64 -> encodeURI
* NB: querystring does the uri encoding
*/
export declare function encodeRedirectParameters(xml: string, RelayState?: string): Promise<unknown>;
/**
* See https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf 3.5.4 Message Encoding
* TLDR: xml -> base64 -> application/x-www-form-urlencoded
* NB: This does not handle the final step of form encoding.
* The consumer should insert the data into html form fields and submit.
*/
export declare function encodePostFormFields(xml: string, RelayState?: string): {
SAMLRequest: string;
RelayState: string | undefined;
};

25

dist/helpers/encoding.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.encodeRedirectParameters = exports.decodePostResponse = void 0;
exports.encodePostFormFields = exports.encodeRedirectParameters = exports.decodePostResponse = void 0;
const zlib = require("zlib");
const querystring = require("querystring");
function decodePostResponse(message) {
return new Buffer(message, 'base64').toString('utf8');
return Buffer.from(message, 'base64').toString('utf8');
}
exports.decodePostResponse = decodePostResponse;
// See https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf 3.4.4.1 DEFLATE Encoding
// TLDR: xml -> deflate -> base64 -> encodeURI
// NB: querystring does the uri encoding
/**
* See https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf 3.4.4.1 DEFLATE Encoding
* TLDR: xml -> deflate -> base64 -> encodeURI
* NB: querystring does the uri encoding
*/
function encodeRedirectParameters(xml, RelayState) {
return new Promise((resolve, reject) => {
zlib.deflateRaw(new Buffer(xml), (error, deflatedMessage) => {
zlib.deflateRaw(Buffer.from(xml), (error, deflatedMessage) => {
if (error)

@@ -27,2 +29,13 @@ return reject(error);

exports.encodeRedirectParameters = encodeRedirectParameters;
/**
* See https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf 3.5.4 Message Encoding
* TLDR: xml -> base64 -> application/x-www-form-urlencoded
* NB: This does not handle the final step of form encoding.
* The consumer should insert the data into html form fields and submit.
*/
function encodePostFormFields(xml, RelayState) {
const SAMLRequest = Buffer.from(xml).toString('base64');
return { SAMLRequest, RelayState };
}
exports.encodePostFormFields = encodePostFormFields;
//# sourceMappingURL=encoding.js.map

@@ -37,3 +37,4 @@ import { KeyInfo, Signature } from './helpers/saml-types';

};
loginUrl: string;
redirectLoginUrl: string;
postLoginUrl: string;
};

@@ -40,0 +41,0 @@ }

@@ -28,3 +28,4 @@ "use strict";

},
loginUrl: idpDescriptor.SingleSignOnService.filter(service => service.$.Binding === 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect').map(service => service.$.Location)[0]
redirectLoginUrl: idpDescriptor.SingleSignOnService.filter(service => service.$.Binding === 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect').map(service => service.$.Location)[0],
postLoginUrl: idpDescriptor.SingleSignOnService.filter(service => service.$.Binding === 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST').map(service => service.$.Location)[0]
};

@@ -34,4 +35,6 @@ });

const identityProvider = identityProviders[0];
if (!identityProvider.loginUrl)
if (!identityProvider.redirectLoginUrl)
throw new Error('No login url found for the HTTP-Redirect binding');
if (!identityProvider.postLoginUrl)
throw new Error('No login url found for the HTTP-POST binding');
if (identityProvider.signature.allowedCertificates.length === 0)

@@ -38,0 +41,0 @@ throw new Error('No signing certificates found');

@@ -6,3 +6,4 @@ import { Validator } from './helpers/xml';

id: string;
loginUrl: string;
redirectLoginUrl: string;
postLoginUrl: string;
signature: {

@@ -42,2 +43,12 @@ algorithm: 'sha256' | 'sha512';

}
/**
* Data to be inserted into a hidden form and auto-submitted to use the HTTP-POST binding.
*/
export interface LoginRequestPostFormData {
action: string;
fields: {
SAMLRequest: string;
RelayState?: string;
};
}
interface SAMLProviderOptions {

@@ -62,2 +73,3 @@ XSDs: {

buildLoginRequestRedirectURL(relayState?: string, forceAuthentication?: boolean): Promise<string>;
buildLoginRequestPostFormData(relayState?: string, forceAuthentication?: boolean): Promise<LoginRequestPostFormData>;
parseLoginResponse<T extends {

@@ -72,3 +84,4 @@ [key: string]: string;

getMetadata(): string;
private buildLoginRequestXML;
}
export {};

@@ -53,20 +53,17 @@ "use strict";

return __awaiter(this, void 0, void 0, function* () {
const request = login_request_1.default(yield this.getUUID(), {
serviceProviderId: this.serviceProvider.id,
assertionUrl: this.serviceProvider.assertionUrl,
loginUrl: this.identityProvider.loginUrl,
forceAuthentication,
addNameIdPolicy: this.preferences.addNameIdPolicy
});
const xml = this.preferences.signLoginRequests
? crypto_1.signXML(request, certificate_1.getNonExpired(this.serviceProvider.signature))
: request;
const xml = yield this.buildLoginRequestXML(forceAuthentication);
// Google uses SingleSignOnService URLs that have a query param set in them so we need to detect that and build
// the url accordingly
if (url.parse(this.identityProvider.loginUrl).query) {
return this.identityProvider.loginUrl + '&' + (yield encoding_1.encodeRedirectParameters(xml, relayState));
if (url.parse(this.identityProvider.redirectLoginUrl).query) {
return this.identityProvider.redirectLoginUrl + '&' + (yield encoding_1.encodeRedirectParameters(xml, relayState));
}
return this.identityProvider.loginUrl + '?' + (yield encoding_1.encodeRedirectParameters(xml, relayState));
return this.identityProvider.redirectLoginUrl + '?' + (yield encoding_1.encodeRedirectParameters(xml, relayState));
});
}
buildLoginRequestPostFormData(relayState, forceAuthentication = this.preferences.forceAuthenticationByDefault) {
return __awaiter(this, void 0, void 0, function* () {
const xml = yield this.buildLoginRequestXML(forceAuthentication);
return { action: this.identityProvider.postLoginUrl, fields: encoding_1.encodePostFormFields(xml, relayState) };
});
}
parseLoginResponse(query) {

@@ -97,4 +94,19 @@ return __awaiter(this, void 0, void 0, function* () {

}
buildLoginRequestXML(forceAuthentication) {
return __awaiter(this, void 0, void 0, function* () {
const request = login_request_1.default(yield this.getUUID(), {
serviceProviderId: this.serviceProvider.id,
assertionUrl: this.serviceProvider.assertionUrl,
loginUrl: this.identityProvider.redirectLoginUrl,
forceAuthentication,
addNameIdPolicy: this.preferences.addNameIdPolicy
});
const xml = this.preferences.signLoginRequests
? crypto_1.signXML(request, certificate_1.getNonExpired(this.serviceProvider.signature))
: request;
return xml;
});
}
}
exports.default = SAMLProvider;
//# sourceMappingURL=service-provider.js.map

@@ -1,1 +0,1 @@

export declare function assertPromiseRejects<T>(promise: Promise<T>, message: string): Promise<false | void>;
export declare function assertPromiseRejects<T>(promise: Promise<T>, message: string): Promise<false>;

@@ -28,3 +28,4 @@ "use strict";

},
loginUrl: 'https://login.microsoftonline.com/id/saml2'
redirectLoginUrl: 'https://login.microsoftonline.com/id/saml2',
postLoginUrl: 'https://login.microsoftonline.com/id/saml2'
}

@@ -50,2 +51,10 @@ };

});
it('should throw an error if there are not login urls for the HTTP-POST Binding', function () {
return __awaiter(this, void 0, void 0, function* () {
const xml = azure_ad_metadata_file_1.default().replace('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" ' +
'Location="https://login.microsoftonline.com/id/saml2" />', '');
const expectedError = 'No login url found for the HTTP-POST binding';
yield helpers_1.assertPromiseRejects(metadata_1.extract(xml), expectedError);
});
});
it('should throw an error are no key descriptors', function () {

@@ -52,0 +61,0 @@ return __awaiter(this, void 0, void 0, function* () {

@@ -43,3 +43,4 @@ "use strict";

id: 'test-idp',
loginUrl: 'http://localhost:7000/idp/requestLogin',
redirectLoginUrl: 'http://localhost:7000/idp/requestLogin',
postLoginUrl: 'http://localhost:7000/idp/requestLogin',
signature: {

@@ -53,13 +54,27 @@ certificate: testCert,

};
function checkRedirectURL(redirectURL, relayState, validator) {
function checkSamlRequest(samlRequest, signed, validator) {
// Ths is naive but should be enough for now
if (signed) {
chai_1.assert.include(samlRequest, 'Signature', 'Request should be signed');
}
else {
chai_1.assert.notInclude(samlRequest, 'Signature', 'Request should not signed');
}
return validator(samlRequest);
}
function checkRedirectURL(redirectURL, relayState, signed, validator) {
const parts = redirectURL.split('?');
chai_1.assert.equal(parts[0], options.idp.loginUrl, 'Login url must be the one provided in the options');
chai_1.assert.equal(parts[0], options.idp.redirectLoginUrl, 'Login url must be the one provided in the options');
const { SAMLRequest, RelayState } = querystring.parse(parts[1]);
chai_1.assert.equal(RelayState, relayState, 'Relay states do not match');
chai_1.assert.isDefined(SAMLRequest, 'Query should have a SAMLRequest attribute');
const request = zlib.inflateRawSync(new Buffer(SAMLRequest, 'base64')).toString('utf8');
// Ths is naive but should be enough for now
chai_1.assert.include(request, 'Signature', 'Request should be signed');
return validator(request);
const request = zlib.inflateRawSync(Buffer.from(SAMLRequest, 'base64')).toString('utf8');
return checkSamlRequest(request, signed, validator);
}
function checkPostFormData(data, relayState, signed, validator) {
chai_1.assert.equal(data.action, options.idp.redirectLoginUrl);
chai_1.assert.equal(data.fields.RelayState, relayState);
const request = Buffer.from(data.fields.SAMLRequest, 'base64').toString();
return checkSamlRequest(request, signed, validator);
}
it('should generate valid metadata', function () {

@@ -77,3 +92,5 @@ return __awaiter(this, void 0, void 0, function* () {

const redirectURL = yield provider.buildLoginRequestRedirectURL(relayState);
yield checkRedirectURL(redirectURL, relayState, provider.XSDs.protocol);
const postFormData = yield provider.buildLoginRequestPostFormData(relayState);
yield checkRedirectURL(redirectURL, relayState, true, provider.XSDs.protocol);
yield checkPostFormData(postFormData, relayState, true, provider.XSDs.protocol);
});

@@ -83,5 +100,5 @@ });

return __awaiter(this, void 0, void 0, function* () {
const loginUrl = 'http://localhost:7000/idp/requestLogin?param=true';
const redirectLoginUrl = 'http://localhost:7000/idp/requestLogin?param=true';
// Add a parameter to the login url
const opts = Object.assign(Object.assign({}, options), { idp: Object.assign(Object.assign({}, options.idp), { loginUrl }) });
const opts = Object.assign(Object.assign({}, options), { idp: Object.assign(Object.assign({}, options.idp), { redirectLoginUrl }) });
const provider = yield service_provider_1.default.create(opts);

@@ -91,3 +108,3 @@ const relayState = 'someState';

// We just make sure that the url is build properly, the other tests will check the data
redirectURL.startsWith(loginUrl + '&');
redirectURL.startsWith(redirectLoginUrl + '&');
});

@@ -100,3 +117,5 @@ });

const redirectURL = yield provider.buildLoginRequestRedirectURL(relayState, true);
yield checkRedirectURL(redirectURL, relayState, provider.XSDs.protocol);
const postFormData = yield provider.buildLoginRequestPostFormData(relayState, true);
yield checkRedirectURL(redirectURL, relayState, true, provider.XSDs.protocol);
yield checkPostFormData(postFormData, relayState, true, provider.XSDs.protocol);
});

@@ -109,11 +128,5 @@ });

const redirectURL = yield provider.buildLoginRequestRedirectURL(relayState);
const parts = redirectURL.split('?');
chai_1.assert.equal(parts[0], options.idp.loginUrl, 'Login url must be the one provided in the options');
const { SAMLRequest, RelayState } = querystring.parse(parts[1]);
chai_1.assert.equal(RelayState, relayState, 'Relay states do not match');
chai_1.assert.isDefined(SAMLRequest, 'Query should have a SAMLRequest attribute');
const request = zlib.inflateRawSync(new Buffer(SAMLRequest, 'base64')).toString('utf8');
// Ths is naive but should be enough for now
chai_1.assert.notInclude(request, 'Signature', 'Request should not signed');
yield provider.XSDs.protocol(request);
const postFormData = yield provider.buildLoginRequestPostFormData(relayState);
yield checkRedirectURL(redirectURL, relayState, false, provider.XSDs.protocol);
yield checkPostFormData(postFormData, relayState, false, provider.XSDs.protocol);
});

@@ -120,0 +133,0 @@ });

{
"name": "@zaptic-external/saml",
"version": "2.0.8",
"version": "3.0.0",
"description": "Minimal saml service provider with support for sp-initiated redirect login only",

@@ -5,0 +5,0 @@ "main": "dist/service-provider.js",

@@ -5,4 +5,3 @@ [![Known Vulnerabilities](https://snyk.io/test/github/Zaptic/saml/badge.svg?targetFile=package.json)](https://snyk.io/test/github/Zaptic/saml?targetFile=package.json)

This library provides with a simple and secure SAML service provider.
At the moment it supports only service provider initiated login (using redirect binding) but more will come.
This library provides with a simple and secure SAML service provider.

@@ -48,5 +47,8 @@ ## Options

`idp.loginUrl`
The url that we should send auth requests to
`idp.loginRedirectUrl`
The url that we should send auth requests using the HTTP-Redirect binding to
`idp.loginPostUrl`
The url that we should send auth requests using the HTTP-POST binding to
`idp.signature`

@@ -129,3 +131,4 @@ This is the object that contains the certificates and signature algorithm that we should accept for signing the

id: 'test-idp',
loginUrl: 'http://localhost:7000/idp/requestLogin',
loginRedirectUrl: 'http://localhost:7000/idp/requestLogin',
loginPostUrl: 'http://localhost:7000/idp/requestLogin',
signature: {

@@ -132,0 +135,0 @@ algorithm: <'sha256'>'sha256',

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc