diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee index e53ee477b4..f3faad6c69 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee @@ -30,6 +30,24 @@ module.exports = AuthenticationController = deserializeUser: (user, cb) -> cb(null, user) + afterLoginSessionSetup: (req, user, callback=(err)->) -> + req.login user, (err) -> + # Regenerate the session to get a new sessionID (cookie value) to + # protect against session fixation attacks + oldSession = req.session + req.session.destroy() + req.sessionStore.generate(req) + for key, value of oldSession + req.session[key] = value + # copy to the old `session.user` location, for backward-comptability + req.session.user = req.session.passport.user + req.session.save (err) -> + if err? + logger.err {user_id: user._id}, "error saving regenerated session after login" + return callback(err) + UserSessionsManager.trackSession(user, req.sessionID, () ->) + callback(null) + passportLogin: (req, res, next) -> # This function is middleware which wraps the passport.authenticate middleware, # so we can send back our custom `{message: {text: "", type: ""}}` responses on failure, @@ -38,22 +56,10 @@ module.exports = AuthenticationController = if err? return next(err) if user # `user` is either a user object or false - req.login user, (err) -> - # Regenerate the session to get a new sessionID (cookie value) to - # protect against session fixation attacks - oldSession = req.session - req.session.destroy() - req.sessionStore.generate(req) - for key, value of oldSession - req.session[key] = value - # copy to the old `session.user` location, for backward-comptability - req.session.user = req.session.passport.user - req.session.save (err) -> - if err? - logger.err {user_id: user._id}, "error saving regenerated session after login" - return next(err) - UserSessionsManager.trackSession(user, req.sessionID, () ->) - res.json {redir: req._redir} + AuthenticationController.afterLoginSessionSetup req, user, (err) -> + if err? + return next(err) + res.json {redir: req._redir} else res.json message: info )(req, res, next) diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index 7d551f89b3..e19d9c5ca8 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -106,6 +106,10 @@ passport.use(new LocalStrategy( passport.serializeUser(AuthenticationController.serializeUser) passport.deserializeUser(AuthenticationController.deserializeUser) +Modules.hooks.fire 'passportSetup', passport, (err) -> + if err? + logger.err {err}, "error setting up passport in modules" + # Measure expiry from last request, not last login webRouter.use (req, res, next) -> req.session.touch() diff --git a/services/web/package.json b/services/web/package.json index cca2733643..d302cc30d8 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -47,6 +47,7 @@ "nodemailer-ses-transport": "^1.3.0", "optimist": "0.6.1", "passport": "^0.3.2", + "passport-ldapauth": "^0.6.0", "passport-local": "^1.0.0", "pg": "^6.0.3", "pg-hstore": "^2.3.2", diff --git a/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee b/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee index 3a697441cb..104d2e99c3 100644 --- a/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee @@ -157,6 +157,56 @@ describe "AuthenticationController", -> @res.json.callCount.should.equal 1 @res.json.calledWith({message: @info}).should.equal true + describe 'afterLoginSessionSetup', -> + + beforeEach -> + @req.login = sinon.stub().callsArgWith(1, null) + @req.session = @session = {passport: {user: @user}} + @req.session = + passport: {user: {_id: "one"}} + @req.session.destroy = sinon.stub() + @req.session.save = sinon.stub().callsArgWith(0, null) + @req.sessionStore = {generate: sinon.stub()} + @UserSessionsManager.trackSession = sinon.stub() + @call = (callback) => + @AuthenticationController.afterLoginSessionSetup @req, @user, callback + + it 'should not produce an error', (done) -> + @call (err) => + expect(err).to.equal null + done() + + it 'should call req.login', (done) -> + @call (err) => + @req.login.callCount.should.equal 1 + done() + + it 'should call req.session.save', (done) -> + @call (err) => + @req.session.save.callCount.should.equal 1 + done() + + it 'should call UserSessionsManager.trackSession', (done) -> + @call (err) => + @UserSessionsManager.trackSession.callCount.should.equal 1 + done() + + describe 'when req.session.save produces an error', -> + + beforeEach -> + @req.session.save = sinon.stub().callsArgWith(0, new Error('woops')) + + it 'should produce an error', (done) -> + @call (err) => + expect(err).to.not.be.oneOf [null, undefined] + expect(err).to.be.instanceof Error + done() + + it 'should not call UserSessionsManager.trackSession', (done) -> + @call (err) => + @UserSessionsManager.trackSession.callCount.should.equal 0 + done() + describe 'getSessionUser', -> it 'should get the user object from session', ->