diff --git a/api/src/middleware/rate-limiter.ts b/api/src/middleware/rate-limiter.ts index 6e0241019e..0457835d19 100644 --- a/api/src/middleware/rate-limiter.ts +++ b/api/src/middleware/rate-limiter.ts @@ -5,53 +5,13 @@ * in future */ import { RequestHandler } from 'express'; -import redis from 'redis'; import asyncHandler from 'express-async-handler'; -import { RateLimiterRedis, RateLimiterMemory } from 'rate-limiter-flexible'; import { HitRateLimitException } from '../exceptions'; -import { RedisNotFoundException } from '../exceptions'; -import env from '../env'; - -const redisClient = redis.createClient({ - enable_offline_queue: false, - host: env.REDIS_HOST, - port: env.REDIS_PORT, - password: env.REDIS_PASSWORD, -}); +import rateLimiterConfig from '../utils/get-rate-limiter-config'; const rateLimiter: RequestHandler = asyncHandler(async (req, res, next) => { - // options for the rate limiter are set below. Opts can be found - // at https://github.com/animir/node-rate-limiter-flexible/wiki/Options - // more basic for memory store - const opts = { - points: env.CONSUMED_POINTS_LIMIT, // Number of points - duration: env.CONSUMED_RESET_DURATION, // Number of seconds before consumed points are reset. - keyPrefix: 'rlflx', // must be unique for limiters with different purpose - }; - - let rateLimiterSet = new RateLimiterMemory(opts); - - if (env.RATE_LIMIT_TYPE === 'redis') { - const redisOpts = { - ...opts, - storeClient: redisClient, - // Custom - execEvenly: env.EXEC_EVENLY, // delay actions after first action - this may need adjusting (leaky bucket) - blockDuration: env.BLOCK_POINT_DURATION, // Do not block if consumed more than points - inmemoryBlockOnConsumed: env.INMEMORY_BLOCK_CONSUMED, // eg if 200 points consumed - inmemoryBlockDuration: env.INMEMEMORY_BLOCK_DURATION, // block for certain amount of seconds - }; - - rateLimiterSet = new RateLimiterRedis(redisOpts); - - // first need to check that redis is running! - if (!redisClient) { - throw new RedisNotFoundException('Redis client does not exist'); - } - } - try { - await rateLimiterSet.consume(req.ip); + await rateLimiterConfig.consume(req.ip); } catch (rejRes) { // If there is no error, rateLimiterRedis promise rejected with number of ms before next request allowed const secs = Math.round(rejRes.msBeforeNext / 1000) || 1; diff --git a/api/src/utils/get-rate-limiter-config.ts b/api/src/utils/get-rate-limiter-config.ts new file mode 100644 index 0000000000..cd50e37120 --- /dev/null +++ b/api/src/utils/get-rate-limiter-config.ts @@ -0,0 +1,85 @@ +import redis from 'redis'; +import { + RateLimiterRedis, + RateLimiterMemory, + IRateLimiterStoreOptions, + IRateLimiterOptions, +} from 'rate-limiter-flexible'; +import { RedisNotFoundException } from '../exceptions'; + +import env from '../env'; + +// options for the rate limiter are set below. Opts can be found +// at https://github.com/animir/node-rate-limiter-flexible/wiki/Options + +const redisClient = redis.createClient({ + enable_offline_queue: false, + host: env.REDIS_HOST, + port: env.REDIS_PORT, + password: env.REDIS_PASSWORD, +}); + +let rateLimiterConfig = new RateLimiterMemory(getRateLimiterConfig()); +// need to pick redis or memory +if (env.RATE_LIMIT_TYPE === 'redis') { + rateLimiterConfig = new RateLimiterRedis(getRateLimiterRedisConfig()); +} +// first need to check that redis is running! + +if (!redisClient) { + throw new RedisNotFoundException('Redis client does not exist'); +} + +export default rateLimiterConfig; + +function getRateLimiterConfig(): IRateLimiterOptions { + const config: any = {}; + config.keyPrefix = 'rlflx'; + for (const [key, value] of Object.entries(env)) { + if (key === 'CONSUMED_POINTS_LIMIT') { + config.points = value; + continue; + } + if (key === 'CONSUMED_RESET_DURATION') { + config.duration = value; + continue; + } + } + + return config; +} + +function getRateLimiterRedisConfig(): IRateLimiterStoreOptions { + const redisConfig: any = {}; + redisConfig.keyPrefix = 'rlflx'; + redisConfig.storeClient = redisClient; + + for (const [key, value] of Object.entries(env)) { + if (key === 'CONSUMED_POINTS_LIMIT') { + redisConfig.points = value; + continue; + } + if (key === 'CONSUMED_RESET_DURATION') { + redisConfig.duration = value; + continue; + } + if (key === 'EXEC_EVENLY') { + redisConfig.execEvenly = value; + continue; + } + if (key === 'BLOCK_POINT_DURATION') { + redisConfig.blockDuration = value; + continue; + } + if (key === 'INMEMORY_BLOCK_CONSUMED') { + redisConfig.inmemoryBlockOnConsumed = value; + continue; + } + if (key === 'INMEMEMORY_BLOCK_DURATION') { + redisConfig.inmemoryBlockDuration = value; + continue; + } + } + + return redisConfig; +}