mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
rate limit emails sent sharing projects by users
This commit is contained in:
parent
a153375688
commit
fed88504f8
5 changed files with 67 additions and 21 deletions
|
@ -11,7 +11,7 @@ module.exports = CollaboratorsEmailHandler =
|
|||
"user_first_name=#{encodeURIComponent(project.owner_ref.first_name)}"
|
||||
].join("&")
|
||||
|
||||
notifyUserOfProjectInvite: (project_id, email, invite, callback)->
|
||||
notifyUserOfProjectInvite: (project_id, email, invite, sendingUser, callback)->
|
||||
Project
|
||||
.findOne(_id: project_id )
|
||||
.select("name owner_ref")
|
||||
|
@ -24,4 +24,5 @@ module.exports = CollaboratorsEmailHandler =
|
|||
name: project.name
|
||||
inviteUrl: CollaboratorsEmailHandler._buildInviteUrl(project, invite)
|
||||
owner: project.owner_ref
|
||||
sendingUser_id: sendingUser._id
|
||||
EmailHandler.sendEmail "projectInvite", emailOptions, callback
|
||||
|
|
|
@ -53,7 +53,7 @@ module.exports = CollaboratorsInviteHandler =
|
|||
|
||||
_sendMessages: (projectId, sendingUser, invite, callback=(err)->) ->
|
||||
logger.log {projectId, inviteId: invite._id}, "sending notification and email for invite"
|
||||
CollaboratorsEmailHandler.notifyUserOfProjectInvite projectId, invite.email, invite, (err)->
|
||||
CollaboratorsEmailHandler.notifyUserOfProjectInvite projectId, invite.email, invite, sendingUser, (err)->
|
||||
return callback(err) if err?
|
||||
CollaboratorsInviteHandler._trySendInviteNotification projectId, sendingUser, invite, (err)->
|
||||
return callback(err) if err?
|
||||
|
|
|
@ -4,7 +4,7 @@ Settings = require('settings-sharelatex')
|
|||
nodemailer = require("nodemailer")
|
||||
sesTransport = require('nodemailer-ses-transport')
|
||||
sgTransport = require('nodemailer-sendgrid-transport')
|
||||
|
||||
rateLimiter = require('../../infrastructure/RateLimiter')
|
||||
_ = require("underscore")
|
||||
|
||||
if Settings.email? and Settings.email.fromAddress?
|
||||
|
@ -39,24 +39,39 @@ if nm_client?
|
|||
else
|
||||
logger.warn "Failed to create email transport. Please check your settings. No email will be sent."
|
||||
|
||||
checkCanSendEmail = (options, callback)->
|
||||
if !options.sendingUser_id? #email not sent from user, not rate limited
|
||||
callback(null, true)
|
||||
opts =
|
||||
endpointName: "send_email"
|
||||
timeInterval: 60 * 60 * 3
|
||||
subjectName: options.sendingUser_id
|
||||
throttle: 100
|
||||
rateLimiter.addCount opts, callback
|
||||
|
||||
module.exports =
|
||||
sendEmail : (options, callback = (error) ->)->
|
||||
logger.log receiver:options.to, subject:options.subject, "sending email"
|
||||
metrics.inc "email"
|
||||
options =
|
||||
to: options.to
|
||||
from: defaultFromAddress
|
||||
subject: options.subject
|
||||
html: options.html
|
||||
text: options.text
|
||||
replyTo: options.replyTo || Settings.email.replyToAddress
|
||||
socketTimeout: 30 * 1000
|
||||
if Settings.email.textEncoding?
|
||||
opts.textEncoding = textEncoding
|
||||
client.sendMail options, (err, res)->
|
||||
checkCanSendEmail options, (err, canContinue)->
|
||||
if err?
|
||||
logger.err err:err, "error sending message"
|
||||
else
|
||||
logger.log "Message sent to #{options.to}"
|
||||
callback(err)
|
||||
return callback(err)
|
||||
if !canContinue
|
||||
logger.log sendingUser_id:options.sendingUser_id, to:options.to, subject:options.subject, canContinue:canContinue, "rate limit hit for sending email, not sending"
|
||||
return callback("rate limit hit sending email")
|
||||
metrics.inc "email"
|
||||
options =
|
||||
to: options.to
|
||||
from: defaultFromAddress
|
||||
subject: options.subject
|
||||
html: options.html
|
||||
text: options.text
|
||||
replyTo: options.replyTo || Settings.email.replyToAddress
|
||||
socketTimeout: 30 * 1000
|
||||
if Settings.email.textEncoding?
|
||||
opts.textEncoding = textEncoding
|
||||
client.sendMail options, (err, res)->
|
||||
if err?
|
||||
logger.err err:err, "error sending message"
|
||||
else
|
||||
logger.log "Message sent to #{options.to}"
|
||||
callback(err)
|
||||
|
|
|
@ -185,7 +185,7 @@ describe "CollaboratorsInviteHandler", ->
|
|||
describe '_sendMessages', ->
|
||||
|
||||
beforeEach ->
|
||||
@CollaboratorsEmailHandler.notifyUserOfProjectInvite = sinon.stub().callsArgWith(3, null)
|
||||
@CollaboratorsEmailHandler.notifyUserOfProjectInvite = sinon.stub().callsArgWith(4, null)
|
||||
@CollaboratorsInviteHandler._trySendInviteNotification = sinon.stub().callsArgWith(3, null)
|
||||
@call = (callback) =>
|
||||
@CollaboratorsInviteHandler._sendMessages @projectId, @sendingUser, @fakeInvite, callback
|
||||
|
@ -213,7 +213,7 @@ describe "CollaboratorsInviteHandler", ->
|
|||
describe 'when CollaboratorsEmailHandler.notifyUserOfProjectInvite produces an error', ->
|
||||
|
||||
beforeEach ->
|
||||
@CollaboratorsEmailHandler.notifyUserOfProjectInvite = sinon.stub().callsArgWith(3, new Error('woops'))
|
||||
@CollaboratorsEmailHandler.notifyUserOfProjectInvite = sinon.stub().callsArgWith(4, new Error('woops'))
|
||||
|
||||
it 'should produce an error', (done) ->
|
||||
@call (err, invite) =>
|
||||
|
|
|
@ -10,6 +10,9 @@ describe "EmailSender", ->
|
|||
|
||||
beforeEach ->
|
||||
|
||||
@RateLimiter =
|
||||
addCount:sinon.stub()
|
||||
|
||||
@settings =
|
||||
email:
|
||||
transport: "ses"
|
||||
|
@ -21,11 +24,15 @@ describe "EmailSender", ->
|
|||
|
||||
@sesClient =
|
||||
sendMail: sinon.stub()
|
||||
|
||||
@ses =
|
||||
createTransport: => @sesClient
|
||||
|
||||
|
||||
@sender = SandboxedModule.require modulePath, requires:
|
||||
'nodemailer': @ses
|
||||
"settings-sharelatex":@settings
|
||||
'../../infrastructure/RateLimiter':@RateLimiter
|
||||
"logger-sharelatex":
|
||||
log:->
|
||||
warn:->
|
||||
|
@ -84,6 +91,29 @@ describe "EmailSender", ->
|
|||
args.replyTo.should.equal @opts.replyTo
|
||||
done()
|
||||
|
||||
|
||||
it "should not send an email when the rate limiter says no", (done)->
|
||||
@opts.sendingUser_id = "12321312321"
|
||||
@RateLimiter.addCount.callsArgWith(1, null, false)
|
||||
@sender.sendEmail @opts, =>
|
||||
@sesClient.sendMail.called.should.equal false
|
||||
done()
|
||||
|
||||
it "should send the email when the rate limtier says continue", (done)->
|
||||
@sesClient.sendMail.callsArgWith(1)
|
||||
@opts.sendingUser_id = "12321312321"
|
||||
@RateLimiter.addCount.callsArgWith(1, null, true)
|
||||
@sender.sendEmail @opts, =>
|
||||
@sesClient.sendMail.called.should.equal true
|
||||
done()
|
||||
|
||||
it "should not check the rate limiter when there is no sendingUser_id", (done)->
|
||||
@sesClient.sendMail.callsArgWith(1)
|
||||
@sender.sendEmail @opts, =>
|
||||
@sesClient.sendMail.called.should.equal true
|
||||
@RateLimiter.addCount.called.should.equal false
|
||||
done()
|
||||
|
||||
describe 'with plain-text email content', () ->
|
||||
|
||||
beforeEach ->
|
||||
|
|
Loading…
Reference in a new issue