Просмотр исходного кода

Add recaptch to share endpoint

James Allen 8 лет назад
Родитель
Сommit
83086e4a79

+ 47 - 30
services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee

@@ -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

+ 3 - 0
services/web/app/views/layout.pug

@@ -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}";

+ 7 - 0
services/web/app/views/project/editor/share.pug

@@ -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(

+ 26 - 3
services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee

@@ -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()
-	]
+	]

+ 2 - 1
services/web/public/coffee/ide/share/services/projectInvites.coffee

@@ -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) ->

+ 4 - 0
services/web/public/stylesheets/app/editor/share.less

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

+ 15 - 0
services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee

@@ -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 ->