Merge pull request #2276 from overleaf/bg-reject-invalid-sessions

reject invalid sessions

GitOrigin-RevId: 5dc59609d01d7ad9bc29f9bf18faee1165d10689
This commit is contained in:
Brian Gough 2019-10-22 10:08:49 +01:00 committed by sharelatex
parent 8ffaa5b0ca
commit 45ebc42bf6
5 changed files with 102 additions and 0 deletions

View file

@ -8,6 +8,7 @@ const Settings = require('settings-sharelatex')
const basicAuth = require('basic-auth-connect')
const UserHandler = require('../User/UserHandler')
const UserSessionsManager = require('../User/UserSessionsManager')
const SessionStoreManager = require('../../infrastructure/SessionStoreManager')
const Analytics = require('../Analytics/AnalyticsManager')
const passport = require('passport')
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
@ -309,6 +310,29 @@ const AuthenticationController = (module.exports = {
}
},
validateUserSession: function() {
// Middleware to check that the user's session is still good on key actions,
// such as opening a a project. Could be used to check that session has not
// exceeded a maximum lifetime (req.session.session_created), or for session
// hijacking checks (e.g. change of ip address, req.session.ip_address). For
// now, just check that the session has been loaded from the session store
// correctly.
return function(req, res, next) {
// check that the session store is returning valid results
if (req.session && !SessionStoreManager.hasValidationToken(req)) {
// force user to update session
req.session.regenerate(() => {
// need to destroy the existing session and generate a new one
// otherwise they will already be logged in when they are redirected
// to the login page
AuthenticationController._redirectToLoginOrRegisterPage(req, res)
})
} else {
next()
}
}
},
_globalLoginWhitelist: [],
addEndpointToLoginWhitelist(endpoint) {
return AuthenticationController._globalLoginWhitelist.push(endpoint)

View file

@ -61,5 +61,13 @@ module.exports = {
} else {
return next()
}
},
hasValidationToken(req) {
if (req && req.session && req.session.validationToken) {
return true
} else {
return false
}
}
}

View file

@ -288,6 +288,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
maxRequests: 15,
timeInterval: 60
}),
AuthenticationController.validateUserSession(),
AuthorizationMiddleware.ensureUserCanReadProject,
ProjectController.loadEditor
)

View file

@ -33,6 +33,7 @@ describe('AuthenticationController', function() {
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
recordEvent: sinon.stub()
}),
'../../infrastructure/SessionStoreManager': (this.SessionStoreManager = {}),
'logger-sharelatex': (this.logger = {
log: sinon.stub(),
warn: sinon.stub(),
@ -582,6 +583,54 @@ describe('AuthenticationController', function() {
})
})
describe('validateUserSession', function() {
beforeEach(function() {
this.user = {
_id: 'user-id-123',
email: 'user@sharelatex.com'
}
this.middleware = this.AuthenticationController.validateUserSession()
})
describe('when the user has a session token', function() {
beforeEach(function() {
this.req.user = this.user
this.SessionStoreManager.hasValidationToken = sinon.stub().returns(true)
this.middleware(this.req, this.res, this.next)
})
it('should call the next method in the chain', function() {
this.next.called.should.equal(true)
})
})
describe('when the user does not have a session token', function() {
beforeEach(function() {
this.req.session = {
user: this.user,
regenerate: sinon.stub().yields()
}
this.req.user = this.user
this.AuthenticationController._redirectToLoginOrRegisterPage = sinon.stub()
this.req.query = {}
this.SessionStoreManager.hasValidationToken = sinon
.stub()
.returns(false)
this.middleware(this.req, this.res, this.next)
})
it('should destroy the current session', function() {
this.req.session.regenerate.called.should.equal(true)
})
it('should redirect to the register or login page', function() {
this.AuthenticationController._redirectToLoginOrRegisterPage
.calledWith(this.req, this.res)
.should.equal(true)
})
})
})
describe('requireOauth', function() {
beforeEach(function() {
this.res.send = sinon.stub()

View file

@ -84,4 +84,24 @@ describe('SessionStoreManager', function() {
expect(this.next).to.be.calledWithExactly()
})
})
describe('hasValidationToken', function() {
this.beforeEach(function() {
this.req = { sessionID: '123456789' }
})
it('should return true when the session is valid', function() {
this.req.session = { validationToken: 'v1:6789' }
const result = this.SessionStoreManager.hasValidationToken(this.req)
expect(result).to.equal(true)
})
it('should return false when the session is valid', function() {
this.req.session = { validationToken: 'v1:abcd' }
const result = this.SessionStoreManager.hasValidationToken(this.req)
expect(result).to.equal(true)
})
it('should return false when the validation token is missing', function() {
this.req.session = {}
const result = this.SessionStoreManager.hasValidationToken(this.req)
expect(result).to.equal(false)
})
})
})