From de3370602175b5e63b1d6d28c4d6439839fcc550 Mon Sep 17 00:00:00 2001 From: Damian Fortuna Date: Fri, 25 Feb 2022 19:46:32 -0300 Subject: [PATCH] fix: some request properties are not being passed to hawk in Node16 Due to https://github.com/nodejs/node/issues/36550 (it was reverted in Node 14 but it is back in Node 16). req.headers and other properties are not own properties of requests so they are not cloned. Node documentation (see discussion on https://github.com/nodejs/node/issues/36550) advices against clonning the req (or any stream object). This PR fixes that without modifying the original request object by creating a new object which prototype is the original request object instead of trying to clone it. --- .gitignore | 3 +- lib/strategy.js | 44 ++++++++----- test/bewit.tests.js | 145 ++++++++++++++++++++++++++----------------- test/header.tests.js | 134 ++++++++++++++++++++++----------------- test/reqMock.js | 9 +++ 5 files changed, 203 insertions(+), 132 deletions(-) create mode 100644 test/reqMock.js diff --git a/.gitignore b/.gitignore index dbf0821..9ea26ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules/* \ No newline at end of file +node_modules/* +package-lock.json \ No newline at end of file diff --git a/lib/strategy.js b/lib/strategy.js index 621650b..22ef836 100644 --- a/lib/strategy.js +++ b/lib/strategy.js @@ -1,11 +1,11 @@ /** * Module dependencies. */ -var passport = require('passport'), - util = require('util'), - hawk = require('hawk'); +var passport = require("passport"), + util = require("util"), + hawk = require("hawk"); -var xtend = require('xtend'); +var xtend = require("xtend"); /** * `Strategy` constructor. @@ -36,15 +36,18 @@ var xtend = require('xtend'); * @api public */ function Strategy(bewit, verify) { - if (typeof bewit == 'function') { + if (typeof bewit == "function") { verify = bewit; bewit = false; } - if (typeof bewit == 'object') { + if (typeof bewit == "object") { bewit = bewit.bewit; } - if (!verify) throw new Error('HTTP Hawk authentication strategy requires a verify function'); + if (!verify) + throw new Error( + "HTTP Hawk authentication strategy requires a verify function" + ); this.verify = verify; this.bewit = bewit; passport.Strategy.call(this); @@ -62,19 +65,26 @@ util.inherits(Strategy, passport.Strategy); * @param {Object} req * @api protected */ -Strategy.prototype.authenticate = function(req, opts) { +Strategy.prototype.authenticate = function (req, opts) { //express change req.url when mounting with app.use //this creates a new request object with url = originalUrl - req = xtend({}, req, { url: req.originalUrl || req.url }); - var authenticate = this.bewit ? 'authenticateBewit' : 'authenticate'; - hawk.server[authenticate](req, this.verify, opts || {}, function(err, credentials, ext) { - if (err) { - if (err.isMissing) return this.fail('Missing authentication tokens'); - return this.error(err); // Return hawk error - } - return this.success(credentials.user, ext); - }.bind(this)); + req = Object.create(req); + req.url = req.originalUrl || req.url; + + var authenticate = this.bewit ? "authenticateBewit" : "authenticate"; + hawk.server[authenticate]( + req, + this.verify, + opts || {}, + function (err, credentials, ext) { + if (err) { + if (err.isMissing) return this.fail("Missing authentication tokens"); + return this.error(err); // Return hawk error + } + return this.success(credentials.user, ext); + }.bind(this) + ); }; /** diff --git a/test/bewit.tests.js b/test/bewit.tests.js index c3cb3e9..4ceb82b 100644 --- a/test/bewit.tests.js +++ b/test/bewit.tests.js @@ -1,102 +1,135 @@ -var HawkStrategy = require('../lib/strategy'), - Hawk = require('hawk'), - should = require('should'); +var HawkStrategy = require("../lib/strategy"), + Hawk = require("hawk"), + should = require("should"), + buildRequest = require("./reqMock").buildRequest; var credentials = { - key: 'abcd', - algorithm: 'sha256', - user: 'tito', - id: 'dasd123' + key: "abcd", + algorithm: "sha256", + user: "tito", + id: "dasd123", }; -var strategy = new HawkStrategy({ bewit: true }, function(id, done) { +var strategy = new HawkStrategy({ bewit: true }, function (id, done) { if (id === credentials.id) return done(null, credentials); return done(null, null); }); +describe("passport-hawk with bewit", function () { + it("can authenticate a request with a correct header", function (testDone) { + var bewit = Hawk.uri.getBewit( + "http://example.com:8080/resource/4?filter=a", + { + credentials: credentials, + ttlSec: 60 * 5, + } + ); + var req = buildRequest({ + headers: { + host: "example.com:8080", + }, + method: "GET", + url: "/resource/4?filter=a&bewit=" + bewit, + }); -describe('passport-hawk with bewit', function() { + strategy.success = function (user) { + user.should.eql("tito"); + testDone(); + }; - it('can authenticate a request with a correct header', function(testDone) { - var bewit = Hawk.uri.getBewit('http://example.com:8080/resource/4?filter=a', { - credentials: credentials, - ttlSec: 60 * 5 - }); - var req = { + strategy.error = function () { + testDone(new Error(arguments)); + }; + strategy.authenticate(req); + }); + + it("does not modifies req.url when is available", function (testDone) { + var bewit = Hawk.uri.getBewit( + "http://example.com:8080/resource/4?filter=a", + { + credentials: credentials, + ttlSec: 60 * 5, + } + ); + var req = buildRequest({ headers: { - host: 'example.com:8080' + host: "example.com:8080", }, - method: 'GET', - url: '/resource/4?filter=a&bewit=' + bewit - }; + method: "GET", + url: "/abc", + originalUrl: "/resource/4?filter=a&bewit=" + bewit, + }); + + strategy.success = function (user) { + req.url.should.eql("/abc"); - strategy.success = function(user) { - user.should.eql('tito'); testDone(); }; - strategy.error = function() { + strategy.error = function () { testDone(new Error(arguments)); }; strategy.authenticate(req); }); - it('should properly fail with correct challenge code when using different url', function(testDone) { - var bewit = Hawk.uri.getBewit('http://example.com:8080/resource/4?filter=a' + bewit, { - credentials: credentials, - ttlSec: 60 * 5 - }); - var req = { + it("should properly fail with correct challenge code when using different url", function (testDone) { + var bewit = Hawk.uri.getBewit( + "http://example.com:8080/resource/4?filter=a" + bewit, + { + credentials: credentials, + ttlSec: 60 * 5, + } + ); + var req = buildRequest({ headers: { - host: 'example.com:8080' + host: "example.com:8080", }, - method: 'GET', - url: '/resource/4?filter=a&bewit=' + bewit - }; - strategy.error = function(challenge) { - challenge.message.should.eql('Bad mac'); + method: "GET", + url: "/resource/4?filter=a&bewit=" + bewit, + }); + strategy.error = function (challenge) { + challenge.message.should.eql("Bad mac"); testDone(); }; strategy.authenticate(req); }); - it('should call done with false when the id doesnt exist', function(testDone) { - var bewit = Hawk.uri.getBewit('http://example.com:8080/foobar', { + it("should call done with false when the id doesnt exist", function (testDone) { + var bewit = Hawk.uri.getBewit("http://example.com:8080/foobar", { credentials: { - id: '321321', - key: 'dsa', - algorithm: 'sha256' + id: "321321", + key: "dsa", + algorithm: "sha256", }, - ttlSec: 60 * 5 + ttlSec: 60 * 5, }); - var req = { + var req = buildRequest({ headers: { - host: 'example.com:8080' + host: "example.com:8080", }, - method: 'GET', - url: '/resource/4?filter=a&bewit=' + bewit - }; + method: "GET", + url: "/resource/4?filter=a&bewit=" + bewit, + }); - strategy.error = function(challenge) { - challenge.message.should.eql('Unknown credentials'); + strategy.error = function (challenge) { + challenge.message.should.eql("Unknown credentials"); testDone(); }; strategy.authenticate(req); }); - it('should call fail when url doesnt have a bewit', function(testDone) { - - var req = { + it("should call fail when url doesnt have a bewit", function (testDone) { + var req = buildRequest({ headers: { - host: 'example.com:8080' + host: "example.com:8080", }, - method: 'GET', - url: '/resource/4?filter=a' - }; + method: "GET", + url: "/resource/4?filter=a", + }); - strategy.fail = function(failure) { - failure.should.eql('Missing authentication tokens'); + strategy.fail = function (failure) { + failure.should.eql("Missing authentication tokens"); testDone(); }; strategy.authenticate(req); diff --git a/test/header.tests.js b/test/header.tests.js index 61c2382..46eb8ec 100644 --- a/test/header.tests.js +++ b/test/header.tests.js @@ -1,107 +1,125 @@ -var HawkStrategy = require('../lib/strategy'), - Hawk = require('hawk'), - should = require('should'); +var HawkStrategy = require("../lib/strategy"), + Hawk = require("hawk"), + should = require("should"), + buildRequest = require("./reqMock").buildRequest; var credentials = { - key: 'abcd', - algorithm: 'sha256', - user: 'tito', - id: 'dasd123' + key: "abcd", + algorithm: "sha256", + user: "tito", + id: "dasd123", }; -var strategy = new HawkStrategy(function(id, done) { +var strategy = new HawkStrategy(function (id, done) { if (id === credentials.id) return done(null, credentials); return done(null, null); }); -describe('passport-hawk', function() { - it('can authenticate a request with a correct header', function(testDone) { - var header = Hawk.client.header('http://example.com:8080/resource/4?filter=a', 'GET', { credentials: credentials }); - var req = { +describe("passport-hawk", function () { + it("can authenticate a request with a correct header", function (testDone) { + var header = Hawk.client.header( + "http://example.com:8080/resource/4?filter=a", + "GET", + { credentials: credentials } + ); + var req = buildRequest({ headers: { authorization: header.field, - host: 'example.com:8080' + host: "example.com:8080", }, - method: 'GET', - url: '/resource/4?filter=a' - }; - strategy.success = function(user) { - user.should.eql('tito'); + method: "GET", + url: "/resource/4?filter=a", + }); + strategy.success = function (user) { + user.should.eql("tito"); testDone(); }; strategy.authenticate(req); }); - it('should properly fail with correct challenge code when using different url', function(testDone) { - var header = Hawk.client.header('http://example.com:8080/resource/4?filter=a', 'GET', { credentials: credentials }); - var req = { + it("should properly fail with correct challenge code when using different url", function (testDone) { + var header = Hawk.client.header( + "http://example.com:8080/resource/4?filter=a", + "GET", + { credentials: credentials } + ); + var req = buildRequest({ headers: { authorization: header.field, - host: 'example.com:9090' + host: "example.com:9090", }, - method: 'GET', - url: '/resource/4?filter=a' - }; - strategy.error = function(challenge) { - challenge.message.should.eql('Bad mac'); + method: "GET", + url: "/resource/4?filter=a", + }); + strategy.error = function (challenge) { + challenge.message.should.eql("Bad mac"); testDone(); }; strategy.authenticate(req); }); - it('should call done with false when the id doesnt exist', function(testDone) { + it("should call done with false when the id doesnt exist", function (testDone) { var testCredentials = { - id: '321321', - key: 'dsa', - algorithm: 'sha256' - } - var authHeader = Hawk.client.header('http://example.com:8080/resource/4?filter=a', 'POST', { credentials: testCredentials }); - var req = { + id: "321321", + key: "dsa", + algorithm: "sha256", + }; + var authHeader = Hawk.client.header( + "http://example.com:8080/resource/4?filter=a", + "POST", + { credentials: testCredentials } + ); + var req = buildRequest({ headers: { authorization: authHeader.field, - host: 'example.com:8080' + host: "example.com:8080", }, - method: 'GET', - url: '/resource/4?filter=a' - }; + method: "GET", + url: "/resource/4?filter=a", + }); - strategy.error = function(challenge) { - challenge.message.should.eql('Unknown credentials'); + strategy.error = function (challenge) { + challenge.message.should.eql("Unknown credentials"); testDone(); }; strategy.authenticate(req); }); - it('should fail with a stale request', function(testDone) { - var fixedHeader = 'Hawk id="dasd123", ts="1366220539", nonce="xVO62D", mac="9x+7TGN6VLRH8zX5PpwewpIzvf+mTt8m7PDQQW2NU/U="'; - var req = { + it("should fail with a stale request", function (testDone) { + var fixedHeader = + 'Hawk id="dasd123", ts="1366220539", nonce="xVO62D", mac="9x+7TGN6VLRH8zX5PpwewpIzvf+mTt8m7PDQQW2NU/U="'; + var req = buildRequest({ headers: { authorization: fixedHeader, - host: 'example.com:8080' + host: "example.com:8080", }, - method: 'GET', - url: '/resource/4?filter=a' - }; - strategy.error = function(challenge) { - challenge.message.should.eql('Stale timestamp'); + method: "GET", + url: "/resource/4?filter=a", + }); + strategy.error = function (challenge) { + challenge.message.should.eql("Stale timestamp"); testDone(); }; strategy.authenticate(req); }); - it('can authenticate a request with options', function(testDone) { - var header = Hawk.client.header('https://example.com/resource/4?filter=a', 'GET', { credentials: credentials }); - var req = { + it("can authenticate a request with options", function (testDone) { + var header = Hawk.client.header( + "https://example.com/resource/4?filter=a", + "GET", + { credentials: credentials } + ); + var req = buildRequest({ headers: { authorization: header.field, - host: 'example.com:3000' + host: "example.com:3000", }, - method: 'GET', - url: '/resource/4?filter=a' - }; + method: "GET", + url: "/resource/4?filter=a", + }); var opts = { port: 443 }; - strategy.success = function(user) { - user.should.eql('tito'); + strategy.success = function (user) { + user.should.eql("tito"); testDone(); }; strategy.authenticate(req, opts); diff --git a/test/reqMock.js b/test/reqMock.js new file mode 100644 index 0000000..440136c --- /dev/null +++ b/test/reqMock.js @@ -0,0 +1,9 @@ +exports.buildRequest = function buildRequest(reqProps) { + const obj = Object.create({ headers: reqProps.headers }); + + return Object.assign(obj, { + method: reqProps.method, + url: reqProps.url, + originalUrl: reqProps.originalUrl, + }); +};