RateLimiterMiddleware.mjs 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import logger from '@overleaf/logger'
  2. import SessionManager from '../Authentication/SessionManager.mjs'
  3. import LoginRateLimiter from './LoginRateLimiter.mjs'
  4. import settings from '@overleaf/settings'
  5. /**
  6. * Return a rate limiting middleware
  7. *
  8. * Pass an array of opts.params to segment this based on parameters in the
  9. * request URL, e.g.:
  10. *
  11. * app.get "/project/:project_id", RateLimiterMiddleware.rateLimit(
  12. * rateLimiter, params: ["project_id"]
  13. * )
  14. *
  15. * will rate limit each project_id separately.
  16. *
  17. * Unique clients are identified by user_id if logged in, and IP address if not.
  18. * The method label is used to identify this in our metrics.
  19. */
  20. function rateLimit(rateLimiter, opts = {}) {
  21. const getUserId =
  22. opts.getUserId || (req => SessionManager.getLoggedInUserId(req.session))
  23. return function (req, res, next) {
  24. const clientId = opts.ipOnly ? req.ip : getUserId(req) || req.ip
  25. const method = clientId === req.ip ? 'ip' : 'userId'
  26. if (
  27. settings.smokeTest &&
  28. settings.smokeTest.userId &&
  29. settings.smokeTest.userId.toString() === clientId.toString()
  30. ) {
  31. // ignore smoke test user
  32. return next()
  33. }
  34. let key = clientId
  35. if (!opts.ipOnly) {
  36. const params = (opts.params || []).map(p => req.params[p])
  37. params.push(clientId)
  38. key = params.join(':')
  39. }
  40. rateLimiter
  41. .consume(key, 1, { method })
  42. .then(() => next())
  43. .catch(err => {
  44. if (err instanceof Error) {
  45. next(err)
  46. } else {
  47. res.status(429) // Too many requests
  48. res.write('Rate limit reached, please try again later')
  49. res.end()
  50. }
  51. })
  52. }
  53. }
  54. function loginRateLimitEmail(emailField = 'email') {
  55. return function (req, res, next) {
  56. const email = req.body[emailField]
  57. if (!email) {
  58. return next()
  59. }
  60. LoginRateLimiter.processLoginRequest(email, (err, isAllowed) => {
  61. if (err) {
  62. return next(err)
  63. }
  64. if (isAllowed) {
  65. next()
  66. } else {
  67. logger.warn({ email }, 'rate limit exceeded')
  68. res.status(429) // Too many requests
  69. res.json({
  70. message: {
  71. type: 'error',
  72. text: req.i18n.translate('to_many_login_requests_2_mins'),
  73. key: 'to-many-login-requests-2-mins',
  74. },
  75. })
  76. }
  77. })
  78. }
  79. }
  80. const RateLimiterMiddleware = {
  81. rateLimit,
  82. loginRateLimitEmail,
  83. }
  84. export default RateLimiterMiddleware