mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #4112 from overleaf/tm-private-api-basic-auth
Add requireBasicAuth middleware and refactor httpAuth to use it GitOrigin-RevId: 7f68c0dc4a40102bfe4a97711def517e465ec7fd
This commit is contained in:
parent
d6454a84bb
commit
95c83866c5
6 changed files with 81 additions and 50 deletions
|
@ -16,7 +16,7 @@ module.exports = {
|
||||||
|
|
||||||
publicApiRouter.use(
|
publicApiRouter.use(
|
||||||
'/analytics/uniExternalCollaboration',
|
'/analytics/uniExternalCollaboration',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
AnalyticsProxy.call('/uniExternalCollaboration')
|
AnalyticsProxy.call('/uniExternalCollaboration')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -316,7 +316,7 @@ const AuthenticationController = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.headers.authorization != null) {
|
if (req.headers.authorization != null) {
|
||||||
AuthenticationController.httpAuth(req, res, next)
|
AuthenticationController.requirePrivateApiAuth()(req, res, next)
|
||||||
} else if (AuthenticationController.isUserLoggedIn(req)) {
|
} else if (AuthenticationController.isUserLoggedIn(req)) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
|
@ -361,17 +361,23 @@ const AuthenticationController = {
|
||||||
return next()
|
return next()
|
||||||
},
|
},
|
||||||
|
|
||||||
httpAuth: basicAuth(function (user, pass) {
|
requireBasicAuth: function (userDetails) {
|
||||||
const expectedPassword = Settings.httpAuthUsers[user]
|
return basicAuth(function (user, pass) {
|
||||||
const isValid =
|
const expectedPassword = userDetails[user]
|
||||||
expectedPassword &&
|
const isValid =
|
||||||
expectedPassword.length === pass.length &&
|
expectedPassword &&
|
||||||
crypto.timingSafeEqual(Buffer.from(expectedPassword), Buffer.from(pass))
|
expectedPassword.length === pass.length &&
|
||||||
if (!isValid) {
|
crypto.timingSafeEqual(Buffer.from(expectedPassword), Buffer.from(pass))
|
||||||
logger.err({ user, pass }, 'invalid login details')
|
if (!isValid) {
|
||||||
}
|
logger.err({ user, pass }, 'invalid login details')
|
||||||
return isValid
|
}
|
||||||
}),
|
return isValid
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
requirePrivateApiAuth() {
|
||||||
|
return AuthenticationController.requireBasicAuth(Settings.httpAuthUsers)
|
||||||
|
},
|
||||||
|
|
||||||
setRedirectInSession(req, value) {
|
setRedirectInSession(req, value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
|
|
@ -57,7 +57,7 @@ module.exports = {
|
||||||
)
|
)
|
||||||
apiRouter.post(
|
apiRouter.post(
|
||||||
'/project/:Project_id/doc/:entity_id/convert-to-file',
|
'/project/:Project_id/doc/:entity_id/convert-to-file',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
validate({
|
validate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
userId: Joi.objectId().required(),
|
userId: Joi.objectId().required(),
|
||||||
|
@ -71,7 +71,7 @@ module.exports = {
|
||||||
// whenever a user joins a project, like updating the deleted status.
|
// whenever a user joins a project, like updating the deleted status.
|
||||||
apiRouter.post(
|
apiRouter.post(
|
||||||
'/project/:Project_id/join',
|
'/project/:Project_id/join',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
RateLimiterMiddleware.rateLimit({
|
RateLimiterMiddleware.rateLimit({
|
||||||
endpointName: 'join-project',
|
endpointName: 'join-project',
|
||||||
params: ['Project_id'],
|
params: ['Project_id'],
|
||||||
|
|
|
@ -136,7 +136,7 @@ module.exports = {
|
||||||
// Currently used in acceptance tests only, as a way to trigger the syncing logic
|
// Currently used in acceptance tests only, as a way to trigger the syncing logic
|
||||||
return publicApiRouter.post(
|
return publicApiRouter.post(
|
||||||
'/user/:user_id/features/sync',
|
'/user/:user_id/features/sync',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
SubscriptionController.refreshUserFeatures
|
SubscriptionController.refreshUserFeatures
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -231,7 +231,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
)
|
)
|
||||||
privateApiRouter.get(
|
privateApiRouter.get(
|
||||||
'/user/:user_id/personal_info',
|
'/user/:user_id/personal_info',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
UserInfoController.getPersonalInfo
|
UserInfoController.getPersonalInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -564,7 +564,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
)
|
)
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/project/:Project_id/history/resync',
|
'/project/:Project_id/history/resync',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
HistoryController.resyncProjectHistory
|
HistoryController.resyncProjectHistory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -639,28 +639,28 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
)
|
)
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/internal/expire-deleted-projects-after-duration',
|
'/internal/expire-deleted-projects-after-duration',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
ProjectController.expireDeletedProjectsAfterDuration
|
ProjectController.expireDeletedProjectsAfterDuration
|
||||||
)
|
)
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/internal/expire-deleted-users-after-duration',
|
'/internal/expire-deleted-users-after-duration',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
UserController.expireDeletedUsersAfterDuration
|
UserController.expireDeletedUsersAfterDuration
|
||||||
)
|
)
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/internal/project/:projectId/expire-deleted-project',
|
'/internal/project/:projectId/expire-deleted-project',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
ProjectController.expireDeletedProject
|
ProjectController.expireDeletedProject
|
||||||
)
|
)
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/internal/users/:userId/expire',
|
'/internal/users/:userId/expire',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
UserController.expireDeletedUser
|
UserController.expireDeletedUser
|
||||||
)
|
)
|
||||||
|
|
||||||
privateApiRouter.get(
|
privateApiRouter.get(
|
||||||
'/user/:userId/tag',
|
'/user/:userId/tag',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
TagsController.apiGetAllTags
|
TagsController.apiGetAllTags
|
||||||
)
|
)
|
||||||
webRouter.get(
|
webRouter.get(
|
||||||
|
@ -733,35 +733,35 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
// Deprecated in favour of /internal/project/:project_id but still used by versioning
|
// Deprecated in favour of /internal/project/:project_id but still used by versioning
|
||||||
privateApiRouter.get(
|
privateApiRouter.get(
|
||||||
'/project/:project_id/details',
|
'/project/:project_id/details',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
ProjectApiController.getProjectDetails
|
ProjectApiController.getProjectDetails
|
||||||
)
|
)
|
||||||
|
|
||||||
// New 'stable' /internal API end points
|
// New 'stable' /internal API end points
|
||||||
privateApiRouter.get(
|
privateApiRouter.get(
|
||||||
'/internal/project/:project_id',
|
'/internal/project/:project_id',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
ProjectApiController.getProjectDetails
|
ProjectApiController.getProjectDetails
|
||||||
)
|
)
|
||||||
privateApiRouter.get(
|
privateApiRouter.get(
|
||||||
'/internal/project/:Project_id/zip',
|
'/internal/project/:Project_id/zip',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
ProjectDownloadsController.downloadProject
|
ProjectDownloadsController.downloadProject
|
||||||
)
|
)
|
||||||
privateApiRouter.get(
|
privateApiRouter.get(
|
||||||
'/internal/project/:project_id/compile/pdf',
|
'/internal/project/:project_id/compile/pdf',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
CompileController.compileAndDownloadPdf
|
CompileController.compileAndDownloadPdf
|
||||||
)
|
)
|
||||||
|
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/internal/deactivateOldProjects',
|
'/internal/deactivateOldProjects',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
InactiveProjectController.deactivateOldProjects
|
InactiveProjectController.deactivateOldProjects
|
||||||
)
|
)
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/internal/project/:project_id/deactivate',
|
'/internal/project/:project_id/deactivate',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
InactiveProjectController.deactivateProject
|
InactiveProjectController.deactivateProject
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -775,40 +775,40 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
req.params = params
|
req.params = params
|
||||||
next()
|
next()
|
||||||
},
|
},
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
CompileController.getFileFromClsi
|
CompileController.getFileFromClsi
|
||||||
)
|
)
|
||||||
|
|
||||||
privateApiRouter.get(
|
privateApiRouter.get(
|
||||||
'/project/:Project_id/doc/:doc_id',
|
'/project/:Project_id/doc/:doc_id',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
DocumentController.getDocument
|
DocumentController.getDocument
|
||||||
)
|
)
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/project/:Project_id/doc/:doc_id',
|
'/project/:Project_id/doc/:doc_id',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
DocumentController.setDocument
|
DocumentController.setDocument
|
||||||
)
|
)
|
||||||
|
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/user/:user_id/update/*',
|
'/user/:user_id/update/*',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
TpdsController.mergeUpdate
|
TpdsController.mergeUpdate
|
||||||
)
|
)
|
||||||
privateApiRouter.delete(
|
privateApiRouter.delete(
|
||||||
'/user/:user_id/update/*',
|
'/user/:user_id/update/*',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
TpdsController.deleteUpdate
|
TpdsController.deleteUpdate
|
||||||
)
|
)
|
||||||
|
|
||||||
privateApiRouter.post(
|
privateApiRouter.post(
|
||||||
'/project/:project_id/contents/*',
|
'/project/:project_id/contents/*',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
TpdsController.updateProjectContents
|
TpdsController.updateProjectContents
|
||||||
)
|
)
|
||||||
privateApiRouter.delete(
|
privateApiRouter.delete(
|
||||||
'/project/:project_id/contents/*',
|
'/project/:project_id/contents/*',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
TpdsController.deleteProjectContents
|
TpdsController.deleteProjectContents
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -884,7 +884,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
// overleaf-integration module), but may expand beyond that role.
|
// overleaf-integration module), but may expand beyond that role.
|
||||||
publicApiRouter.post(
|
publicApiRouter.post(
|
||||||
'/api/clsi/compile/:submission_id',
|
'/api/clsi/compile/:submission_id',
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
CompileController.compileSubmission
|
CompileController.compileSubmission
|
||||||
)
|
)
|
||||||
publicApiRouter.get(
|
publicApiRouter.get(
|
||||||
|
@ -898,7 +898,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
req.params = params
|
req.params = params
|
||||||
next()
|
next()
|
||||||
},
|
},
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
CompileController.getFileFromClsiWithoutUser
|
CompileController.getFileFromClsiWithoutUser
|
||||||
)
|
)
|
||||||
publicApiRouter.post(
|
publicApiRouter.post(
|
||||||
|
@ -908,7 +908,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||||
maxRequests: 1,
|
maxRequests: 1,
|
||||||
timeInterval: 60,
|
timeInterval: 60,
|
||||||
}),
|
}),
|
||||||
AuthenticationController.httpAuth,
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
InstitutionsController.confirmDomain
|
InstitutionsController.confirmDomain
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -630,7 +630,10 @@ describe('AuthenticationController', function () {
|
||||||
describe('requireGlobalLogin', function () {
|
describe('requireGlobalLogin', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.req.headers = {}
|
this.req.headers = {}
|
||||||
this.AuthenticationController.httpAuth = sinon.stub()
|
this.middleware = sinon.stub()
|
||||||
|
this.AuthenticationController.requirePrivateApiAuth = sinon
|
||||||
|
.stub()
|
||||||
|
.returns(this.middleware)
|
||||||
this.setRedirect = sinon.spy(
|
this.setRedirect = sinon.spy(
|
||||||
this.AuthenticationController,
|
this.AuthenticationController,
|
||||||
'setRedirectInSession'
|
'setRedirectInSession'
|
||||||
|
@ -684,8 +687,8 @@ describe('AuthenticationController', function () {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should pass the request onto httpAuth', function () {
|
it('should pass the request onto requirePrivateApiAuth middleware', function () {
|
||||||
this.AuthenticationController.httpAuth
|
this.middleware
|
||||||
.calledWith(this.req, this.res, this.next)
|
.calledWith(this.req, this.res, this.next)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
@ -726,7 +729,16 @@ describe('AuthenticationController', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('httpAuth', function () {
|
describe('requireBasicAuth', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.basicAuthUsers = {
|
||||||
|
'basic-auth-user': 'basic-auth-password',
|
||||||
|
}
|
||||||
|
this.middleware = this.AuthenticationController.requireBasicAuth(
|
||||||
|
this.basicAuthUsers
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
describe('with http auth', function () {
|
describe('with http auth', function () {
|
||||||
it('should error with incorrect user', function (done) {
|
it('should error with incorrect user', function (done) {
|
||||||
this.req.headers = {
|
this.req.headers = {
|
||||||
|
@ -736,12 +748,12 @@ describe('AuthenticationController', function () {
|
||||||
expect(status).to.equal('Unauthorized')
|
expect(status).to.equal('Unauthorized')
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
this.AuthenticationController.httpAuth(this.req, this.req)
|
this.middleware(this.req, this.req)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should error with incorrect password', function (done) {
|
it('should error with incorrect password', function (done) {
|
||||||
this.req.headers = {
|
this.req.headers = {
|
||||||
authorization: `Basic ${Buffer.from('valid-test-user:nope').toString(
|
authorization: `Basic ${Buffer.from('basic-auth-user:nope').toString(
|
||||||
'base64'
|
'base64'
|
||||||
)}`,
|
)}`,
|
||||||
}
|
}
|
||||||
|
@ -749,12 +761,12 @@ describe('AuthenticationController', function () {
|
||||||
expect(status).to.equal('Unauthorized')
|
expect(status).to.equal('Unauthorized')
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
this.AuthenticationController.httpAuth(this.req, this.req)
|
this.middleware(this.req, this.req)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should fail with empty pass', function (done) {
|
it('should fail with empty pass', function (done) {
|
||||||
this.req.headers = {
|
this.req.headers = {
|
||||||
authorization: `Basic ${Buffer.from(`invalid-test-user:`).toString(
|
authorization: `Basic ${Buffer.from(`basic-auth-user:`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
)}`,
|
)}`,
|
||||||
}
|
}
|
||||||
|
@ -762,20 +774,33 @@ describe('AuthenticationController', function () {
|
||||||
expect(status).to.equal('Unauthorized')
|
expect(status).to.equal('Unauthorized')
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
this.AuthenticationController.httpAuth(this.req, this.req)
|
this.middleware(this.req, this.req)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should succeed with correct user/pass', function (done) {
|
it('should succeed with correct user/pass', function (done) {
|
||||||
this.req.headers = {
|
this.req.headers = {
|
||||||
authorization: `Basic ${Buffer.from(
|
authorization: `Basic ${Buffer.from(
|
||||||
`valid-test-user:${this.httpAuthUsers['valid-test-user']}`
|
`basic-auth-user:${this.basicAuthUsers['basic-auth-user']}`
|
||||||
).toString('base64')}`,
|
).toString('base64')}`,
|
||||||
}
|
}
|
||||||
this.AuthenticationController.httpAuth(this.req, this.res, done)
|
this.middleware(this.req, this.res, done)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('requirePrivateApiAuth', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.AuthenticationController.requireBasicAuth = sinon.stub()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call requireBasicAuth with the private API user details', function () {
|
||||||
|
this.AuthenticationController.requirePrivateApiAuth()
|
||||||
|
this.AuthenticationController.requireBasicAuth
|
||||||
|
.calledWith(this.httpAuthUsers)
|
||||||
|
.should.equal(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('_redirectToLoginOrRegisterPage', function () {
|
describe('_redirectToLoginOrRegisterPage', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.middleware = this.AuthenticationController.requireLogin(
|
this.middleware = this.AuthenticationController.requireLogin(
|
||||||
|
|
Loading…
Reference in a new issue