mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Refactor captcha into middleware and angular service
This commit is contained in:
parent
69499847e4
commit
53dc8cddfc
6 changed files with 68 additions and 64 deletions
|
@ -0,0 +1,21 @@
|
||||||
|
request = require 'request'
|
||||||
|
logger = require 'logger-sharelatex'
|
||||||
|
Settings = require 'settings-sharelatex'
|
||||||
|
|
||||||
|
module.exports = CaptchaMiddleware =
|
||||||
|
validateCaptcha: (req, res, next) ->
|
||||||
|
if !Settings.recaptcha?
|
||||||
|
return next()
|
||||||
|
response = req.body['g-recaptcha-response']
|
||||||
|
options =
|
||||||
|
form:
|
||||||
|
secret: Settings.recaptcha.secretKey
|
||||||
|
response: response
|
||||||
|
json: true
|
||||||
|
request.post "https://www.google.com/recaptcha/api/siteverify", options, (error, response, body) ->
|
||||||
|
return next(error) if error?
|
||||||
|
if !body?.success
|
||||||
|
logger.warn {statusCode: response.statusCode, body: body}, 'failed recaptcha siteverify request'
|
||||||
|
return res.sendStatus 400
|
||||||
|
else
|
||||||
|
return next()
|
|
@ -33,7 +33,7 @@ module.exports = CollaboratorsInviteController =
|
||||||
callback(null, userExists)
|
callback(null, userExists)
|
||||||
else
|
else
|
||||||
callback(null, true)
|
callback(null, true)
|
||||||
|
|
||||||
_checkRateLimit: (user_id, callback = (error) ->) ->
|
_checkRateLimit: (user_id, callback = (error) ->) ->
|
||||||
LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)->
|
LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)->
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
|
@ -47,59 +47,43 @@ module.exports = CollaboratorsInviteController =
|
||||||
throttle: collabLimit
|
throttle: collabLimit
|
||||||
rateLimiter.addCount opts, callback
|
rateLimiter.addCount opts, callback
|
||||||
|
|
||||||
_validateCaptcha: (response, callback = (error, valid) ->) ->
|
|
||||||
if !Settings.recaptcha?
|
|
||||||
return callback(null, true)
|
|
||||||
options =
|
|
||||||
form:
|
|
||||||
secret: Settings.recaptcha.secretKey
|
|
||||||
response: response
|
|
||||||
json: true
|
|
||||||
request.post "https://www.google.com/recaptcha/api/siteverify", options, (error, response, body) ->
|
|
||||||
return callback(error) if error?
|
|
||||||
return callback null, body?.success
|
|
||||||
|
|
||||||
inviteToProject: (req, res, next) ->
|
inviteToProject: (req, res, next) ->
|
||||||
projectId = req.params.Project_id
|
projectId = req.params.Project_id
|
||||||
email = req.body.email
|
email = req.body.email
|
||||||
CollaboratorsInviteController._validateCaptcha req.body['g-recaptcha-response'], (error, valid) ->
|
sendingUser = AuthenticationController.getSessionUser(req)
|
||||||
|
sendingUserId = sendingUser._id
|
||||||
|
if email == sendingUser.email
|
||||||
|
logger.log {projectId, email, sendingUserId}, "cannot invite yourself to project"
|
||||||
|
return res.json {invite: null, error: 'cannot_invite_self'}
|
||||||
|
logger.log {projectId, email, sendingUserId}, "inviting to project"
|
||||||
|
LimitationsManager.canAddXCollaborators projectId, 1, (error, allowed) =>
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
if !valid
|
if !allowed
|
||||||
return res.sendStatus 400
|
logger.log {projectId, email, sendingUserId}, "not allowed to invite more users to project"
|
||||||
sendingUser = AuthenticationController.getSessionUser(req)
|
return res.json {invite: null}
|
||||||
sendingUserId = sendingUser._id
|
{email, privileges} = req.body
|
||||||
if email == sendingUser.email
|
email = EmailHelper.parseEmail(email)
|
||||||
logger.log {projectId, email, sendingUserId}, "cannot invite yourself to project"
|
if !email? or email == ""
|
||||||
return res.json {invite: null, error: 'cannot_invite_self'}
|
logger.log {projectId, email, sendingUserId}, "invalid email address"
|
||||||
logger.log {projectId, email, sendingUserId}, "inviting to project"
|
return res.sendStatus(400)
|
||||||
LimitationsManager.canAddXCollaborators projectId, 1, (error, allowed) =>
|
CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
if !allowed
|
if !underRateLimit
|
||||||
logger.log {projectId, email, sendingUserId}, "not allowed to invite more users to project"
|
return res.sendStatus(429)
|
||||||
return res.json {invite: null}
|
CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)->
|
||||||
{email, privileges} = req.body
|
if err?
|
||||||
email = EmailHelper.parseEmail(email)
|
logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address"
|
||||||
if !email? or email == ""
|
return next(err)
|
||||||
logger.log {projectId, email, sendingUserId}, "invalid email address"
|
if !shouldAllowInvite
|
||||||
return res.sendStatus(400)
|
logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address"
|
||||||
CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) ->
|
return res.json {invite: null, error: 'cannot_invite_non_user'}
|
||||||
return next(error) if error?
|
CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) ->
|
||||||
if !underRateLimit
|
|
||||||
return res.sendStatus(429)
|
|
||||||
CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)->
|
|
||||||
if err?
|
if err?
|
||||||
logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address"
|
logger.err {projectId, email, sendingUserId}, "error creating project invite"
|
||||||
return next(err)
|
return next(err)
|
||||||
if !shouldAllowInvite
|
logger.log {projectId, email, sendingUserId}, "invite created"
|
||||||
logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address"
|
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true})
|
||||||
return res.json {invite: null, error: 'cannot_invite_non_user'}
|
return res.json {invite: invite}
|
||||||
CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) ->
|
|
||||||
if err?
|
|
||||||
logger.err {projectId, email, sendingUserId}, "error creating project invite"
|
|
||||||
return next(err)
|
|
||||||
logger.log {projectId, email, sendingUserId}, "invite created"
|
|
||||||
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true})
|
|
||||||
return res.json {invite: invite}
|
|
||||||
|
|
||||||
revokeInvite: (req, res, next) ->
|
revokeInvite: (req, res, next) ->
|
||||||
projectId = req.params.Project_id
|
projectId = req.params.Project_id
|
||||||
|
|
|
@ -3,6 +3,7 @@ AuthenticationController = require('../Authentication/AuthenticationController')
|
||||||
AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear')
|
AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear')
|
||||||
CollaboratorsInviteController = require('./CollaboratorsInviteController')
|
CollaboratorsInviteController = require('./CollaboratorsInviteController')
|
||||||
RateLimiterMiddlewear = require('../Security/RateLimiterMiddlewear')
|
RateLimiterMiddlewear = require('../Security/RateLimiterMiddlewear')
|
||||||
|
CaptchaMiddleware = require '../Captcha/CaptchaMiddleware'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
apply: (webRouter, apiRouter) ->
|
apply: (webRouter, apiRouter) ->
|
||||||
|
@ -32,6 +33,7 @@ module.exports =
|
||||||
maxRequests: 100
|
maxRequests: 100
|
||||||
timeInterval: 60 * 10
|
timeInterval: 60 * 10
|
||||||
}),
|
}),
|
||||||
|
CaptchaMiddleware.validateCaptcha,
|
||||||
AuthenticationController.requireLogin(),
|
AuthenticationController.requireLogin(),
|
||||||
AuthorizationMiddlewear.ensureUserCanAdminProject,
|
AuthorizationMiddlewear.ensureUserCanAdminProject,
|
||||||
CollaboratorsInviteController.inviteToProject
|
CollaboratorsInviteController.inviteToProject
|
||||||
|
|
|
@ -2,7 +2,7 @@ define [
|
||||||
"base"
|
"base"
|
||||||
"libs/passfield"
|
"libs/passfield"
|
||||||
], (App) ->
|
], (App) ->
|
||||||
App.directive "asyncForm", ($http) ->
|
App.directive "asyncForm", ($http, validateCaptcha) ->
|
||||||
return {
|
return {
|
||||||
controller: ['$scope', ($scope) ->
|
controller: ['$scope', ($scope) ->
|
||||||
@getEmail = () ->
|
@getEmail = () ->
|
||||||
|
@ -17,11 +17,23 @@ define [
|
||||||
|
|
||||||
element.on "submit", (e) ->
|
element.on "submit", (e) ->
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
validateCaptchaIfEnabled (response) ->
|
||||||
|
submitRequest e, response
|
||||||
|
|
||||||
|
validateCaptchaIfEnabled = (callback = (response) ->) ->
|
||||||
|
if attrs.captcha?
|
||||||
|
validateCaptcha callback
|
||||||
|
else
|
||||||
|
callback()
|
||||||
|
|
||||||
|
submitRequest = (e, grecaptchaResponse) ->
|
||||||
formData = {}
|
formData = {}
|
||||||
for data in element.serializeArray()
|
for data in element.serializeArray()
|
||||||
formData[data.name] = data.value
|
formData[data.name] = data.value
|
||||||
|
|
||||||
|
if grecaptchaResponse?
|
||||||
|
formData['g-recaptcha-response'] = grecaptchaResponse
|
||||||
|
|
||||||
scope[attrs.name].inflight = true
|
scope[attrs.name].inflight = true
|
||||||
|
|
||||||
# for asyncForm prevent automatic redirect to /login if
|
# for asyncForm prevent automatic redirect to /login if
|
||||||
|
|
|
@ -17,7 +17,7 @@ define [
|
||||||
_recaptchaCallbacks.push callback
|
_recaptchaCallbacks.push callback
|
||||||
_recaptchaCallbacks.push reset
|
_recaptchaCallbacks.push reset
|
||||||
if !recaptchaId?
|
if !recaptchaId?
|
||||||
el = $('.g-recaptcha')[0]
|
el = $('#recaptcha')[0]
|
||||||
recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit})
|
recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit})
|
||||||
grecaptcha.execute(recaptchaId)
|
grecaptcha.execute(recaptchaId)
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,6 @@ describe "CollaboratorsInviteController", ->
|
||||||
'../Authentication/AuthenticationController': @AuthenticationController
|
'../Authentication/AuthenticationController': @AuthenticationController
|
||||||
'settings-sharelatex': @settings = {}
|
'settings-sharelatex': @settings = {}
|
||||||
"../../infrastructure/RateLimiter":@RateLimiter
|
"../../infrastructure/RateLimiter":@RateLimiter
|
||||||
'request': @request = {}
|
|
||||||
@res = new MockResponse()
|
@res = new MockResponse()
|
||||||
@req = new MockRequest()
|
@req = new MockRequest()
|
||||||
|
|
||||||
|
@ -97,7 +96,6 @@ describe "CollaboratorsInviteController", ->
|
||||||
@req.body =
|
@req.body =
|
||||||
email: @targetEmail
|
email: @targetEmail
|
||||||
privileges: @privileges = "readAndWrite"
|
privileges: @privileges = "readAndWrite"
|
||||||
'g-recaptcha-response': @grecaptchaResponse = 'grecaptcha response'
|
|
||||||
@res.json = sinon.stub()
|
@res.json = sinon.stub()
|
||||||
@res.sendStatus = sinon.stub()
|
@res.sendStatus = sinon.stub()
|
||||||
@invite = {
|
@invite = {
|
||||||
|
@ -110,8 +108,6 @@ describe "CollaboratorsInviteController", ->
|
||||||
}
|
}
|
||||||
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
|
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
|
||||||
@CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, null, @invite)
|
@CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, null, @invite)
|
||||||
@CollaboratorsInviteController._validateCaptcha = sinon.stub()
|
|
||||||
@CollaboratorsInviteController._validateCaptcha.withArgs(@grecaptchaResponse).yields(null, true)
|
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
@next = sinon.stub()
|
@next = sinon.stub()
|
||||||
|
|
||||||
|
@ -289,17 +285,6 @@ describe "CollaboratorsInviteController", ->
|
||||||
it 'should not call emitToRoom', ->
|
it 'should not call emitToRoom', ->
|
||||||
@EditorRealTimeController.emitToRoom.called.should.equal false
|
@EditorRealTimeController.emitToRoom.called.should.equal false
|
||||||
|
|
||||||
describe "when recaptcha is not valid", ->
|
|
||||||
beforeEach ->
|
|
||||||
@CollaboratorsInviteController._validateCaptcha = sinon.stub().yields(null, false)
|
|
||||||
@CollaboratorsInviteController.inviteToProject @req, @res, @next
|
|
||||||
|
|
||||||
it "should return 400", ->
|
|
||||||
@res.sendStatus.calledWith(400).should.equal true
|
|
||||||
|
|
||||||
it "should not inviteToProject", ->
|
|
||||||
@CollaboratorsInviteHandler.inviteToProject.called.should.equal false
|
|
||||||
|
|
||||||
describe "viewInvite", ->
|
describe "viewInvite", ->
|
||||||
|
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
|
Loading…
Reference in a new issue