From c8c0a0d46ba9c5513eefeb82cbcaf9af55df78cc Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Mon, 14 Oct 2024 16:04:24 +0200 Subject: [PATCH] refactor: change fastify rate limiting config --- package.json | 2 +- src/backend/lib/config.ts | 2 +- src/backend/lib/types.ts | 19 +++++++++++++++---- src/backend/server.ts | 18 +++++++++++++++++- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index cbfe61a..4053aa3 100644 --- a/package.json +++ b/package.json @@ -54,4 +54,4 @@ "extends": "@sapphire" }, "packageManager": "yarn@4.5.0" -} \ No newline at end of file +} diff --git a/src/backend/lib/config.ts b/src/backend/lib/config.ts index ffebd63..86240bb 100644 --- a/src/backend/lib/config.ts +++ b/src/backend/lib/config.ts @@ -16,7 +16,7 @@ export const config: Config = { rateLimits: { max: envParseInteger('RATE_LIMIT_MAX', 500), - timeWindow: envParseString('RATE_LIMIT_TIME_WINDOW', '1 minute') + timeWindow: envParseInteger('RATE_LIMIT_TIME_WINDOW', 1000 * 60) }, storage: { diff --git a/src/backend/lib/types.ts b/src/backend/lib/types.ts index ff431fd..eeffae5 100644 --- a/src/backend/lib/types.ts +++ b/src/backend/lib/types.ts @@ -27,10 +27,21 @@ export interface Config { /** The rate limit configuration */ interface RateLimitsConfig { - /** The maximum amount of requests within the timespan {@link RateLimitsConfig.timeWindow timeWindow} */ + /** + * Maximum number of requests a single client can perform inside a {@link RateLimitsConfig.timeWindow timeWindow}. + * + * It can be an async function with the signature `async (request, key) => {}` where `request` is the + * Fastify request object and `key` is the value generated by the `keyGenerator`. The function **must** return a number. + */ max: number; - /** The time window in which the requests count, in the {@linkplain https://github.com/zeit/ms ms} string format */ - timeWindow: string; + /** + * The duration of the time window. + * + * It can be expressed in milliseconds, as a string (in the [`ms`](https://github.com/zeit/ms) format), or as an + * async function with the signature `async (request, key) => {}` where `request` is the Fastify request object and + * `key` is the value generated by the `keyGenerator`. The function **must** return a number. + */ + timeWindow: number; } /** The storage options */ @@ -80,7 +91,7 @@ declare module '@skyra/env-utilities' { KEY_LENGTH: IntegerString; MAX_LENGTH: IntegerString; RATE_LIMIT_MAX: IntegerString; - RATE_LIMIT_TIME_WINDOW: string; + RATE_LIMIT_TIME_WINDOW: IntegerString; STORAGE_TYPE: 'file' | 'redis'; STORAGE_HOST: string; STORAGE_PORT: IntegerString; diff --git a/src/backend/server.ts b/src/backend/server.ts index 5b3e723..b8b89eb 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -47,7 +47,23 @@ fastify.setErrorHandler((error, _, reply) => { // Register the rate limit handler await fastify.register(import('@fastify/rate-limit'), { max: config.rateLimits.max, - timeWindow: config.rateLimits.timeWindow + timeWindow: config.rateLimits.timeWindow, + keyGenerator: (request) => { + console.log(request.headers); + const xRealIp = request.headers['x-real-ip']; + if (typeof xRealIp === 'string') return xRealIp; + return request.ip; + }, + errorResponseBuilder: (_, context) => { + return { + statusCode: 429, + error: 'Too Many Requests', + message: `Rate limit exceeded. Only ${context.max} requests per ${context.after} to this service are allowed. Retry in ${context.after}ms.`, + date: Date.now(), + expiresIn: context.ttl + }; + }, + allowList: [] }); // Register Fastify Sensible for sending errors