DockerERTFF/lib/modules/ratelimit.js

85 lines
3.5 KiB
JavaScript

module.exports = {
/**
* Setup a rate limiter instance
*
* @param {Object} options
* @param {number} options.points - Maximum number of points
* @param {number} options.duration - Duration in seconds
* @param {number} options.blockDuration - Duration in seconds to block the user
* @param {string} name - The name of the rate limiter instance
*/
setup: function (options, name) {
if (!name) throw new Error('rateLimit.setup has no name.');
this.setRateLimiter(name, options);
},
/**
* Consume points from a rate limiter instance
*
* @typedef {Object} options
* @param {string} options.instance - The rate limiter instance name
* @param {number} [options.points=1] - The number of points to consume
* @param {number} [options.status=429] - The status code to return
* @param {string} [options.message=Too Many Requests] - The message to return
* @param {boolean} [options.throw=false] - Throw an error instead of sending a response
*
* @return {Promise} - Resolves if the points were consumed, rejects if the rate limit was exceeded and options.throw is true
*/
consume: function (options) {
options = options || {};
options.points = options.points || 1;
options.status = options.status || 429;
options.message = options.message || 'Too Many Requests';
if (options.instance) {
const rateLimiter = this.getRateLimiter(options.instance);
return rateLimiter.consume(this.req.ip, options.points).catch(() => {
if (options.throw) {
throw new Error(options.message);
} else {
if (this.req.is('json')) {
this.res.status(options.status).json({ error: options.message });
} else {
this.res.status(options.status).send(options.message);
}
}
});
} else if (this.req.app.rateLimiter) {
const config = require('../setup/config');
let isPrivate = false;
let key = this.req.ip;
if (this.req.app.privateRateLimiter) {
if (this.req.session && this.req.session[config.rateLimit.private.provider + 'Id']) {
isPrivate = true;
key = this.req.session[config.rateLimit.private.provider + 'Id'];
}
}
const limit = isPrivate ? config.rateLimit.private.points : config.rateLimit.points;
const duration = isPrivate ? config.rateLimit.private.duration : config.rateLimit.duration;
this.req.app[isPrivate ? 'privateRateLimiter' : 'rateLimiter'].consume(key, points).then((rateLimiterRes) => {
const reset = Math.ceil(rateLimiterRes.msBeforeNext / 1000);
this.res.set('RateLimit-Policy', `${limit};w=${duration}`);
this.res.set('RateLimit', `limit=${limit}, remaining=${rateLimiterRes.remainingPoints}, reset=${reset}`);
next();
}).catch((rateLimiterRes) => {
if (options.throw) {
throw new Error(options.message);
} else {
const reset = Math.ceil(rateLimiterRes.msBeforeNext / 1000);
this.res.set('RateLimit-Policy', `${limit};w=${duration}`);
this.res.set('RateLimit', `limit=${limit}, remaining=${rateLimiterRes.remainingPoints}, reset=${reset}`);
this.res.set('Retry-After', reset);
if (this.req.is('json')) {
this.res.status(options.status).json({ error: options.message });
} else {
this.res.status(options.status).send(options.message);
}
}
});
}
},
};