85 lines
3.5 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
};
|