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 dfa1fd364..48e82c9f6 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"; @@ -875,11 +874,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); diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index e434bd91f..677ba78fa 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -7,6 +7,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; +import { logger } from "@app/lib/logger"; import { ActorType } from "../auth/auth-type"; import { TProjectDALFactory } from "../project/project-dal"; @@ -166,11 +167,15 @@ export const serviceTokenServiceFactory = ({ const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceToken.secretHash); if (!isMatch) throw new UnauthorizedError(); - const updatedToken = await serviceTokenDAL.updateById(serviceToken.id, { - lastUsed: new Date() - }); + // const updatedToken = await serviceTokenDAL.updateById(serviceToken.id, { + // lastUsed: new Date() + // }); - return { ...serviceToken, lastUsed: updatedToken.lastUsed, orgId: project.orgId }; + logger.info( + `fnValidateServiceToken: [serviceToken=${serviceToken.id}] [serviceTokenProjectId=${serviceToken.projectId}]` + ); + + return { ...serviceToken, lastUsed: serviceToken.lastUsed, orgId: project.orgId }; }; return {