RateLimiterMiddleware.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. const RateLimiter = require('../../infrastructure/RateLimiter')
  2. const logger = require('@overleaf/logger')
  3. const SessionManager = require('../Authentication/SessionManager')
  4. const LoginRateLimiter = require('./LoginRateLimiter')
  5. const settings = require('@overleaf/settings')
  6. /*
  7. Do not allow more than opts.maxRequests from a single client in
  8. opts.timeInterval. Pass an array of opts.params to segment this based on
  9. parameters in the request URL, e.g.:
  10. app.get "/project/:project_id", RateLimiterMiddleware.rateLimit(endpointName: "open-editor", params: ["project_id"])
  11. will rate limit each project_id separately.
  12. Unique clients are identified by user_id if logged in, and IP address if not.
  13. */
  14. function rateLimit(opts) {
  15. const getUserId =
  16. opts.getUserId || (req => SessionManager.getLoggedInUserId(req.session))
  17. return function (req, res, next) {
  18. const userId = getUserId(req) || req.ip
  19. if (
  20. settings.smokeTest &&
  21. settings.smokeTest.userId &&
  22. settings.smokeTest.userId.toString() === userId.toString()
  23. ) {
  24. // ignore smoke test user
  25. return next()
  26. }
  27. const params = (opts.params || []).map(p => req.params[p])
  28. params.push(userId)
  29. let subjectName = params.join(':')
  30. if (opts.ipOnly) {
  31. subjectName = req.ip
  32. }
  33. if (opts.endpointName == null) {
  34. throw new Error('no endpointName provided')
  35. }
  36. const options = {
  37. endpointName: opts.endpointName,
  38. timeInterval: opts.timeInterval || 60,
  39. subjectName,
  40. throttle: opts.maxRequests || 6,
  41. }
  42. return RateLimiter.addCount(options, function (error, canContinue) {
  43. if (error != null) {
  44. return next(error)
  45. }
  46. if (canContinue) {
  47. return next()
  48. } else {
  49. logger.warn(options, 'rate limit exceeded')
  50. res.status(429) // Too many requests
  51. res.write('Rate limit reached, please try again later')
  52. return res.end()
  53. }
  54. })
  55. }
  56. }
  57. function rateLimitV2(rateLimiter, opts = {}) {
  58. const getUserId =
  59. opts.getUserId || (req => SessionManager.getLoggedInUserId(req.session))
  60. return function (req, res, next) {
  61. const userId = getUserId(req) || req.ip
  62. if (
  63. settings.smokeTest &&
  64. settings.smokeTest.userId &&
  65. settings.smokeTest.userId.toString() === userId.toString()
  66. ) {
  67. // ignore smoke test user
  68. return next()
  69. }
  70. let key
  71. if (opts.ipOnly) {
  72. key = req.ip
  73. } else {
  74. const params = (opts.params || []).map(p => req.params[p])
  75. params.push(userId)
  76. key = params.join(':')
  77. }
  78. rateLimiter
  79. .consume(key)
  80. .then(() => next())
  81. .catch(err => {
  82. if (err instanceof Error) {
  83. next(err)
  84. } else {
  85. res.status(429) // Too many requests
  86. res.write('Rate limit reached, please try again later')
  87. res.end()
  88. }
  89. })
  90. }
  91. }
  92. function loginRateLimit(req, res, next) {
  93. const { email } = req.body
  94. if (!email) {
  95. return next()
  96. }
  97. LoginRateLimiter.processLoginRequest(email, function (err, isAllowed) {
  98. if (err) {
  99. return next(err)
  100. }
  101. if (isAllowed) {
  102. return next()
  103. } else {
  104. logger.warn({ email }, 'rate limit exceeded')
  105. res.status(429) // Too many requests
  106. res.write('Rate limit reached, please try again later')
  107. return res.end()
  108. }
  109. })
  110. }
  111. const RateLimiterMiddleware = {
  112. rateLimit,
  113. rateLimitV2,
  114. loginRateLimit,
  115. }
  116. module.exports = RateLimiterMiddleware