1
0
Fork 0
mirror of https://github.com/overleaf/overleaf.git synced 2025-04-11 00:57:18 +00:00

Add recaptch to share endpoint

This commit is contained in:
James Allen 2017-12-11 11:44:34 +00:00
parent 74cc13a888
commit 83086e4a79
7 changed files with 104 additions and 34 deletions
services/web
app
coffee/Features/Collaborators
views
layout.pug
project/editor
public
coffee/ide/share
stylesheets/app/editor
test/unit/coffee/Collaborators

View file

@ -11,6 +11,7 @@ NotificationsBuilder = require("../Notifications/NotificationsBuilder")
AnalyticsManger = require("../Analytics/AnalyticsManager")
AuthenticationController = require("../Authentication/AuthenticationController")
rateLimiter = require("../../infrastructure/RateLimiter")
request = require 'request'
module.exports = CollaboratorsInviteController =
@ -46,43 +47,59 @@ module.exports = CollaboratorsInviteController =
throttle: collabLimit
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) ->
projectId = req.params.Project_id
email = req.body.email
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) =>
CollaboratorsInviteController._validateCaptcha req.body['g-recaptcha-response'], (error, valid) ->
return next(error) if error?
if !allowed
logger.log {projectId, email, sendingUserId}, "not allowed to invite more users to project"
return res.json {invite: null}
{email, privileges} = req.body
email = EmailHelper.parseEmail(email)
if !email? or email == ""
logger.log {projectId, email, sendingUserId}, "invalid email address"
return res.sendStatus(400)
CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) ->
if !valid
return res.sendStatus 400
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?
if !underRateLimit
return res.sendStatus(429)
CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)->
if err?
logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address"
return next(err)
if !shouldAllowInvite
logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address"
return res.json {invite: null, error: 'cannot_invite_non_user'}
CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) ->
if !allowed
logger.log {projectId, email, sendingUserId}, "not allowed to invite more users to project"
return res.json {invite: null}
{email, privileges} = req.body
email = EmailHelper.parseEmail(email)
if !email? or email == ""
logger.log {projectId, email, sendingUserId}, "invalid email address"
return res.sendStatus(400)
CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) ->
return next(error) if error?
if !underRateLimit
return res.sendStatus(429)
CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)->
if err?
logger.err {projectId, email, sendingUserId}, "error creating project invite"
logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address"
return next(err)
logger.log {projectId, email, sendingUserId}, "invite created"
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true})
return res.json {invite: invite}
if !shouldAllowInvite
logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address"
return res.json {invite: null, error: 'cannot_invite_non_user'}
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) ->
projectId = req.params.Project_id

View file

@ -53,6 +53,9 @@ html(itemscope, itemtype='http://schema.org/Product')
- else
script(type='text/javascript').
window.ga = function() { console.log("would send to GA", arguments) };
- if (settings.recaptcha)
script(src="https://www.google.com/recaptcha/api.js")
script(type="text/javascript").
window.csrfToken = "#{csrfToken}";

View file

@ -102,6 +102,13 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
i.fa.fa-times
.row.invite-controls
form(ng-show="canAddCollaborators")
div(
id="recaptcha"
class="g-recaptcha"
data-sitekey=settings.recaptcha.siteKey
data-size="invisible"
data-badge="inline"
)
.small #{translate("share_with_your_collabs")}
.form-group
tags-input(

View file

@ -55,6 +55,25 @@ define [
getCurrentInviteEmails = () ->
($scope.project.invites || []).map (u) -> u.email
_recaptchaCallbacks = []
onRecaptchaSubmit = (token) ->
for cb in _recaptchaCallbacks
cb(token)
_recaptchaCallbacks = []
recaptchaId = null
validateCaptcha = (callback = (response) ->) =>
if !grecaptcha?
return callback()
reset = () ->
grecaptcha.reset()
_recaptchaCallbacks.push callback
_recaptchaCallbacks.push reset
if !recaptchaId?
el = $('.g-recaptcha')[0]
recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit})
grecaptcha.execute(recaptchaId)
$scope.filterAutocompleteUsers = ($query) ->
currentMemberEmails = getCurrentMemberEmails()
return $scope.autocompleteContacts.filter (contact) ->
@ -100,7 +119,7 @@ define [
if email in currentInviteEmails and inviteId = _.find(($scope.project.invites || []), (invite) -> invite.email == email)?._id
request = projectInvites.resendInvite(inviteId)
else
request = projectInvites.sendInvite(email, $scope.inputs.privileges)
request = projectInvites.sendInvite(email, $scope.inputs.privileges, $scope.grecaptchaResponse)
request
.then (response) ->
@ -135,7 +154,9 @@ define [
else
$scope.state.errorReason = null
$timeout addMembers, 50 # Give email list a chance to update
validateCaptcha (response) ->
$scope.grecaptchaResponse = response
$timeout addMembers, 50 # Give email list a chance to update
$scope.removeMember = (member) ->
$scope.state.error = null
@ -210,6 +231,8 @@ define [
$scope.cancel = () ->
$modalInstance.dismiss()
App.controller "MakePublicModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
$scope.inputs = {
privileges: "readAndWrite"
@ -244,4 +267,4 @@ define [
$scope.cancel = () ->
$modalInstance.dismiss()
]
]

View file

@ -4,11 +4,12 @@ define [
App.factory "projectInvites", ["ide", "$http", (ide, $http) ->
return {
sendInvite: (email, privileges) ->
sendInvite: (email, privileges, grecaptchaResponse) ->
$http.post("/project/#{ide.project_id}/invite", {
email: email
privileges: privileges
_csrf: window.csrfToken
'g-recaptcha-response': grecaptchaResponse
})
revokeInvite: (inviteId) ->

View file

@ -66,3 +66,7 @@
text-align: left;
}
}
.grecaptcha-badge {
display: none;
}

View file

@ -38,6 +38,7 @@ describe "CollaboratorsInviteController", ->
'../Authentication/AuthenticationController': @AuthenticationController
'settings-sharelatex': @settings = {}
"../../infrastructure/RateLimiter":@RateLimiter
'request': @request = {}
@res = new MockResponse()
@req = new MockRequest()
@ -96,6 +97,7 @@ describe "CollaboratorsInviteController", ->
@req.body =
email: @targetEmail
privileges: @privileges = "readAndWrite"
'g-recaptcha-response': @grecaptchaResponse = 'grecaptcha response'
@res.json = sinon.stub()
@res.sendStatus = sinon.stub()
@invite = {
@ -108,6 +110,8 @@ describe "CollaboratorsInviteController", ->
}
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
@CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, null, @invite)
@CollaboratorsInviteController._validateCaptcha = sinon.stub()
@CollaboratorsInviteController._validateCaptcha.withArgs(@grecaptchaResponse).yields(null, true)
@callback = sinon.stub()
@next = sinon.stub()
@ -285,6 +289,17 @@ describe "CollaboratorsInviteController", ->
it 'should not call emitToRoom', ->
@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", ->
beforeEach ->