filesafe/controllers/middlewares/RateLimiter.js

61 lines
1.9 KiB
JavaScript
Raw Normal View History

const { RateLimiterMemory } = require('rate-limiter-flexible')
const ClientError = require('./../utils/ClientError')
class RateLimiter {
2022-07-12 03:30:36 +00:00
rateLimiterMemory
#requestKey
#whitelistedKeys
constructor (requestKey, options = {}, whitelistedKeys) {
if (typeof options.points !== 'number' || typeof options.duration !== 'number') {
throw new Error('Points and Duration must be set with numbers in options')
}
if (whitelistedKeys && typeof whitelistedKeys instanceof Set) {
throw new TypeError('Whitelisted keys must be a Set')
}
this.#requestKey = requestKey
this.#whitelistedKeys = new Set(whitelistedKeys)
this.rateLimiterMemory = new RateLimiterMemory(options)
}
2022-07-21 17:57:57 +00:00
#middleware (req, res, next) {
if (res.locals.rateLimit) {
return next()
}
// If unset, assume points pool is shared to all visitors of each route
const key = this.#requestKey ? req[this.#requestKey] : req.path
if (this.#whitelistedKeys.has(key)) {
// Set the Response local variable for earlier bypass in any subsequent RateLimit middlewares
res.locals.rateLimit = 'BYPASS'
2022-07-21 17:57:57 +00:00
return next()
}
// Always consume only 1 point
2022-07-21 17:57:57 +00:00
this.rateLimiterMemory.consume(key, 1)
.then(result => {
res.locals.rateLimit = result
res.header('Retry-After', String(result.msBeforeNext / 1000))
res.header('X-RateLimit-Limit', String(this.rateLimiterMemory._points))
res.header('X-RateLimit-Remaining', String(result.remainingPoints))
res.header('X-RateLimit-Reset', String(new Date(Date.now() + result.msBeforeNext)))
2022-07-21 17:57:57 +00:00
return next()
})
.catch(reject => {
// Re-throw with ClientError
2022-07-21 17:57:57 +00:00
return next(new ClientError('Rate limit reached, please try again in a while.', { statusCode: 429 }))
})
}
get middleware () {
return this.#middleware.bind(this)
}
}
module.exports = RateLimiter