UNPKG

express-token-api-middleware

Version:

An express middleware that allows to protect an api behind token authentication, rate limiting and endpoint permissions.

122 lines (114 loc) 4.13 kB
const unitRegex = /([a-zA-Z]*)\s*$/; class Limiter { constructor(config) { this._config = config; this.queues = {}; } /** * A function that does nothing, but is used in reference when counting foreign requests that we should wait on. * @private */ _noop() {} /** * @param {number|string|RateLimit} rate * @returns {number} How many ms to wait between requests * @private */ static _validateRate(rate) { if (typeof rate == 'string') { let value = parseInt(rate); let unit = rate.match(unitRegex)[0]; rate = { value, unit }; } if (typeof rate == 'object') { switch(rate.unit.toLowerCase()) { case 'ns': rate = Math.ceil(rate.value / 1000000); break; case '': case 'ms': rate = rate.value; break; case 's': rate = rate.value * 1000; break; case 'm': rate = rate.value * 1000 * 60; break; case 'h': rate = rate.value * 1000 * 60 * 60; break; case 'd': rate = rate.value * 1000 * 60 * 60 * 24; break; case 'w': rate = rate.value * 1000 * 60 * 60 * 24 * 7; break; default: throw new Error('Unknown unit specified for rate limit: ' + rate.unit); } } if (rate < 1) { throw new Error('Invalid rate specified for determining rate limits:' + rate); } return rate; } /** * Check's whether there's another request blocking this one based on the rate limit configuration of the token. * Users without a rate limit settings will not be blocked. * @param {TokenConfig} user The user object that has the rate configuration * @param {function} cb The next() handler passed in from express */ check(user, cb) { if (!user.rate) { return cb(); } let rate = Limiter._validateRate(user.rate); if (!this.queues[user.id]) { this.queues[user.id] = []; cb(); setTimeout(this._process.bind(this), rate * this._config.nodes, user); } else { if (this._config.timeout) { if ((this.queues[user.id].length + 1) * rate * this._config.nodes >= this._config.timeout) { throw new Error('Queue limit exceeded due to timeout setting'); } } this.queues[user.id].push(cb) } } /** * Allows a third party to notify this limiter that requests have been made somewhere else that should be counted * towards the request limit. * @param {TokenConfig} user The user for which to update the request count * @param {number} [requests=1] How many requests have been made somewhere else */ notify(user, requests = 1) { if (!this.queues[user.id]) { let rate = Limiter._validateRate(user.rate); this.queues[user.id] = []; for (let i = 1; i < requests; i++) { this.queues[user.id].push(this._noop); } return setTimeout(this._process.bind(this), rate * this._config.nodes, user); } for (let i = 0; i < requests; i++) { this.queues[user.id].push(this._noop); } } /** * Processes the next request on the queue after a timeout. * @param {TokenConfig} user * @private */ _process(user) { if (!this.queues[user.id].length) { return delete this.queues[user.id]; } let rate = Limiter._validateRate(user.rate); let cb = this.queues[user.id].shift(); cb(); setTimeout(this._process.bind(this), rate * this._config.nodes, user); } } module.exports = Limiter;