From 77a88f1575dd2b3bdb8386929ae74e7180e896ef Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sat, 19 Oct 2024 06:35:49 +0400 Subject: [PATCH 1/2] feat: better rate limit errors --- backend/src/lib/errors/index.ts | 7 +++++++ backend/src/server/config/rateLimiter.ts | 6 ++++++ backend/src/server/plugins/error-handler.ts | 10 +++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/src/lib/errors/index.ts b/backend/src/lib/errors/index.ts index 01d1c8b8e..0818cfe7d 100644 --- a/backend/src/lib/errors/index.ts +++ b/backend/src/lib/errors/index.ts @@ -71,6 +71,13 @@ export class BadRequestError extends Error { } } +export class RateLimitError extends Error { + constructor({ message }: { message?: string }) { + super(message || "Rate limit exceeded"); + this.name = "RateLimitExceeded"; + } +} + export class NotFoundError extends Error { name: string; diff --git a/backend/src/server/config/rateLimiter.ts b/backend/src/server/config/rateLimiter.ts index bdbf80371..176d44183 100644 --- a/backend/src/server/config/rateLimiter.ts +++ b/backend/src/server/config/rateLimiter.ts @@ -2,6 +2,7 @@ import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-lim import { Redis } from "ioredis"; import { getConfig } from "@app/lib/config/env"; +import { RateLimitError } from "@app/lib/errors"; export const globalRateLimiterCfg = (): RateLimitPluginOptions => { const appCfg = getConfig(); @@ -10,6 +11,11 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => { : null; return { + errorResponseBuilder: (_, context) => { + throw new RateLimitError({ + message: `Rate limit exceeded. Please try again in ${context.after}` + }); + }, timeWindow: 60 * 1000, max: 600, redis, diff --git a/backend/src/server/plugins/error-handler.ts b/backend/src/server/plugins/error-handler.ts index 76bfa9023..91837dc2a 100644 --- a/backend/src/server/plugins/error-handler.ts +++ b/backend/src/server/plugins/error-handler.ts @@ -10,6 +10,7 @@ import { GatewayTimeoutError, InternalServerError, NotFoundError, + RateLimitError, ScimRequestError, UnauthorizedError } from "@app/lib/errors"; @@ -27,7 +28,8 @@ enum HttpStatusCodes { Forbidden = 403, // eslint-disable-next-line @typescript-eslint/no-shadow InternalServerError = 500, - GatewayTimeout = 504 + GatewayTimeout = 504, + RateLimitExceeded = 429 } export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => { @@ -69,6 +71,12 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider message: error.message, error: error.name }); + } else if (error instanceof RateLimitError) { + void res.status(HttpStatusCodes.RateLimitExceeded).send({ + statusCode: HttpStatusCodes.RateLimitExceeded, + message: error.message, + error: error.name + }); } else if (error instanceof ScimRequestError) { void res.status(error.status).send({ schemas: error.schemas, From 5a8ac850b5ef307c62f83a2a0a86ac959c611561 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sat, 19 Oct 2024 06:41:29 +0400 Subject: [PATCH 2/2] fix: variable naming --- backend/src/server/plugins/error-handler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/server/plugins/error-handler.ts b/backend/src/server/plugins/error-handler.ts index 91837dc2a..be8665a84 100644 --- a/backend/src/server/plugins/error-handler.ts +++ b/backend/src/server/plugins/error-handler.ts @@ -29,7 +29,7 @@ enum HttpStatusCodes { // eslint-disable-next-line @typescript-eslint/no-shadow InternalServerError = 500, GatewayTimeout = 504, - RateLimitExceeded = 429 + TooManyRequests = 429 } export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => { @@ -72,8 +72,8 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider error: error.name }); } else if (error instanceof RateLimitError) { - void res.status(HttpStatusCodes.RateLimitExceeded).send({ - statusCode: HttpStatusCodes.RateLimitExceeded, + void res.status(HttpStatusCodes.TooManyRequests).send({ + statusCode: HttpStatusCodes.TooManyRequests, message: error.message, error: error.name });