diff --git a/lib/middleware/initialize.js b/lib/middleware/initialize.js index 90b869b6..47ba7e86 100644 --- a/lib/middleware/initialize.js +++ b/lib/middleware/initialize.js @@ -52,7 +52,40 @@ module.exports = function initialize(passport, options) { if (options.userProperty) { req._userProperty = options.userProperty; } - + + var compat = (options.compat === undefined) ? true : options.compat; + if (compat) { + // `passport@0.5.1` [removed][1] all internal use of `req._passport`. + // From the standpoint of this package, this should have been a + // non-breaking change. However, some strategies (such as `passport-azure-ad`) + // depend directly on `passport@0.4.x` or earlier. `require`-ing earlier + // versions of `passport` has the effect of monkeypatching `http.IncomingMessage` + // with `logIn`, `logOut`, `isAuthenticated` and `isUnauthenticated` + // functions that [expect][2] the `req._passport` property to exist. + // Since pre-existing functions on `req` are given [preference][3], this + // results in [issues][4]. + // + // The changes here restore the expected properties needed when earlier + // versions of `passport` are `require`-ed. This compatibility mode is + // enabled by default, and can be disabld by simply not `use`-ing `passport.initialize()` + // middleware or setting `compat: false` as an option to the middleware. + // + // An alternative approach to addressing this issue would be to not + // preferentially use pre-existing functions on `req`, but rather always + // overwrite `req.logIn`, etc. with the versions of those functions shiped + // with `authenticate()` middleware. This option should be reconsidered + // in a future major version release. + // + // [1]: https://github.com/jaredhanson/passport/pull/875 + // [2]: https://github.com/jaredhanson/passport/blob/v0.4.1/lib/http/request.js + // [3]: https://github.com/jaredhanson/passport/blob/v0.5.1/lib/middleware/authenticate.js#L96 + // [4]: https://github.com/jaredhanson/passport/issues/877 + passport._userProperty = options.userProperty || 'user'; + + req._passport = {}; + req._passport.instance = passport; + } + next(); }; }; diff --git a/test/authenticator.middleware.test.js b/test/authenticator.middleware.test.js index bf6d0074..a6d77ef0 100644 --- a/test/authenticator.middleware.test.js +++ b/test/authenticator.middleware.test.js @@ -42,6 +42,14 @@ describe('Authenticator', function() { it('should not initialize namespace within session', function() { expect(request.session.passport).to.be.undefined; }); + + it('should expose authenticator on internal request property', function() { + expect(request._passport).to.be.an('object'); + expect(request._passport.instance).to.be.an.instanceOf(Authenticator); + expect(request._passport.instance).to.equal(passport); + expect(request._passport.instance._sm).to.be.an('object'); + expect(request._passport.instance._userProperty).to.equal('user'); + }); }); describe('handling a request with custom user property', function() { @@ -72,6 +80,14 @@ describe('Authenticator', function() { it('should not initialize namespace within session', function() { expect(request.session.passport).to.be.undefined; }); + + it('should expose authenticator on internal request property', function() { + expect(request._passport).to.be.an('object'); + expect(request._passport.instance).to.be.an.instanceOf(Authenticator); + expect(request._passport.instance).to.equal(passport); + expect(request._passport.instance._sm).to.be.an('object'); + expect(request._passport.instance._userProperty).to.equal('currentUser'); + }); }); }); diff --git a/test/middleware/initialize.test.js b/test/middleware/initialize.test.js index acef7a38..b572f93b 100644 --- a/test/middleware/initialize.test.js +++ b/test/middleware/initialize.test.js @@ -31,6 +31,14 @@ describe('middleware/initialize', function() { it('should not error', function() { expect(error).to.be.undefined; }); + + it('should expose authenticator on internal request property', function() { + expect(request._passport).to.be.an('object'); + expect(request._passport.instance).to.be.an.instanceOf(Passport); + expect(request._passport.instance).to.equal(passport); + expect(request._passport.instance._sm).to.be.an('object'); + expect(request._passport.instance._userProperty).to.equal('user'); + }); }); describe('handling a request with a new session', function() { @@ -58,6 +66,14 @@ describe('middleware/initialize', function() { it('should not initialize namespace within session', function() { expect(request.session.passport).to.be.undefined; }); + + it('should expose authenticator on internal request property', function() { + expect(request._passport).to.be.an('object'); + expect(request._passport.instance).to.be.an.instanceOf(Passport); + expect(request._passport.instance).to.equal(passport); + expect(request._passport.instance._sm).to.be.an('object'); + expect(request._passport.instance._userProperty).to.equal('user'); + }); }); describe('handling a request with an existing session', function() { @@ -89,6 +105,14 @@ describe('middleware/initialize', function() { expect(Object.keys(request.session.passport)).to.have.length(1); expect(request.session.passport.user).to.equal('123456'); }); + + it('should expose authenticator on internal request property', function() { + expect(request._passport).to.be.an('object'); + expect(request._passport.instance).to.be.an.instanceOf(Passport); + expect(request._passport.instance).to.equal(passport); + expect(request._passport.instance._sm).to.be.an('object'); + expect(request._passport.instance._userProperty).to.equal('user'); + }); }); describe('handling a request with an existing session using custom session key', function() { @@ -121,6 +145,45 @@ describe('middleware/initialize', function() { expect(Object.keys(request.session.authentication)).to.have.length(1); expect(request.session.authentication.user).to.equal('123456'); }); + + it('should expose authenticator on internal request property', function() { + expect(request._passport).to.be.an('object'); + expect(request._passport.instance).to.be.an.instanceOf(Passport); + expect(request._passport.instance).to.equal(passport); + expect(request._passport.instance._sm).to.be.an('object'); + expect(request._passport.instance._userProperty).to.equal('user'); + }); + }); + + describe('handling a request with a new session without compat mode', function() { + var passport = new Passport(); + var request, error; + + before(function(done) { + chai.connect.use(initialize(passport, { compat: false })) + .req(function(req) { + request = req; + + req.session = {}; + }) + .next(function(err) { + error = err; + done(); + }) + .dispatch(); + }); + + it('should not error', function() { + expect(error).to.be.undefined; + }); + + it('should not initialize namespace within session', function() { + expect(request.session.passport).to.be.undefined; + }); + + it('should expose authenticator on internal request property', function() { + expect(request._passport).to.be.undefined; + }); }); });