136 lines
3.7 KiB
JavaScript
136 lines
3.7 KiB
JavaScript
'use strict';
|
|
|
|
const REDIS_DB = 8;
|
|
|
|
const RATE_LIMIT_ENDPOINTS_GROUPS = {
|
|
ENDPOINT_1: 'anonymous',
|
|
ENDPOINT_2: 'static',
|
|
ENDPOINT_3: 'static-named',
|
|
ENDPOINT_5: 'dataview',
|
|
ENDPOINT_6: 'dataview-search',
|
|
ENDPOINT_7: 'analysis',
|
|
ENDPOINT_8: 'tile',
|
|
ENDPOINT_9: 'attributes',
|
|
ENDPOINT_10: 'named-list',
|
|
ENDPOINT_11: 'named-create',
|
|
ENDPOINT_12: 'named-get',
|
|
ENDPOINT_13: 'named',
|
|
ENDPOINT_14: 'named-update',
|
|
ENDPOINT_15: 'named-delete',
|
|
ENDPOINT_16: 'named-options',
|
|
ENDPOINT_17: 'named-tiles'
|
|
};
|
|
|
|
/**
|
|
* The full key is: rate-limit:store:{user}:{endpoint}
|
|
* The value is a Redis hash:
|
|
* maxBurst (b): Integer (as string)
|
|
* countPerPeriod (c): Integer (as string)
|
|
* period (p): Integer (as string)
|
|
*/
|
|
const RATE_LIMIT_STORE_KEY = 'rate-limit:store:';
|
|
|
|
/**
|
|
* The full key is: rate-limit:status:{user}:{endpoint}
|
|
* This key is managed by redis-cell (CL.THROTTLE command)
|
|
*/
|
|
const RATE_LIMIT_STATUS_KEY = 'rate-limit:status:';
|
|
|
|
|
|
function rateLimitMiddleware(metadataBackend, endpointGroup = null) {
|
|
return function rateLimit(req, res, next) {
|
|
|
|
const user = 'cdb';
|
|
// const user = res.locals.user;
|
|
if (!endpointGroup) {
|
|
endpointGroup = getEndpointGroup();
|
|
}
|
|
|
|
const redisParams = [
|
|
getLuaScript(),
|
|
2,
|
|
getStoreKey(user, endpointGroup), // KEY[1]
|
|
getStatusKey(user, endpointGroup) // KEY[2]
|
|
];
|
|
|
|
metadataBackend.redisCmd(REDIS_DB, 'EVAL', redisParams, function(err, data) {
|
|
if (err) {
|
|
return next(err);
|
|
}
|
|
|
|
if (!data || !Array.isArray(data)) {
|
|
return next();
|
|
}
|
|
|
|
const isBloqued = data[0];
|
|
const limit = data[1];
|
|
const remaining = data[2];
|
|
const retry = data[3];
|
|
const reset = data[4];
|
|
|
|
res.set({
|
|
'X-Rate-Limit-Limit': limit,
|
|
'X-Rate-Limit-Remaining': remaining,
|
|
'X-Rate-Limit-Retry-After': retry,
|
|
'X-Rate-Limit-Reset': reset
|
|
});
|
|
|
|
if (isBloqued) {
|
|
const err = new Error('You are over the limits.');
|
|
err.http_status = 429;
|
|
return next(err);
|
|
}
|
|
|
|
return next();
|
|
});
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the endpoint key in Redis
|
|
*/
|
|
function getEndpointGroup() {
|
|
// TODO: get endpoint from route path
|
|
return RATE_LIMIT_ENDPOINTS_GROUPS.ENDPOINT_8;
|
|
}
|
|
|
|
/**
|
|
* Returns Redis key where the limits are saved by user and endpoint
|
|
* @param {string} user
|
|
* @param {string} endpointGroup
|
|
*/
|
|
function getStoreKey(user, endpointGroup) {
|
|
return RATE_LIMIT_STORE_KEY + user + ':' + endpointGroup;
|
|
}
|
|
|
|
/**
|
|
* Returns Redis key where the current state of the limit by user and endpoint
|
|
* (so, the key where CL.THROTTLE works)
|
|
* @param {string} user
|
|
* @param {string} endpointGroup
|
|
*/
|
|
function getStatusKey(user, endpointGroup) {
|
|
return RATE_LIMIT_STATUS_KEY + user + ':' + endpointGroup;
|
|
}
|
|
|
|
function getLuaScript() {
|
|
return `
|
|
local limmits = {}
|
|
local limmitsArray = redis.call("HGETALL", KEYS[1])
|
|
if table.getn(limmitsArray) == 6 then
|
|
limmits[limmitsArray[1]] = limmitsArray[2]
|
|
limmits[limmitsArray[3]] = limmitsArray[4]
|
|
limmits[limmitsArray[5]] = limmitsArray[6]
|
|
|
|
return redis.call("CL.THROTTLE", KEYS[2], limmits['b'], limmits['c'], limmits['p'])
|
|
else
|
|
return nil
|
|
end
|
|
`;
|
|
}
|
|
|
|
module.exports.rateLimitMiddleware = rateLimitMiddleware;
|
|
module.exports.RATE_LIMIT_ENDPOINTS_GROUPS = RATE_LIMIT_ENDPOINTS_GROUPS;
|
|
module.exports.RATE_LIMIT_STORE_KEY = RATE_LIMIT_STORE_KEY;
|