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

Merge pull request #200 from sharelatex/ja-add-recaptcha

Add recaptcha to share and register endpoint
James Allen 8 лет назад
Родитель
Сommit
9fe1f36df4

+ 21 - 0
services/web/app/coffee/Features/Captcha/CaptchaMiddleware.coffee

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

+ 2 - 1
services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee

@@ -11,6 +11,7 @@ NotificationsBuilder = require("../Notifications/NotificationsBuilder")
 AnalyticsManger = require("../Analytics/AnalyticsManager")
 AnalyticsManger = require("../Analytics/AnalyticsManager")
 AuthenticationController = require("../Authentication/AuthenticationController")
 AuthenticationController = require("../Authentication/AuthenticationController")
 rateLimiter = require("../../infrastructure/RateLimiter")
 rateLimiter = require("../../infrastructure/RateLimiter")
+request = require 'request'
 
 
 module.exports = CollaboratorsInviteController =
 module.exports = CollaboratorsInviteController =
 
 
@@ -32,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?

+ 2 - 0
services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee

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

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

@@ -97,6 +97,15 @@ html(itemscope, itemtype='http://schema.org/Product')
 				}
 				}
 				
 				
 	body
 	body
+		if(settings.recaptcha)
+			script(src="https://www.google.com/recaptcha/api.js?render=explicit")
+			div(
+				id="recaptcha"
+				class="g-recaptcha"
+				data-sitekey=settings.recaptcha.siteKey
+				data-size="invisible"
+				data-badge="inline"
+			)
 
 
 		- if(typeof(suppressSystemMessages) == "undefined")
 		- if(typeof(suppressSystemMessages) == "undefined")
 			.system-messages(
 			.system-messages(

+ 13 - 1
services/web/public/coffee/directives/asyncForm.coffee

@@ -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 response
 
 
+				validateCaptchaIfEnabled = (callback = (response) ->) ->
+					if attrs.captcha?
+						validateCaptcha callback
+					else
+						callback()
+
+				submitRequest = (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

+ 1 - 0
services/web/public/coffee/ide.coffee

@@ -33,6 +33,7 @@ define [
 	"directives/expandableTextArea"
 	"directives/expandableTextArea"
 	"directives/videoPlayState"
 	"directives/videoPlayState"
 	"services/queued-http"
 	"services/queued-http"
+	"services/validateCaptcha"
 	"filters/formatDate"
 	"filters/formatDate"
 	"main/event"
 	"main/event"
 	"main/account-upgrade"
 	"main/account-upgrade"

+ 8 - 4
services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee

@@ -1,7 +1,7 @@
 define [
 define [
 	"base"
 	"base"
 ], (App) ->
 ], (App) ->
-	App.controller "ShareProjectModalController", ($scope, $modalInstance, $timeout, projectMembers, projectInvites, $modal, $http, ide) ->
+	App.controller "ShareProjectModalController", ($scope, $modalInstance, $timeout, projectMembers, projectInvites, $modal, $http, ide, validateCaptcha) ->
 		$scope.inputs = {
 		$scope.inputs = {
 			privileges: "readAndWrite"
 			privileges: "readAndWrite"
 			contacts: []
 			contacts: []
@@ -100,7 +100,7 @@ define [
 					if email in currentInviteEmails and inviteId = _.find(($scope.project.invites || []), (invite) -> invite.email == email)?._id
 					if email in currentInviteEmails and inviteId = _.find(($scope.project.invites || []), (invite) -> invite.email == email)?._id
 						request = projectInvites.resendInvite(inviteId)
 						request = projectInvites.resendInvite(inviteId)
 					else
 					else
-						request = projectInvites.sendInvite(email, $scope.inputs.privileges)
+						request = projectInvites.sendInvite(email, $scope.inputs.privileges, $scope.grecaptchaResponse)
 
 
 					request
 					request
 						.then (response) ->
 						.then (response) ->
@@ -135,7 +135,9 @@ define [
 							else
 							else
 								$scope.state.errorReason = null
 								$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.removeMember = (member) ->
 			$scope.state.error = null
 			$scope.state.error = null
@@ -210,6 +212,8 @@ define [
 		$scope.cancel = () ->
 		$scope.cancel = () ->
 			$modalInstance.dismiss()
 			$modalInstance.dismiss()
 
 
+
+
 	App.controller "MakePublicModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
 	App.controller "MakePublicModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
 		$scope.inputs = {
 		$scope.inputs = {
 			privileges: "readAndWrite"
 			privileges: "readAndWrite"
@@ -244,4 +248,4 @@ define [
 
 
 		$scope.cancel = () ->
 		$scope.cancel = () ->
 			$modalInstance.dismiss()
 			$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) ->
 	App.factory "projectInvites", ["ide", "$http", (ide, $http) ->
 		return {
 		return {
 
 
-			sendInvite: (email, privileges) ->
+			sendInvite: (email, privileges, grecaptchaResponse) ->
 				$http.post("/project/#{ide.project_id}/invite", {
 				$http.post("/project/#{ide.project_id}/invite", {
 					email: email
 					email: email
 					privileges: privileges
 					privileges: privileges
 					_csrf: window.csrfToken
 					_csrf: window.csrfToken
+					'g-recaptcha-response': grecaptchaResponse
 				})
 				})
 
 
 			revokeInvite: (inviteId) ->
 			revokeInvite: (inviteId) ->

+ 1 - 0
services/web/public/coffee/main.coffee

@@ -30,6 +30,7 @@ define [
 	"directives/maxHeight"
 	"directives/maxHeight"
 	"directives/creditCards"
 	"directives/creditCards"
 	"services/queued-http"
 	"services/queued-http"
+	"services/validateCaptcha"
 	"filters/formatDate"
 	"filters/formatDate"
 	"__MAIN_CLIENTSIDE_INCLUDES__"
 	"__MAIN_CLIENTSIDE_INCLUDES__"
 ], () ->
 ], () ->

+ 24 - 0
services/web/public/coffee/services/validateCaptcha.coffee

@@ -0,0 +1,24 @@
+define [
+	"base"
+], (App) ->
+	App.factory "validateCaptcha", () ->
+		_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 = $('#recaptcha')[0]
+				recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit})
+			grecaptcha.execute(recaptchaId)
+
+		return validateCaptcha

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

@@ -104,3 +104,7 @@
   -ms-transform-origin: center bottom;
   -ms-transform-origin: center bottom;
   transform-origin: center bottom;
   transform-origin: center bottom;
 }
 }
+
+.grecaptcha-badge {
+  display: none;
+}