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,