Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@node-saml/node-saml

Package Overview
Dependencies
Maintainers
2
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@node-saml/node-saml - npm Package Compare versions

Comparing version 4.0.0-beta.6 to 4.0.0

2

lib/saml.d.ts

@@ -28,3 +28,3 @@ /// <reference types="node" />

getLogoutResponseUrl(samlLogoutRequest: Profile, RelayState: string, options: AuthenticateOptions & AuthorizeOptions, success: boolean, callback: (err: Error | null, url?: string) => void): void;
protected getLogoutResponseUrlAsync(samlLogoutRequest: Profile, RelayState: string, options: AuthenticateOptions & AuthorizeOptions, success: boolean): Promise<string>;
getLogoutResponseUrlAsync(samlLogoutRequest: Profile, RelayState: string, options: AuthenticateOptions & AuthorizeOptions, success: boolean): Promise<string>;
protected certsToCheck(): Promise<string[]>;

@@ -31,0 +31,0 @@ validatePostResponseAsync(container: Record<string, string>): Promise<{

@@ -491,2 +491,3 @@ "use strict";

async validatePostResponseAsync(container) {
var _a, _b, _c, _d;
let xml;

@@ -497,5 +498,3 @@ let doc;

xml = Buffer.from(container.SAMLResponse, "base64").toString("utf8");
doc = (0, xml_1.parseDomFromString)(xml);
if (!Object.prototype.hasOwnProperty.call(doc, "documentElement"))
throw new Error("SAMLResponse is not valid base64-encoded XML");
doc = await (0, xml_1.parseDomFromString)(xml);
const inResponseToNodes = xml_1.xpath.selectAttributes(doc, "/*[local-name()='Response']/@InResponseTo");

@@ -509,4 +508,3 @@ if (inResponseToNodes) {

let validSignature = false;
if ((0, xml_1.validateSignature)(xml, doc.documentElement, certs) &&
Array.from(doc.childNodes).filter((n) => n.tagName != null && n.childNodes != null).length === 1) {
if ((0, xml_1.validateSignature)(xml, doc.documentElement, certs)) {
validSignature = true;

@@ -535,3 +533,3 @@ }

const decryptedXml = await (0, xml_1.decryptXml)(encryptedAssertionXml, this.options.decryptionPvk);
const decryptedDoc = (0, xml_1.parseDomFromString)(decryptedXml);
const decryptedDoc = await (0, xml_1.parseDomFromString)(decryptedXml);
const decryptedAssertions = xml_1.xpath.selectElements(decryptedDoc, "/*[local-name()='Assertion']");

@@ -548,7 +546,6 @@ if (decryptedAssertions.length != 1)

// LogoutResponse code.
const xmljsDoc = await (0, xml_1.parseXml2JsFromString)(xml);
const xmljsDoc = (await (0, xml_1.parseXml2JsFromString)(xml));
const response = xmljsDoc.Response;
if (response) {
const assertion = response.Assertion;
if (!assertion) {
if (!("Assertion" in response)) {
const status = response.Status;

@@ -558,6 +555,6 @@ if (status) {

if (statusCode &&
statusCode[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:Responder") {
((_a = statusCode[0].$) === null || _a === void 0 ? void 0 : _a.Value) === "urn:oasis:names:tc:SAML:2.0:status:Responder") {
const nestedStatusCode = statusCode[0].StatusCode;
if (nestedStatusCode &&
nestedStatusCode[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:NoPassive") {
((_b = nestedStatusCode[0].$) === null || _b === void 0 ? void 0 : _b.Value) === "urn:oasis:names:tc:SAML:2.0:status:NoPassive") {
if (!validSignature) {

@@ -572,11 +569,12 @@ throw new Error("Invalid signature: NoPassive");

// let's go ahead and give the potentially more helpful error.
if (statusCode && statusCode[0].$.Value) {
const msgType = statusCode[0].$.Value.match(/[^:]*$/)[0];
if (msgType != "Success") {
if (statusCode && ((_c = statusCode[0].$) === null || _c === void 0 ? void 0 : _c.Value)) {
const msgType = statusCode[0].$.Value.match(/[^:]*$/);
if (msgType && msgType[0] != "Success") {
let msg = "unspecified";
if (status[0].StatusMessage) {
msg = status[0].StatusMessage[0]._;
msg = status[0].StatusMessage[0]._ || msg;
}
else if (statusCode[0].StatusCode) {
msg = statusCode[0].StatusCode[0].$.Value.match(/[^:]*$/)[0];
const msgValues = (_d = statusCode[0].StatusCode[0].$) === null || _d === void 0 ? void 0 : _d.Value.match(/[^:]*$/);
msg = msgValues ? msgValues[0] : msg;
}

@@ -629,3 +627,3 @@ const statusXml = (0, xml_1.buildXml2JsObject)("Status", status[0]);

const inflated = await inflateRawAsync(data);
const dom = (0, xml_1.parseDomFromString)(inflated.toString());
const dom = await (0, xml_1.parseDomFromString)(inflated.toString());
const doc = await (0, xml_1.parseXml2JsFromString)(inflated);

@@ -922,3 +920,3 @@ samlMessageType === "SAMLResponse"

const xml = Buffer.from(container.SAMLRequest, "base64").toString("utf8");
const dom = (0, xml_1.parseDomFromString)(xml);
const dom = await (0, xml_1.parseDomFromString)(xml);
const doc = await (0, xml_1.parseXml2JsFromString)(xml);

@@ -925,0 +923,0 @@ const certs = await this.certsToCheck();

@@ -28,5 +28,32 @@ /// <reference types="node" />

export declare type XMLOutput = Record<string, any>;
export interface AuthorizeRequestXML {
export declare type AuthorizeRequestXML = {
"samlp:AuthnRequest": XMLInput;
}
};
export declare type XmlJsObject = {
[key: string]: string | XmlJsObject | XmlJsObject[] | undefined;
$?: {
Value: string;
};
_?: string;
};
export declare type SamlResponseXmlJs = XmlJsObject & {
Response?: SamlAssertionXmlJs | SamlStatusXmlJs;
LogoutResponse?: unknown;
};
export declare type SamlRequestXmlJs = {
Request: unknown;
};
export declare type SamlAssertionXmlJs = {
Assertion: unknown;
};
export declare type SamlStatusXmlJs = {
Status: [
{
StatusCode: [XmlJsObject & {
StatusCode: [XmlJsObject];
}];
StatusMessage: [XmlJsObject];
}
];
};
export declare type CertCallback = (callback: (err: Error | null, cert?: string | string[]) => void) => void;

@@ -49,3 +76,3 @@ /**

}
export interface LogoutRequestXML {
export declare type LogoutRequestXML = {
"samlp:LogoutRequest": {

@@ -55,4 +82,4 @@ "saml:NameID": XMLInput;

};
}
export interface ServiceMetadataXML {
};
export declare type ServiceMetadataXML = {
EntityDescriptor: {

@@ -62,3 +89,3 @@ [key: string]: XMLValue;

};
}
};
export interface NameID {

@@ -65,0 +92,0 @@ value: string | null;

/// <reference types="node" />
import { NameID, SamlSigningOptions, XMLOutput, XmlSignatureLocation } from "./types";
import { NameID, SamlSigningOptions, XmlJsObject, XMLOutput, XmlSignatureLocation } from "./types";
export declare const xpath: {

@@ -21,7 +21,7 @@ selectAttributes: (node: Node, xpath: string) => Attr[];

export declare const signXml: (xml: string, xpath: string, location: XmlSignatureLocation, options: SamlSigningOptions) => string;
export declare const parseDomFromString: (xml: string) => Document;
export declare const parseXml2JsFromString: (xml: string | Buffer) => Promise<XMLOutput>;
export declare const buildXml2JsObject: (rootName: string, xml: XMLOutput) => string;
export declare const parseDomFromString: (xml: string) => Promise<Document>;
export declare const parseXml2JsFromString: (xml: string | Buffer) => Promise<XmlJsObject>;
export declare const buildXml2JsObject: (rootName: string, xml: XmlJsObject) => string;
export declare const buildXmlBuilderObject: (xml: XMLOutput, pretty: boolean) => string;
export declare const promiseWithNameId: (nameid: Node) => Promise<NameID>;
export declare const getNameIdAsync: (doc: Node, decryptionPvk: string | Buffer | null) => Promise<NameID>;

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

const parseDomFromString = (xml) => {
return new xmldom.DOMParser().parseFromString(xml);
return new Promise(function (resolve, reject) {
function errHandler(msg) {
return reject(new Error(msg));
}
const dom = new xmldom.DOMParser({
/**
* locator is always need for error position info
*/
locator: {},
/**
* you can override the errorHandler for xml parser
* @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
*/
errorHandler: {
error: errHandler,
fatalError: errHandler,
},
}).parseFromString(xml, "text/xml");
if (!Object.prototype.hasOwnProperty.call(dom, "documentElement")) {
return reject(new Error("Not a valid XML document"));
}
if (Array.from(dom.childNodes).filter((n) => n.tagName != null && n.childNodes != null).length !== 1) {
return reject(new Error("Malformed XML; multiple roots detected"));
}
return resolve(dom);
});
};

@@ -204,3 +229,3 @@ exports.parseDomFromString = parseDomFromString;

const decryptedXml = await (0, exports.decryptXml)(encryptedDataXml, decryptionPvk);
const decryptedDoc = (0, exports.parseDomFromString)(decryptedXml);
const decryptedDoc = await (0, exports.parseDomFromString)(decryptedXml);
const decryptedIds = exports.xpath.selectElements(decryptedDoc, "/*[local-name()='NameID']");

@@ -207,0 +232,0 @@ if (decryptedIds.length !== 1) {

{
"name": "@node-saml/node-saml",
"version": "4.0.0-beta.6",
"version": "4.0.0",
"description": "SAML 2.0 implementation for Node.js",

@@ -39,5 +39,5 @@ "keywords": [

"changelog": "gren changelog --override --generate --head master",
"lint": "eslint --ext .ts \"**/*.ts\" --cache",
"lint": "eslint --ext .ts \"**/*.ts\" --cache && npm run prettier-check",
"lint-watch": "onchange -k -p 100 \"**/*.ts\" -- eslint {{file}}",
"lint:fix": "eslint --ext .ts --fix src",
"lint:fix": "eslint --ext .ts --fix src && npm run prettier-format",
"prepare": "tsc",

@@ -61,6 +61,6 @@ "prettier-check": "prettier --config .prettierrc.json --check .",

"@types/xml2js": "^0.4.11",
"@xmldom/xmldom": "^0.8.2",
"@xmldom/xmldom": "^0.8.3",
"debug": "^4.3.4",
"xml-crypto": "^2.1.4",
"xml-encryption": "^2.0.0",
"xml-crypto": "^3.0.0",
"xml-encryption": "^3.0.1",
"xml2js": "^0.4.23",

@@ -70,20 +70,20 @@ "xmlbuilder": "^15.1.1"

"devDependencies": {
"@cjbarth/github-release-notes": "^2.1.0",
"@cjbarth/github-release-notes": "^3.0.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/chai": "^4.3.3",
"@types/mocha": "^9.1.1",
"@types/node": "^14.18.22",
"@types/mocha": "^10.0.0",
"@types/node": "^14.18.32",
"@types/sinon": "^10.0.13",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.40.0",
"body-parser": "^1.20.0",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"body-parser": "^1.20.1",
"chai": "^4.3.6",
"choma": "^1.2.1",
"concurrently": "^7.3.0",
"eslint": "^8.23.0",
"concurrently": "^7.5.0",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-deprecation": "^1.3.2",
"eslint-plugin-prettier": "^4.2.1",
"express": "^4.18.1",
"mocha": "^10.0.0",
"express": "^4.18.2",
"mocha": "^10.1.0",
"nyc": "^15.1.0",

@@ -94,3 +94,3 @@ "onchange": "^7.1.0",

"release-it": "^15.5.0",
"sinon": "^14.0.0",
"sinon": "^14.0.1",
"ts-node": "^10.9.1",

@@ -97,0 +97,0 @@ "typescript": "^4.8.4"

@@ -67,3 +67,3 @@ # Node SAML

- `attributeConsumingServiceIndex`: optional `AttributeConsumingServiceIndex` attribute to add to AuthnRequest to instruct the IDP which attribute set to attach to the response ([link](http://blog.aniljohn.com/2014/01/data-minimization-front-channel-saml-attribute-requests.html))
- `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context. This is [known to help when authenticating against Active Directory](https://github.com/node-saml/passport-saml/issues/226) (AD FS) servers.
- `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context.
- `authnContext`: if truthy, name identifier format to request auth context (default: `urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport`); array of values is also supported

@@ -179,5 +179,5 @@ - `racComparison`: Requested Authentication Context comparison type. Possible values are 'exact','minimum','maximum','better'. Default is 'exact'.

Passport-SAML uses the HTTP Redirect Binding for its `AuthnRequest`s (unless overridden with the `authnRequestBinding` parameter), and expects to receive the messages back via the HTTP POST binding.
Node-SAML uses the HTTP Redirect Binding for its `AuthnRequest`s (unless overridden with the `authnRequestBinding` parameter), and expects to receive the messages back via the HTTP POST binding.
Authentication requests sent by Passport-SAML can be signed using RSA signature with SHA1, SHA256 or SHA512 hashing algorithms.
Authentication requests sent by Node-SAML can be signed using RSA signature with SHA1, SHA256 or SHA512 hashing algorithms.

@@ -251,26 +251,7 @@ To select hashing algorithm, use:

## Usage with Active Directory Federation Services
Here is a configuration that has been proven to work with ADFS:
```javascript
{
entryPoint: 'https://ad.example.net/adfs/ls/',
issuer: 'https://your-app.example.net/login/callback',
callbackUrl: 'https://your-app.example.net/login/callback',
cert: 'MIICizCCAfQCCQCY8tKaMc0BMjANBgkqh ... W==',
authnContext: 'http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/windows',
identifierFormat: null
}
```
Please note that ADFS needs to have a trust established to your service in order for this to work.
For more detailed instructions, see [ADFS documentation](docs/adfs/README.md).
## SAML Response Validation - NotBefore and NotOnOrAfter
If the `NotBefore` or the `NotOnOrAfter` attributes are returned in the SAML response, Passport-SAML will validate them
If the `NotBefore` or the `NotOnOrAfter` attributes are returned in the SAML response, Node-SAML will validate them
against the current time +/- a configurable clock skew value. The default for the skew is 0s. This is to account for
differences between the clock time on the client (Node server with Passport-SAML) and the server (Identity provider).
differences between the clock time on the client (Node server with Node-SAML) and the server (Identity provider).

@@ -282,4 +263,4 @@ `NotBefore` and `NotOnOrAfter` can be part of either the `SubjectConfirmation` element, or within in the `Assertion/Conditions` element

When configured (turn `validateInResponseTo` to `always` in the Passport-SAML config), the `InResponseTo` attribute will be validated.
Validation will succeed if Passport-SAML previously generated a SAML request with an id that matches the value of `InResponseTo`.
When configured (turn `validateInResponseTo` to `always` in the Node-SAML config), the `InResponseTo` attribute will be validated.
Validation will succeed if Node-SAML previously generated a SAML request with an id that matches the value of `InResponseTo`.

@@ -290,3 +271,3 @@ Also note that `InResponseTo` is validated as an attribute of the top level `Response` element in the SAML response, as well

Previous request id's generated for SAML requests will eventually expire. This is controlled with the `requestIdExpirationPeriodMs` option
passed into the Passport-SAML config. The default is 28,800,000 ms (8 hours). Once expired, a subsequent SAML response
passed into the Node-SAML config. The default is 28,800,000 ms (8 hours). Once expired, a subsequent SAML response
received with an `InResponseTo` equal to the expired id will not validate and an error will be returned.

@@ -318,3 +299,3 @@

Passport-SAML has built in support for SLO including
Node-SAML has built in support for SLO including

@@ -321,0 +302,0 @@ - Signature validation

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
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc