diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 51cef185a..00f26f6be 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -8,8 +8,6 @@ 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"; @@ -19,7 +17,6 @@ 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"; @@ -67,10 +64,6 @@ 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 1695ac313..1d9793264 100644 --- a/backend/src/server/config/rateLimiter.ts +++ b/backend/src/server/config/rateLimiter.ts @@ -1,20 +1,36 @@ 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 `identity-${req.auth.identityId}`; + } + if (req.auth.actor === ActorType.SERVICE) { + return ( + `${req.auth.serviceToken.createdByEmail}-service-token` || `service-token-null-creator-${req.auth.serviceTokenId}` + ); // 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) => req.realIp + keyGenerator: (req) => getDistinctRequestActorId(req) }; }; @@ -22,39 +38,39 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => { export const readLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 600, - keyGenerator: (req) => req.realIp + keyGenerator: (req) => getDistinctRequestActorId(req) }; // POST, PATCH, PUT, DELETE endpoints export const writeLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 50, - keyGenerator: (req) => req.realIp + keyGenerator: (req) => getDistinctRequestActorId(req) }; // special endpoints export const secretsLimit: RateLimitOptions = { // secrets, folders, secret imports timeWindow: 60 * 1000, - max: 1000, - keyGenerator: (req) => req.realIp + max: 60, + keyGenerator: (req) => getDistinctRequestActorId(req) }; export const authRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 60, - keyGenerator: (req) => req.realIp + keyGenerator: (req) => getDistinctRequestActorId(req) }; export const inviteUserRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 30, - keyGenerator: (req) => req.realIp + keyGenerator: (req) => getDistinctRequestActorId(req) }; export const creationLimit: RateLimitOptions = { // identity, project, org timeWindow: 60 * 1000, max: 30, - keyGenerator: (req) => req.realIp + keyGenerator: (req) => getDistinctRequestActorId(req) }; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 75c43a9aa..1e5cb3bf9 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1,3 +1,4 @@ +import ratelimiter, { FastifyRateLimitOptions } from "@fastify/rate-limit"; import { Knex } from "knex"; import { z } from "zod"; @@ -61,7 +62,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 { readLimit } from "@app/server/config/rateLimiter"; +import { globalRateLimiterCfg, 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"; @@ -839,6 +840,11 @@ 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);