Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ jobs:

strategy:
matrix:
node-version: [12.x, 14.x, 15.x, 16.x]
node-version: [22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Semgrep

on:
pull_request_target: {}
pull_request: {}
push:
branches: ["master", "main"]
permissions:
Expand All @@ -17,4 +17,4 @@ jobs:
- uses: actions/checkout@v3
- run: semgrep ci
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules/*
.DS_Store
coverage/*
.nyc_output
package-lock.json
.idea
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [7.2.0](https://github.com/DataRecognitionCorporation/node-samlp/compare/v7.1.1...v7.2.0) (2026-04-02)


### ⚠ BREAKING CHANGES

* Requires Node.js >= 22


### Bug Fixes

* fix metadata endpoint ignoring `X-Forwarded-Host` header for endpoint URLs
* fix test compatibility with Node.js 22 / OpenSSL 3 (dynamic redirect signature, updated error regex)


### Dependency Updates

* saml upgraded to ^4.0.0
* @auth0/xmldom updated to 0.1.23
* ejs updated to ^3.1.10
* mocha pinned to 11.3.0
* replace istanbul with nyc@15 for coverage
* update CI workflow to Node.js 22.x; upgrade actions/checkout and actions/setup-node to v4


### [7.1.1](https://github.com/auth0/node-samlp/compare/v7.1.0...v7.1.1) (2023-11-20)


Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SAML Protocol middleware to create SAMLP identity providers for node.js.

### Supported Node Versions

node >= 12
node >= 22

## Introduction

Expand Down Expand Up @@ -154,6 +154,18 @@ samlp.sendError({
})(req, res, next);
~~~~

## Testing

Tests require Node.js >= 22 and are run with Mocha:

npm test

The test suite starts a real Express server (`test/fixture/server.js`) on `http://localhost:5050` before each test file runs and tears it down afterward. The server mounts the actual `samlp` middleware against a hardcoded fake user and fixture certificate/key pair (`test/fixture/samlp.test-cert.{pem,key}`). Tests make real HTTP requests to this server using the `request` library and assert against the responses.

Individual test suites reconfigure the server between tests by directly mutating `server.options`, which is merged into the middleware options on each request — allowing cert, key, signing, and session-participant settings to be swapped without restarting the server.

Test certificates and keys in `test/fixture/` are for local testing only and must not be used in production.

## Issue Reporting

If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues.
Expand Down
11 changes: 6 additions & 5 deletions lib/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ var encoders = require('./encoders');

var URL_PATH = '/FederationMetadata/2007-06/FederationMetadata.xml';

function getEndpointAddress (req, endpointPath) {
function getEndpointAddress (req, endpointPath, absoluteUrls) {
endpointPath = endpointPath ||
(req.originalUrl.substr(0, req.originalUrl.length - URL_PATH.length));

var protocol = req.headers['x-iisnode-https'] && req.headers['x-iisnode-https'] == 'on' ?
'https' :
(req.headers['x-forwarded-proto'] || req.protocol);
var host = req.headers['x-forwarded-host'] || req.headers['host'];
return protocol + '://' + host + endpointPath;
return absoluteUrls ? endpointPath : protocol + '://' + host + endpointPath;
}

/**
Expand Down Expand Up @@ -50,17 +50,18 @@ function metadataMiddleware (options) {
var claimTypes = (options.profileMapper || PassportProfileMapper).prototype.metadata;
var issuer = options.issuer;
var pem = encoders.removeHeaders(options.cert);
var absoluteUrls = options.absoluteUrls || false;

return function (req, res) {
var redirectEndpoint = getEndpointAddress(req, options.redirectEndpointPath);
var postEndpoint = getEndpointAddress(req, options.postEndpointPath);
var redirectEndpoint = getEndpointAddress(req, options.redirectEndpointPath, absoluteUrls);
var postEndpoint = getEndpointAddress(req, options.postEndpointPath, absoluteUrls);

options.logoutEndpointPaths = options.logoutEndpointPaths || { redirect: '/logout' };

var logoutEndpoints = {};
['redirect', 'post'].forEach(function (binding) {
if (options.logoutEndpointPaths[binding]) {
logoutEndpoints[binding] = getEndpointAddress(req, options.logoutEndpointPaths[binding]);
logoutEndpoints[binding] = getEndpointAddress(req, options.logoutEndpointPaths[binding], absoluteUrls);
}
});

Expand Down
44 changes: 36 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "samlp",
"version": "7.1.1",
"version": "7.2.0",
"engines": {
"node": ">=12"
"node": ">=22"
},
"description": "SAML Protocol server middleware",
"main": "lib/index.js",
"scripts": {
"test": "./node_modules/.bin/_mocha -R spec --colors",
"cover": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- -R spec --colors",
"cover": "nyc ./node_modules/.bin/_mocha -R spec --colors",
"open_cover": "open coverage/lcov-report/*.html",
"release": "standard-version"
},
Expand All @@ -25,12 +25,12 @@
"dependencies": {
"@auth0/thumbprint": "0.0.6",
"auth0-id-generator": "^0.2.0",
"ejs": "^3.1.8",
"ejs": "^3.1.10",
"flowstate": "^0.4.0",
"querystring": "^0.2.0",
"saml": "^3.0.1",
"saml": "^4.0.0",
"xml-crypto": "^2.0.0",
"@auth0/xmldom": "0.1.21",
"@auth0/xmldom": "0.1.23",
"xpath": "0.0.5",
"xtend": "^1.0.3"
},
Expand All @@ -41,11 +41,39 @@
"cheerio-select": "~0.0.3",
"express": "^4.17.1",
"express-session": "^1.14.2",
"istanbul": "^0.4.5",
"mocha": "~8.2.1",
"nyc": "^15.1.0",
"mocha": "11.3.0",
"request": "~2.88.0",
"standard-version": "^9.1.0",
"timekeeper": "^2.2.0",
"uid2": "0.0.3"
},
"overrides": {
"@xmldom/xmldom": "^0.8.12",
"underscore": "^1.13.8",
"serialize-javascript": "^7.0.3",
"nth-check": "^2.0.1"
},
"nyc": {
"check-coverage": true,
"per-file": true,
"lines": 70,
"statements": 70,
"functions": 70,
"branches": 70,
"extension": [
".js"
],
"exclude": [
"test/**/*.js",
"coverage/**/*.js"
],
"reporter": [
"html",
"lcov",
"text"
],
"all": false,
"report-dir": "./coverage"
}
}
24 changes: 17 additions & 7 deletions test/samlp.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ var cheerio = require('cheerio');
var xmldom = require('@auth0/xmldom');
var xmlhelper = require('./xmlhelper');
var zlib = require('zlib');
var crypto = require('crypto');
var querystring = require('querystring');
var encoder = require('../lib/encoders');
var fs = require('fs');
var path = require('path');
Expand Down Expand Up @@ -604,18 +606,26 @@ describe('samlp', function () {

before(function (done) {
var SAMLRequest = '<?xml version="1.0" encoding="UTF-8"?><samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://acs" Destination="https://destination" ID="12345" IssueInstant="2013-04-28T22:43:42.386Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp</saml:Issuer></samlp:AuthnRequest>';
var signingKey = fs.readFileSync(path.join(__dirname, 'fixture/samlp.test-cert.key'));
var sigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';

zlib.deflateRaw(new Buffer(SAMLRequest), function (err, buffer) {
zlib.deflateRaw(Buffer.from(SAMLRequest), function (err, buffer) {
if (err) return done(err);

var b64SAMLRequest = buffer.toString('base64');
var content = querystring.stringify({ SAMLRequest: b64SAMLRequest, RelayState: '123', SigAlg: sigAlg });
var signer = crypto.createSign('RSA-SHA1');
signer.update(content);
var signature = signer.sign(signingKey, 'base64');

request.get({
jar: request.jar(),
uri: 'http://localhost:5050/samlp',
qs: {
RelayState: '123',
SAMLRequest: buffer.toString('base64'),
Signature: 'HaX739zOyRn4PR2pi1Bud05rHbPGfppz5x5crr2EuOzLbfNuvLeK//ZCNsC/R/8B4CWe2SYYCYJ6UhBRvhCx8G7H92TIw8TjbsTfAWemp6mJh+zBqaI2It8sFZMYntsbd0jfBo4CbuM8872cNQkdedV5V56gaErjBA8z3HoyTWpQi9nH2fjtmDDfoQmoVum5q+vgbm103qxjH0j/gR+OXi5Rne8ijMLhhXgt9EdLmN8OS6l1LRUPe3XDLz6ZKbo9T2k6GR1x+w6bN18JOdeCwDn+nx4fmPbGGrcz/DT/3mTL5MY7TeRDz8rGSCZ5+yDNtmgQ9Nv2O//joonmRBkF6Q==',
SigAlg: 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
SAMLRequest: b64SAMLRequest,
Signature: signature,
SigAlg: sigAlg
}
}, function (err, response, b){
if(err) return done(err);
Expand All @@ -626,7 +636,7 @@ describe('samlp', function () {
body = b;
$ = cheerio.load(body);
var SAMLResponse = $('input[name="SAMLResponse"]').attr('value');
samlResponse = new Buffer(SAMLResponse, 'base64').toString();
samlResponse = Buffer.from(SAMLResponse, 'base64').toString();
signedAssertion = /(<saml:Assertion.*<\/saml:Assertion>)/.exec(samlResponse)[1];
done();
});
Expand Down Expand Up @@ -687,7 +697,7 @@ describe('samlp', function () {
it('should return an error', function (done) {
doRawSAMLRequest(function (response) {
expect(response.statusCode).to.equal(400);
expect(response.body).to.match(/error:\w+:PEM routines:\w+:no start line/);
expect(response.body).to.match(/(PEM routines|DECODER routines)/);
done();
});
});
Expand Down Expand Up @@ -736,7 +746,7 @@ describe('samlp', function () {
it('should return an error', function (done) {
doRawSAMLRequest(function (response) {
expect(response.statusCode).to.equal(400);
expect(response.body).to.match(/error:\w+:PEM routines:\w+:no start line/);
expect(response.body).to.match(/(PEM routines|DECODER routines)/);
done();
});
});
Expand Down
Loading