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:
parent
74cc13a888
commit
83086e4a79
7 changed files with 104 additions and 34 deletions
services/web
app
coffee/Features/Collaborators
views
public
coffee/ide/share
stylesheets/app/editor
test/unit/coffee/Collaborators
|
@ -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
|
||||
|
|
|
@ -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}";
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
]
|
||||
]
|
|
@ -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) ->
|
||||
|
|
|
@ -66,3 +66,7 @@
|
|||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.grecaptcha-badge {
|
||||
display: none;
|
||||
}
|
|
@ -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 ->
|
||||
|
|
Loading…
Add table
Reference in a new issue