From 416c9785701a15922390ba5da895de590efe8f9d Mon Sep 17 00:00:00 2001 From: Kiko Beats Date: Tue, 9 Jan 2024 13:22:36 +0100 Subject: [PATCH] feat: use in memory rate limiter The service is massive and redis based rate limiter is spending too much money for a simple thing as this --- package.json | 2 +- src/authentication.js | 39 ++++++++++++++++++++++----------------- src/util/rate-limiter.js | 16 ---------------- 3 files changed, 23 insertions(+), 34 deletions(-) delete mode 100644 src/util/rate-limiter.js diff --git a/package.json b/package.json index 4426979..d93956b 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,6 @@ "@microlink/mql": "~0.12.2", "@microlink/ping-url": "~1.4.11", "@microlink/ua": "~1.2.0", - "async-ratelimiter": "~1.3.13", "browserless": "~10.2.4", "cacheable-lookup": "~6.1.0", "cacheable-response": "~2.8.10", @@ -119,6 +118,7 @@ "p-timeout": "~4.1.0", "puppeteer": "~21.7.0", "qsm": "~2.1.2", + "rate-limiter-flexible": "~4.0.0", "router-http": "~1.0.5", "send-http": "~1.0.6", "serve-static": "~1.15.0", diff --git a/src/authentication.js b/src/authentication.js index 81567bb..e10c902 100644 --- a/src/authentication.js +++ b/src/authentication.js @@ -1,9 +1,14 @@ 'use strict' -const rateLimiter = require('./util/rate-limiter') +const { RateLimiterMemory } = require('rate-limiter-flexible') const debug = require('debug-logfmt')('unavatar:rate') -const { API_KEY } = require('./constant') +const { API_KEY, RATE_LIMIT_WINDOW, RATE_LIMIT } = require('./constant') + +const rateLimiter = new RateLimiterMemory({ + points: RATE_LIMIT, + duration: RATE_LIMIT_WINDOW +}) const more = (() => { const email = 'hello@microlink.io' @@ -30,21 +35,21 @@ const rateLimitError = (() => { return rateLimitError })() -module.exports = rateLimiter - ? async (req, res, next) => { - if (req.headers['x-api-key'] === API_KEY) return next() - const { total, reset, remaining } = await rateLimiter.get({ - id: req.ipAddress - }) +module.exports = async (req, res, next) => { + if (req.headers['x-api-key'] === API_KEY) return next() - if (!res.writableEnded) { - const _remaining = Math.max(0, remaining - 1) - res.setHeader('X-Rate-Limit-Limit', total) - res.setHeader('X-Rate-Limit-Remaining', _remaining) - res.setHeader('X-Rate-Limit-Reset', reset) - debug(req.ipAddress, { total, remaining: _remaining }) - } + const { msBeforeNext, remainingPoints: remaining } = await rateLimiter + .consume(req.ipAddress) + .catch(error => error) - return remaining ? next() : next(rateLimitError) + if (!res.writableEnded) { + res.setHeader('X-Rate-Limit-Limit', RATE_LIMIT) + res.setHeader('X-Rate-Limit-Remaining', remaining) + res.setHeader('X-Rate-Limit-Reset', new Date(Date.now() + msBeforeNext)) + debug(req.ipAddress, { total: RATE_LIMIT, remaining }) } - : false + + if (remaining) return next() + res.setHeader('Retry-After', msBeforeNext / 1000) + return next(rateLimitError) +} diff --git a/src/util/rate-limiter.js b/src/util/rate-limiter.js deleted file mode 100644 index c94ccee..0000000 --- a/src/util/rate-limiter.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -const RateLimiter = require('async-ratelimiter') - -const { RATE_LIMIT_WINDOW, RATE_LIMIT } = require('../constant') - -const db = require('./redis') - -module.exports = db - ? new RateLimiter({ - db, - namespace: 'rate', - duration: RATE_LIMIT_WINDOW, - max: RATE_LIMIT - }) - : undefined