overleaf/services/web/app/src/Features/Security/RateLimiterMiddleware.js
Alexandre Bourdin 9468e5cb4f Merge pull request #4338 from overleaf/ab-session-manager
Extract functions from AuthenticationController to SessionManager

GitOrigin-RevId: 86870ce03a762e1a837dcf493759e8851e759883
2021-07-28 12:36:22 +00:00

85 lines
2.4 KiB
JavaScript

const RateLimiter = require('../../infrastructure/RateLimiter')
const logger = require('logger-sharelatex')
const SessionManager = require('../Authentication/SessionManager')
const LoginRateLimiter = require('./LoginRateLimiter')
const settings = require('@overleaf/settings')
/*
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"])
will rate limit each project_id separately.
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 = SessionManager.getLoggedInUserId(req.session) || 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