mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-14 16:43:30 +00:00
Merge branch 'master' into pr-style-v2-resizers
This commit is contained in:
commit
59e675797e
23 changed files with 459 additions and 657 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()
|
|
@ -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 =
|
||||
|
||||
|
@ -32,7 +33,7 @@ module.exports = CollaboratorsInviteController =
|
|||
callback(null, userExists)
|
||||
else
|
||||
callback(null, true)
|
||||
|
||||
|
||||
_checkRateLimit: (user_id, callback = (error) ->) ->
|
||||
LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)->
|
||||
return callback(err) if err?
|
||||
|
|
|
@ -3,6 +3,7 @@ AuthenticationController = require('../Authentication/AuthenticationController')
|
|||
AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear')
|
||||
CollaboratorsInviteController = require('./CollaboratorsInviteController')
|
||||
RateLimiterMiddlewear = require('../Security/RateLimiterMiddlewear')
|
||||
CaptchaMiddleware = require '../Captcha/CaptchaMiddleware'
|
||||
|
||||
module.exports =
|
||||
apply: (webRouter, apiRouter) ->
|
||||
|
@ -32,6 +33,7 @@ module.exports =
|
|||
maxRequests: 100
|
||||
timeInterval: 60 * 10
|
||||
}),
|
||||
CaptchaMiddleware.validateCaptcha,
|
||||
AuthenticationController.requireLogin(),
|
||||
AuthorizationMiddlewear.ensureUserCanAdminProject,
|
||||
CollaboratorsInviteController.inviteToProject
|
||||
|
|
|
@ -4,6 +4,7 @@ Settings = require('settings-sharelatex')
|
|||
nodemailer = require("nodemailer")
|
||||
sesTransport = require('nodemailer-ses-transport')
|
||||
sgTransport = require('nodemailer-sendgrid-transport')
|
||||
mandrillTransport = require('nodemailer-mandrill-transport')
|
||||
rateLimiter = require('../../infrastructure/RateLimiter')
|
||||
_ = require("underscore")
|
||||
|
||||
|
@ -17,22 +18,22 @@ client =
|
|||
sendMail: (options, callback = (err,status) ->) ->
|
||||
logger.log options:options, "Would send email if enabled."
|
||||
callback()
|
||||
|
||||
if Settings?.email?.parameters?.AWSAccessKeyID? or Settings?.email?.driver == 'ses'
|
||||
logger.log "using aws ses for email"
|
||||
nm_client = nodemailer.createTransport(sesTransport(Settings.email.parameters))
|
||||
else if Settings?.email?.parameters?.sendgridApiKey?
|
||||
logger.log "using sendgrid for email"
|
||||
nm_client = nodemailer.createTransport(sgTransport({auth:{api_key:Settings?.email?.parameters?.sendgridApiKey}}))
|
||||
else if Settings?.email?.parameters?.MandrillApiKey?
|
||||
logger.log "using mandril for email"
|
||||
nm_client = nodemailer.createTransport(mandrillTransport({auth:{apiKey:Settings?.email?.parameters?.MandrillApiKey}}))
|
||||
else if Settings?.email?.parameters?
|
||||
smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth", "ignoreTLS")
|
||||
|
||||
|
||||
logger.log "using smtp for email"
|
||||
smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth", "ignoreTLS")
|
||||
nm_client = nodemailer.createTransport(smtp)
|
||||
else
|
||||
nm_client = client
|
||||
logger.warn "Email transport and/or parameters not defined. No emails will be sent."
|
||||
nm_client = client
|
||||
|
||||
if nm_client?
|
||||
client = nm_client
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
EditorRealTimeController = require "../Editor/EditorRealTimeController"
|
||||
LabelsHandler = require './LabelsHandler'
|
||||
logger = require 'logger-sharelatex'
|
||||
|
||||
|
||||
module.exports = LabelsController =
|
||||
|
||||
getAllLabels: (req, res, next) ->
|
||||
project_id = req.params.project_id
|
||||
logger.log {project_id}, "getting all labels for project"
|
||||
LabelsHandler.getAllLabelsForProject project_id, (err, projectLabels) ->
|
||||
if err?
|
||||
logger.err {project_id, err}, "[LabelsController] error getting all labels from project"
|
||||
return next(err)
|
||||
res.json {projectId: project_id, projectLabels: projectLabels}
|
||||
|
||||
broadcastLabelsForDoc: (req, res, next) ->
|
||||
project_id = req.params.project_id
|
||||
doc_id = req.params.doc_id
|
||||
logger.log {project_id, doc_id}, "getting labels for doc"
|
||||
LabelsHandler.getLabelsForDoc project_id, doc_id, (err, docLabels) ->
|
||||
if err?
|
||||
logger.err {project_id, doc_id, err}, "[LabelsController] error getting labels from doc"
|
||||
return next(err)
|
||||
EditorRealTimeController.emitToRoom project_id, 'broadcastDocLabels', {
|
||||
docId: doc_id, labels: docLabels
|
||||
}
|
||||
res.sendStatus(200)
|
|
@ -1,43 +0,0 @@
|
|||
ProjectEntityHandler = require "../Project/ProjectEntityHandler"
|
||||
DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
||||
|
||||
|
||||
module.exports = LabelsHandler =
|
||||
|
||||
labelCaptureRegex: () ->
|
||||
/\\label\{([^\}\n\\]{0,80})\}/g
|
||||
|
||||
getAllLabelsForProject: (projectId, callback=(err, projectLabels)->) ->
|
||||
DocumentUpdaterHandler.flushProjectToMongo projectId, (err) ->
|
||||
if err?
|
||||
return callback(err)
|
||||
ProjectEntityHandler.getAllDocs projectId, (err, docs) ->
|
||||
if err?
|
||||
return callback(err)
|
||||
projectLabels = LabelsHandler.extractLabelsFromProjectDocs docs
|
||||
callback(null, projectLabels)
|
||||
|
||||
getLabelsForDoc: (projectId, docId, callback=(err, docLabels)->) ->
|
||||
DocumentUpdaterHandler.flushDocToMongo projectId, docId, (err) ->
|
||||
if err?
|
||||
return callback(err)
|
||||
ProjectEntityHandler.getDoc projectId, docId, (err, lines) ->
|
||||
if err?
|
||||
return callback(err)
|
||||
docLabels = LabelsHandler.extractLabelsFromDoc lines
|
||||
callback(null, docLabels)
|
||||
|
||||
extractLabelsFromDoc: (lines) ->
|
||||
docLabels = []
|
||||
for line in lines
|
||||
re = LabelsHandler.labelCaptureRegex()
|
||||
while (labelMatch = re.exec(line))
|
||||
if labelMatch[1]
|
||||
docLabels.push(labelMatch[1])
|
||||
return docLabels
|
||||
|
||||
extractLabelsFromProjectDocs: (projectDocs) ->
|
||||
projectLabels = {} # docId => List[Label]
|
||||
for _path, doc of projectDocs
|
||||
projectLabels[doc._id] = LabelsHandler.extractLabelsFromDoc(doc.lines)
|
||||
return projectLabels
|
|
@ -44,7 +44,6 @@ SudoModeMiddlewear = require('./Features/SudoMode/SudoModeMiddlewear')
|
|||
AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
|
||||
AnnouncementsController = require("./Features/Announcements/AnnouncementsController")
|
||||
MetaController = require('./Features/Metadata/MetaController')
|
||||
LabelsController = require('./Features/Labels/LabelsController')
|
||||
TokenAccessController = require('./Features/TokenAccess/TokenAccessController')
|
||||
Features = require('./infrastructure/Features')
|
||||
|
||||
|
@ -206,8 +205,6 @@ module.exports = class Router
|
|||
webRouter.get '/project/:project_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.getMetadata
|
||||
webRouter.post '/project/:project_id/doc/:doc_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.broadcastMetadataForDoc
|
||||
|
||||
webRouter.get '/project/:project_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.getAllLabels
|
||||
webRouter.post '/project/:project_id/doc/:doc_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.broadcastLabelsForDoc
|
||||
|
||||
webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
|
||||
webRouter.post '/tag', AuthenticationController.requireLogin(), TagsController.createTag
|
||||
|
@ -360,4 +357,3 @@ module.exports = class Router
|
|||
TokenAccessController.readAndWriteToken
|
||||
|
||||
webRouter.get '*', ErrorController.notFound
|
||||
|
||||
|
|
|
@ -97,6 +97,15 @@ html(itemscope, itemtype='http://schema.org/Product')
|
|||
}
|
||||
|
||||
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")
|
||||
.system-messages(
|
||||
|
|
807
services/web/npm-shrinkwrap.json
generated
807
services/web/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load diff
|
@ -56,6 +56,7 @@
|
|||
"multer": "^0.1.8",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"nodemailer": "2.1.0",
|
||||
"nodemailer-mandrill-transport": "^1.2.0",
|
||||
"nodemailer-sendgrid-transport": "^0.2.0",
|
||||
"nodemailer-ses-transport": "^1.3.0",
|
||||
"optimist": "0.6.1",
|
||||
|
|
|
@ -2,7 +2,7 @@ define [
|
|||
"base"
|
||||
"libs/passfield"
|
||||
], (App) ->
|
||||
App.directive "asyncForm", ($http) ->
|
||||
App.directive "asyncForm", ($http, validateCaptcha) ->
|
||||
return {
|
||||
controller: ['$scope', ($scope) ->
|
||||
@getEmail = () ->
|
||||
|
@ -17,11 +17,23 @@ define [
|
|||
|
||||
element.on "submit", (e) ->
|
||||
e.preventDefault()
|
||||
validateCaptchaIfEnabled (response) ->
|
||||
submitRequest response
|
||||
|
||||
validateCaptchaIfEnabled = (callback = (response) ->) ->
|
||||
if attrs.captcha?
|
||||
validateCaptcha callback
|
||||
else
|
||||
callback()
|
||||
|
||||
submitRequest = (grecaptchaResponse) ->
|
||||
formData = {}
|
||||
for data in element.serializeArray()
|
||||
formData[data.name] = data.value
|
||||
|
||||
if grecaptchaResponse?
|
||||
formData['g-recaptcha-response'] = grecaptchaResponse
|
||||
|
||||
scope[attrs.name].inflight = true
|
||||
|
||||
# for asyncForm prevent automatic redirect to /login if
|
||||
|
|
|
@ -33,6 +33,7 @@ define [
|
|||
"directives/expandableTextArea"
|
||||
"directives/videoPlayState"
|
||||
"services/queued-http"
|
||||
"services/validateCaptcha"
|
||||
"filters/formatDate"
|
||||
"main/event"
|
||||
"main/account-upgrade"
|
||||
|
|
|
@ -10,13 +10,11 @@ define [
|
|||
"ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
|
||||
"ide/editor/directives/aceEditor/track-changes/TrackChangesManager"
|
||||
"ide/editor/directives/aceEditor/metadata/MetadataManager"
|
||||
"ide/editor/directives/aceEditor/labels/LabelsManager"
|
||||
"ide/labels/services/labels"
|
||||
"ide/metadata/services/metadata"
|
||||
"ide/graphics/services/graphics"
|
||||
"ide/preamble/services/preamble"
|
||||
"ide/files/services/files"
|
||||
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager, LabelsManager) ->
|
||||
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) ->
|
||||
EditSession = ace.require('ace/edit_session').EditSession
|
||||
ModeList = ace.require('ace/ext/modelist')
|
||||
|
||||
|
@ -38,7 +36,7 @@ define [
|
|||
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
|
||||
return url
|
||||
|
||||
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, metadata, graphics, preamble, files, $http, $q) ->
|
||||
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, metadata, graphics, preamble, files, $http, $q) ->
|
||||
monkeyPatchSearch($rootScope, $compile)
|
||||
|
||||
return {
|
||||
|
@ -105,9 +103,8 @@ define [
|
|||
highlightsManager = new HighlightsManager(scope, editor, element)
|
||||
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
|
||||
trackChangesManager = new TrackChangesManager(scope, editor, element)
|
||||
labelsManager = new LabelsManager(scope, editor, element, labels)
|
||||
metadataManager = new MetadataManager(scope, editor, element, metadata)
|
||||
autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, labelsManager, graphics, preamble, files)
|
||||
autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, graphics, preamble, files)
|
||||
|
||||
|
||||
# Prevert Ctrl|Cmd-S from triggering save dialog
|
||||
|
|
|
@ -10,7 +10,7 @@ define [
|
|||
aceSnippetManager = ace.require('ace/snippets').snippetManager
|
||||
|
||||
class AutoCompleteManager
|
||||
constructor: (@$scope, @editor, @element, @metadataManager, @labelsManager, @graphics, @preamble, @files) ->
|
||||
constructor: (@$scope, @editor, @element, @metadataManager, @graphics, @preamble, @files) ->
|
||||
|
||||
@monkeyPatchAutocomplete()
|
||||
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
define [
|
||||
"ace/ace"
|
||||
], () ->
|
||||
Range = ace.require("ace/range").Range
|
||||
|
||||
getLastCommandFragment = (lineUpToCursor) ->
|
||||
if m = lineUpToCursor.match(/(\\[^\\]+)$/)
|
||||
return m[1]
|
||||
else
|
||||
return null
|
||||
|
||||
class LabelsManager
|
||||
constructor: (@$scope, @editor, @element, @Labels) ->
|
||||
@debouncer = {} # DocId => Timeout
|
||||
|
||||
onChange = (change) =>
|
||||
if change.remote
|
||||
return
|
||||
if change.action not in ['remove', 'insert']
|
||||
return
|
||||
cursorPosition = @editor.getCursorPosition()
|
||||
end = change.end
|
||||
range = new Range(end.row, 0, end.row, end.column)
|
||||
lineUpToCursor = @editor.getSession().getTextRange(range)
|
||||
commandFragment = getLastCommandFragment(lineUpToCursor)
|
||||
linesContainLabel = _.any(change.lines, (line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/))
|
||||
lastCommandFragmentIsLabel = commandFragment?.slice(0,7) == '\\label{'
|
||||
if linesContainLabel or lastCommandFragmentIsLabel
|
||||
@scheduleLoadCurrentDocLabelsFromServer()
|
||||
|
||||
@editor.on "changeSession", (e) =>
|
||||
e.oldSession.off "change", onChange
|
||||
e.session.on "change", onChange
|
||||
|
||||
loadCurrentDocLabelsFromServer: () ->
|
||||
currentDocId = @$scope.docId
|
||||
@Labels.loadDocLabelsFromServer(currentDocId)
|
||||
|
||||
loadDocLabelsFromServer: (docId) ->
|
||||
@Labels.loadDocLabelsFromServer(docId)
|
||||
|
||||
scheduleLoadCurrentDocLabelsFromServer: () ->
|
||||
# De-bounce loading labels with a timeout
|
||||
currentDocId = @$scope.docId
|
||||
existingTimeout = @debouncer[currentDocId]
|
||||
if existingTimeout?
|
||||
clearTimeout(existingTimeout)
|
||||
delete @debouncer[currentDocId]
|
||||
@debouncer[currentDocId] = setTimeout(
|
||||
() =>
|
||||
@loadDocLabelsFromServer(currentDocId)
|
||||
delete @debouncer[currentDocId]
|
||||
, 1000
|
||||
, this
|
||||
)
|
||||
|
||||
getAllLabels: () ->
|
||||
@Labels.getAllLabels()
|
|
@ -1,13 +0,0 @@
|
|||
define [], () ->
|
||||
|
||||
class LabelsManager
|
||||
|
||||
constructor: (@ide, @$scope, @labels) ->
|
||||
|
||||
@ide.socket.on 'broadcastDocLabels', (data) =>
|
||||
@labels.onBroadcastDocLabels(data)
|
||||
@$scope.$on 'entity:deleted', @labels.onEntityDeleted
|
||||
@$scope.$on 'file:upload:complete', @labels.fileUploadComplete
|
||||
|
||||
loadProjectLabelsFromServer: () ->
|
||||
@labels.loadProjectLabelsFromServer()
|
|
@ -1,44 +0,0 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
|
||||
App.factory 'labels', ($http, ide) ->
|
||||
|
||||
state = {documents: {}}
|
||||
|
||||
labels = {
|
||||
state: state
|
||||
}
|
||||
|
||||
labels.onBroadcastDocLabels = (data) ->
|
||||
if data.docId and data.labels
|
||||
state.documents[data.docId] = data.labels
|
||||
|
||||
labels.onEntityDeleted = (e, entity) ->
|
||||
if entity.type == 'doc'
|
||||
delete state.documents[entity.id]
|
||||
|
||||
labels.onFileUploadComplete = (e, upload) ->
|
||||
if upload.entity_type == 'doc'
|
||||
labels.loadDocLabelsFromServer(upload.entity_id)
|
||||
|
||||
labels.getAllLabels = () ->
|
||||
_.flatten(labels for docId, labels of state.documents)
|
||||
|
||||
labels.loadProjectLabelsFromServer = () ->
|
||||
$http
|
||||
.get("/project/#{window.project_id}/labels")
|
||||
.then (response) ->
|
||||
{ data } = response
|
||||
if data.projectLabels
|
||||
for docId, docLabels of data.projectLabels
|
||||
state.documents[docId] = docLabels
|
||||
|
||||
labels.loadDocLabelsFromServer = (docId) ->
|
||||
$http
|
||||
.post(
|
||||
"/project/#{window.project_id}/doc/#{docId}/labels",
|
||||
{_csrf: window.csrfToken}
|
||||
)
|
||||
|
||||
return labels
|
|
@ -1,7 +1,7 @@
|
|||
define [
|
||||
"base"
|
||||
], (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 = {
|
||||
privileges: "readAndWrite"
|
||||
contacts: []
|
||||
|
@ -100,7 +100,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 +135,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 +212,8 @@ define [
|
|||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss()
|
||||
|
||||
|
||||
|
||||
App.controller "MakePublicModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
|
||||
$scope.inputs = {
|
||||
privileges: "readAndWrite"
|
||||
|
@ -244,4 +248,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) ->
|
||||
|
|
|
@ -30,6 +30,7 @@ define [
|
|||
"directives/maxHeight"
|
||||
"directives/creditCards"
|
||||
"services/queued-http"
|
||||
"services/validateCaptcha"
|
||||
"filters/formatDate"
|
||||
"__MAIN_CLIENTSIDE_INCLUDES__"
|
||||
], () ->
|
||||
|
|
24
services/web/public/coffee/services/validateCaptcha.coffee
Normal file
24
services/web/public/coffee/services/validateCaptcha.coffee
Normal file
|
@ -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
|
|
@ -104,3 +104,7 @@
|
|||
-ms-transform-origin: center bottom;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.grecaptcha-badge {
|
||||
display: none;
|
||||
}
|
|
@ -31,6 +31,8 @@ describe "EmailSender", ->
|
|||
|
||||
@sender = SandboxedModule.require modulePath, requires:
|
||||
'nodemailer': @ses
|
||||
"nodemailer-mandrill-transport":{}
|
||||
"nodemailer-sendgrid-transport":{}
|
||||
"settings-sharelatex":@settings
|
||||
'../../infrastructure/RateLimiter':@RateLimiter
|
||||
"logger-sharelatex":
|
||||
|
|
Loading…
Add table
Reference in a new issue