2016-06-26 11:53:19 +00:00
|
|
|
var error = require("http-error");
|
2017-06-08 13:05:06 +00:00
|
|
|
var RateLimiter = require('limiter').RateLimiter;
|
2016-06-26 11:53:19 +00:00
|
|
|
|
|
|
|
var MAX_EXPIRE_INTERVAL = 5000;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* In memory rate limiter as connect middleware
|
|
|
|
*/
|
|
|
|
module.exports = ratelimit;
|
|
|
|
|
|
|
|
function ratelimit(key, duration, max) {
|
2017-06-08 13:05:06 +00:00
|
|
|
|
|
|
|
var buckets = Object.create(null); // in case there handles like 'constructor'
|
2016-10-26 02:36:37 +00:00
|
|
|
var rootKey = "params";
|
|
|
|
if (/^req\./.test(key)) {
|
|
|
|
rootKey = null;
|
|
|
|
key = key.replace(/^req\./, "");
|
|
|
|
}
|
|
|
|
|
2016-10-05 21:09:00 +00:00
|
|
|
// Returns a deep value from an object. E.g. resolveValue({user: {id: 5}}, "user.id") === 5
|
2016-09-29 21:46:48 +00:00
|
|
|
function resolveValue(obj, path) {
|
2017-06-02 09:09:02 +00:00
|
|
|
if (path === "*")
|
|
|
|
return "*";
|
|
|
|
|
2016-09-29 21:46:48 +00:00
|
|
|
return path.split('.').reduce(function(prev, curr) {
|
|
|
|
return prev ? prev[curr] : undefined;
|
|
|
|
}, obj);
|
|
|
|
}
|
2017-06-08 13:05:06 +00:00
|
|
|
|
|
|
|
// cleanup empty buckets
|
|
|
|
setInterval(function() {
|
|
|
|
Object.keys(buckets).forEach(function(handle) {
|
|
|
|
var bucket = buckets[handle];
|
2017-06-08 14:32:17 +00:00
|
|
|
if (bucket.tokenBucket.content === 0) {
|
2017-06-08 13:05:06 +00:00
|
|
|
delete buckets[handle];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}, 5 * 1000);
|
2016-09-29 21:46:48 +00:00
|
|
|
|
2016-06-26 11:53:19 +00:00
|
|
|
return function(req, res, next) {
|
2016-10-26 02:36:37 +00:00
|
|
|
var root = rootKey ? req[rootKey] : req;
|
|
|
|
var handle = resolveValue(root, key);
|
2016-06-26 11:53:19 +00:00
|
|
|
|
2017-06-08 13:05:06 +00:00
|
|
|
buckets[handle] = buckets[handle] || new RateLimiter(max, duration, true);
|
|
|
|
var removed = buckets[handle].tryRemoveTokens(1);
|
|
|
|
if (!removed) {
|
2016-06-26 11:53:19 +00:00
|
|
|
var err = new error.TooManyRequests("Rate limit exceeded");
|
|
|
|
err.retryIn = Math.min(duration, 5000);
|
|
|
|
return next(err);
|
|
|
|
}
|
2017-06-08 13:05:06 +00:00
|
|
|
|
2016-06-26 11:53:19 +00:00
|
|
|
return next();
|
|
|
|
};
|
|
|
|
}
|