diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 00f26f6be..51cef185a 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -8,6 +8,8 @@ import cors from "@fastify/cors"; import fastifyEtag from "@fastify/etag"; import fastifyFormBody from "@fastify/formbody"; import helmet from "@fastify/helmet"; +import type { FastifyRateLimitOptions } from "@fastify/rate-limit"; +import ratelimiter from "@fastify/rate-limit"; import fasitfy from "fastify"; import { Knex } from "knex"; import { Logger } from "pino"; @@ -17,6 +19,7 @@ import { getConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; import { TSmtpService } from "@app/services/smtp/smtp-service"; +import { globalRateLimiterCfg } from "./config/rateLimiter"; import { fastifyErrHandler } from "./plugins/error-handler"; import { registerExternalNextjs } from "./plugins/external-nextjs"; import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod"; @@ -64,6 +67,10 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => { await server.register(fastifyFormBody); await server.register(fastifyErrHandler); + // Rate limiters and security headers + if (appCfg.isProductionMode) { + await server.register(ratelimiter, globalRateLimiterCfg()); + } await server.register(helmet, { contentSecurityPolicy: false }); await server.register(maintenanceMode); diff --git a/backend/src/server/config/rateLimiter.ts b/backend/src/server/config/rateLimiter.ts index 20a631395..6c92de62c 100644 --- a/backend/src/server/config/rateLimiter.ts +++ b/backend/src/server/config/rateLimiter.ts @@ -1,34 +1,20 @@ import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit"; -import { FastifyRequest } from "fastify"; import { Redis } from "ioredis"; import { getConfig } from "@app/lib/config/env"; -import { ActorType } from "@app/services/auth/auth-type"; - -const getDistinctRequestActorId = (req: FastifyRequest) => { - if (req?.auth?.actor === ActorType.USER) { - return req.auth.user.username; - } - if (req?.auth?.actor === ActorType.IDENTITY) { - return `${req.auth.identityId}-machine-identity-`; - } - if (req?.auth?.actor === ActorType.SERVICE) { - return `${req.auth.serviceToken.id}-service-token`; // when user gets removed from system - } - return req.realIp; -}; export const globalRateLimiterCfg = (): RateLimitPluginOptions => { const appCfg = getConfig(); const redis = appCfg.isRedisConfigured ? new Redis(appCfg.REDIS_URL, { connectTimeout: 500, maxRetriesPerRequest: 1 }) : null; + return { timeWindow: 60 * 1000, max: 600, redis, allowList: (req) => req.url === "/healthcheck" || req.url === "/api/status", - keyGenerator: (req) => getDistinctRequestActorId(req) + keyGenerator: (req) => req.realIp }; }; @@ -36,39 +22,39 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => { export const readLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 600, - keyGenerator: (req) => getDistinctRequestActorId(req) + keyGenerator: (req) => req.realIp }; // POST, PATCH, PUT, DELETE endpoints export const writeLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 50, - keyGenerator: (req) => getDistinctRequestActorId(req) + keyGenerator: (req) => req.realIp }; // special endpoints export const secretsLimit: RateLimitOptions = { // secrets, folders, secret imports timeWindow: 60 * 1000, - max: 1000, - keyGenerator: (req) => getDistinctRequestActorId(req) + max: 60, + keyGenerator: (req) => req.realIp }; export const authRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 60, - keyGenerator: (req) => getDistinctRequestActorId(req) + keyGenerator: (req) => req.realIp }; export const inviteUserRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 30, - keyGenerator: (req) => getDistinctRequestActorId(req) + keyGenerator: (req) => req.realIp }; export const creationLimit: RateLimitOptions = { // identity, project, org timeWindow: 60 * 1000, max: 30, - keyGenerator: (req) => getDistinctRequestActorId(req) + keyGenerator: (req) => req.realIp }; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 1e5cb3bf9..75c43a9aa 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1,4 +1,3 @@ -import ratelimiter, { FastifyRateLimitOptions } from "@fastify/rate-limit"; import { Knex } from "knex"; import { z } from "zod"; @@ -62,7 +61,7 @@ import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip- import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; -import { globalRateLimiterCfg, readLimit } from "@app/server/config/rateLimiter"; +import { readLimit } from "@app/server/config/rateLimiter"; import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal"; import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { authDALFactory } from "@app/services/auth/auth-dal"; @@ -840,11 +839,6 @@ export const registerRoutes = async ( user: userDAL }); - // Rate limiters and security headers - if (appCfg.isProductionMode) { - await server.register(ratelimiter, globalRateLimiterCfg()); - } - await server.register(injectIdentity, { userDAL, serviceTokenDAL }); await server.register(injectPermission); await server.register(injectAuditLogInfo);