mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
add email rate limiter on login
GitOrigin-RevId: a0da310e5537b420e46c9ed48f8b97051e7e933a
This commit is contained in:
parent
b5d7887dfb
commit
af63c8de97
2 changed files with 77 additions and 63 deletions
|
@ -1,73 +1,85 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
max-len,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let RateLimiterMiddleware
|
||||
const RateLimiter = require('../../infrastructure/RateLimiter')
|
||||
const logger = require('logger-sharelatex')
|
||||
const AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
const LoginRateLimiter = require('./LoginRateLimiter')
|
||||
const settings = require('settings-sharelatex')
|
||||
|
||||
module.exports = RateLimiterMiddleware = {
|
||||
/*
|
||||
Do not allow more than opts.maxRequests from a single client in
|
||||
opts.timeInterval. Pass an array of opts.params to segment this based on
|
||||
parameters in the request URL, e.g.:
|
||||
/*
|
||||
Do not allow more than opts.maxRequests from a single client in
|
||||
opts.timeInterval. Pass an array of opts.params to segment this based on
|
||||
parameters in the request URL, e.g.:
|
||||
|
||||
app.get "/project/:project_id", RateLimiterMiddleware.rateLimit(endpointName: "open-editor", params: ["project_id"])
|
||||
app.get "/project/:project_id", RateLimiterMiddleware.rateLimit(endpointName: "open-editor", params: ["project_id"])
|
||||
|
||||
will rate limit each project_id separately.
|
||||
will rate limit each project_id separately.
|
||||
|
||||
Unique clients are identified by user_id if logged in, and IP address if not.
|
||||
*/
|
||||
rateLimit(opts) {
|
||||
return function(req, res, next) {
|
||||
const user_id = AuthenticationController.getLoggedInUserId(req) || req.ip
|
||||
if (
|
||||
settings.smokeTest &&
|
||||
settings.smokeTest.userId &&
|
||||
settings.smokeTest.userId.toString() === user_id.toString()
|
||||
) {
|
||||
// ignore smoke test user
|
||||
return next()
|
||||
}
|
||||
const params = (opts.params || []).map(p => req.params[p])
|
||||
params.push(user_id)
|
||||
let subjectName = params.join(':')
|
||||
if (opts.ipOnly) {
|
||||
subjectName = req.ip
|
||||
}
|
||||
if (opts.endpointName == null) {
|
||||
throw new Error('no endpointName provided')
|
||||
}
|
||||
const options = {
|
||||
endpointName: opts.endpointName,
|
||||
timeInterval: opts.timeInterval || 60,
|
||||
subjectName,
|
||||
throttle: opts.maxRequests || 6
|
||||
}
|
||||
return RateLimiter.addCount(options, function(error, canContinue) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
if (canContinue) {
|
||||
return next()
|
||||
} else {
|
||||
logger.warn(options, 'rate limit exceeded')
|
||||
res.status(429) // Too many requests
|
||||
res.write('Rate limit reached, please try again later')
|
||||
return res.end()
|
||||
}
|
||||
})
|
||||
Unique clients are identified by user_id if logged in, and IP address if not.
|
||||
*/
|
||||
function rateLimit(opts) {
|
||||
return function(req, res, next) {
|
||||
const userId = AuthenticationController.getLoggedInUserId(req) || req.ip
|
||||
if (
|
||||
settings.smokeTest &&
|
||||
settings.smokeTest.userId &&
|
||||
settings.smokeTest.userId.toString() === userId.toString()
|
||||
) {
|
||||
// ignore smoke test user
|
||||
return next()
|
||||
}
|
||||
const params = (opts.params || []).map(p => req.params[p])
|
||||
params.push(userId)
|
||||
let subjectName = params.join(':')
|
||||
if (opts.ipOnly) {
|
||||
subjectName = req.ip
|
||||
}
|
||||
if (opts.endpointName == null) {
|
||||
throw new Error('no endpointName provided')
|
||||
}
|
||||
const options = {
|
||||
endpointName: opts.endpointName,
|
||||
timeInterval: opts.timeInterval || 60,
|
||||
subjectName,
|
||||
throttle: opts.maxRequests || 6
|
||||
}
|
||||
return RateLimiter.addCount(options, function(error, canContinue) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
if (canContinue) {
|
||||
return next()
|
||||
} else {
|
||||
logger.warn(options, 'rate limit exceeded')
|
||||
res.status(429) // Too many requests
|
||||
res.write('Rate limit reached, please try again later')
|
||||
return res.end()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function loginRateLimit(req, res, next) {
|
||||
const { email } = req.body
|
||||
if (!email) {
|
||||
return next()
|
||||
}
|
||||
LoginRateLimiter.processLoginRequest(email, function(err, isAllowed) {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
if (isAllowed) {
|
||||
return next()
|
||||
} else {
|
||||
logger.warn({ email }, 'rate limit exceeded')
|
||||
res.status(429) // Too many requests
|
||||
res.write('Rate limit reached, please try again later')
|
||||
return res.end()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const RateLimiterMiddleware = {
|
||||
rateLimit,
|
||||
loginRateLimit
|
||||
}
|
||||
|
||||
module.exports = RateLimiterMiddleware
|
||||
|
|
|
@ -139,7 +139,9 @@ module.exports = class UserHelper {
|
|||
json: userData
|
||||
})
|
||||
if (response.statusCode !== 200 || response.body.redir !== '/project') {
|
||||
throw new Error('login failed')
|
||||
const error = new Error('login failed')
|
||||
error.response = response
|
||||
throw error
|
||||
}
|
||||
userHelper.user = await UserGetter.promises.getUser({
|
||||
email: userData.email
|
||||
|
|
Loading…
Reference in a new issue