From bc3f21809e8aa4ff8c06b496c0efe4e30ec5b018 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 14 Jun 2024 02:32:21 +0800 Subject: [PATCH] misc: migrated to structured singleton pattern --- backend/src/server/app.ts | 4 +- backend/src/server/config/rateLimiter.ts | 42 +++--------- .../services/rate-limit/rate-limit-service.ts | 65 +++++++++++++------ 3 files changed, 56 insertions(+), 55 deletions(-) diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index da1bca3e2e..19f5986fbe 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -73,8 +73,8 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => { if (appCfg.isProductionMode) { const rateLimitDAL = rateLimitDALFactory(db); const rateLimitService = rateLimitServiceFactory({ rateLimitDAL }); - const rateLimits = await rateLimitService.getRateLimits(); - await server.register(ratelimiter, globalRateLimiterCfg(rateLimits)); + await rateLimitService.syncRateLimitConfiguration(); + await server.register(ratelimiter, globalRateLimiterCfg()); } await server.register(helmet, { contentSecurityPolicy: false }); diff --git a/backend/src/server/config/rateLimiter.ts b/backend/src/server/config/rateLimiter.ts index 8e0e17bfe0..0e406da886 100644 --- a/backend/src/server/config/rateLimiter.ts +++ b/backend/src/server/config/rateLimiter.ts @@ -1,31 +1,20 @@ import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit"; import { Redis } from "ioredis"; -import { TRateLimit } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; - -export const rateLimitMaxConfiguration = { - readLimit: 60, - publicEndpointLimit: 30, - writeLimit: 200, - secretsLimit: 60, - authRateLimit: 60, - inviteUserRateLimit: 30, - mfaRateLimit: 20, - creationLimit: 30 -}; +import { getRateLimiterConfig } from "@app/services/rate-limit/rate-limit-service"; // GET endpoints export const readLimit: RateLimitOptions = { timeWindow: 60 * 1000, - max: () => rateLimitMaxConfiguration.readLimit, + max: () => getRateLimiterConfig().readLimit, keyGenerator: (req) => req.realIp }; // POST, PATCH, PUT, DELETE endpoints export const writeLimit: RateLimitOptions = { timeWindow: 60 * 1000, - max: () => rateLimitMaxConfiguration.writeLimit, // (too low, FA having issues so increasing it - maidul) + max: () => getRateLimiterConfig().writeLimit, keyGenerator: (req) => req.realIp }; @@ -33,25 +22,25 @@ export const writeLimit: RateLimitOptions = { export const secretsLimit: RateLimitOptions = { // secrets, folders, secret imports timeWindow: 60 * 1000, - max: () => rateLimitMaxConfiguration.secretsLimit, + max: () => getRateLimiterConfig().secretsLimit, keyGenerator: (req) => req.realIp }; export const authRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, - max: () => rateLimitMaxConfiguration.authRateLimit, + max: () => getRateLimiterConfig().authRateLimit, keyGenerator: (req) => req.realIp }; export const inviteUserRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, - max: () => rateLimitMaxConfiguration.inviteUserRateLimit, + max: () => getRateLimiterConfig().inviteUserRateLimit, keyGenerator: (req) => req.realIp }; export const mfaRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, - max: () => rateLimitMaxConfiguration.mfaRateLimit, + max: () => getRateLimiterConfig().mfaRateLimit, keyGenerator: (req) => { return req.headers.authorization?.split(" ")[1] || req.realIp; } @@ -60,7 +49,7 @@ export const mfaRateLimit: RateLimitOptions = { export const creationLimit: RateLimitOptions = { // identity, project, org timeWindow: 60 * 1000, - max: () => rateLimitMaxConfiguration.creationLimit, + max: () => getRateLimiterConfig().creationLimit, keyGenerator: (req) => req.realIp }; @@ -68,27 +57,16 @@ export const creationLimit: RateLimitOptions = { export const publicEndpointLimit: RateLimitOptions = { // Shared Secrets timeWindow: 60 * 1000, - max: () => rateLimitMaxConfiguration.publicEndpointLimit, + max: () => getRateLimiterConfig().publicEndpointLimit, keyGenerator: (req) => req.realIp }; -export const globalRateLimiterCfg = async (customRateLimits?: TRateLimit): Promise => { +export const globalRateLimiterCfg = (): RateLimitPluginOptions => { const appCfg = getConfig(); const redis = appCfg.isRedisConfigured ? new Redis(appCfg.REDIS_URL, { connectTimeout: 500, maxRetriesPerRequest: 1 }) : null; - if (customRateLimits) { - rateLimitMaxConfiguration.readLimit = customRateLimits.readRateLimit; - rateLimitMaxConfiguration.publicEndpointLimit = customRateLimits.publicEndpointLimit; - rateLimitMaxConfiguration.writeLimit = customRateLimits.writeRateLimit; - rateLimitMaxConfiguration.secretsLimit = customRateLimits.secretsRateLimit; - rateLimitMaxConfiguration.authRateLimit = customRateLimits.authRateLimit; - rateLimitMaxConfiguration.inviteUserRateLimit = customRateLimits.inviteUserRateLimit; - rateLimitMaxConfiguration.mfaRateLimit = customRateLimits.mfaRateLimit; - rateLimitMaxConfiguration.creationLimit = customRateLimits.creationLimit; - } - return { timeWindow: 60 * 1000, max: 600, diff --git a/backend/src/services/rate-limit/rate-limit-service.ts b/backend/src/services/rate-limit/rate-limit-service.ts index e458a93100..a76102ac20 100644 --- a/backend/src/services/rate-limit/rate-limit-service.ts +++ b/backend/src/services/rate-limit/rate-limit-service.ts @@ -1,11 +1,27 @@ import { CronJob } from "cron"; import { logger } from "@app/lib/logger"; -import { rateLimitMaxConfiguration } from "@app/server/config/rateLimiter"; import { TRateLimitDALFactory } from "./rate-limit-dal"; import { TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types"; +let rateLimitMaxConfiguration = { + readLimit: 60, + publicEndpointLimit: 30, + writeLimit: 200, + secretsLimit: 60, + authRateLimit: 60, + inviteUserRateLimit: 30, + mfaRateLimit: 20, + creationLimit: 30 +}; + +Object.freeze(rateLimitMaxConfiguration); + +export const getRateLimiterConfig = () => { + return rateLimitMaxConfiguration; +}; + type TRateLimitServiceFactoryDep = { rateLimitDAL: TRateLimitDALFactory; }; @@ -37,27 +53,33 @@ export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFacto return rateLimitDAL.updateById(DEFAULT_RATE_LIMIT_CONFIG_ID, updates); }; - const initializeBackgroundSync = () => { - const rateLimitSync = async () => { - try { - const rateLimit = await getRateLimits(); - if (rateLimit) { - rateLimitMaxConfiguration.readLimit = rateLimit.readRateLimit; - rateLimitMaxConfiguration.publicEndpointLimit = rateLimit.publicEndpointLimit; - rateLimitMaxConfiguration.writeLimit = rateLimit.writeRateLimit; - rateLimitMaxConfiguration.secretsLimit = rateLimit.secretsRateLimit; - rateLimitMaxConfiguration.authRateLimit = rateLimit.authRateLimit; - rateLimitMaxConfiguration.inviteUserRateLimit = rateLimit.inviteUserRateLimit; - rateLimitMaxConfiguration.mfaRateLimit = rateLimit.mfaRateLimit; - rateLimitMaxConfiguration.creationLimit = rateLimit.creationLimit; - } - } catch (error) { - logger.error(`Error syncing rate limit configurations: %o`, error); - } - }; + const syncRateLimitConfiguration = async () => { + try { + const rateLimit = await getRateLimits(); + if (rateLimit) { + const newRateLimitMaxConfiguration: typeof rateLimitMaxConfiguration = { + readLimit: rateLimit.readRateLimit, + publicEndpointLimit: rateLimit.publicEndpointLimit, + writeLimit: rateLimit.writeRateLimit, + secretsLimit: rateLimit.secretsRateLimit, + authRateLimit: rateLimit.authRateLimit, + inviteUserRateLimit: rateLimit.inviteUserRateLimit, + mfaRateLimit: rateLimit.mfaRateLimit, + creationLimit: rateLimit.creationLimit + }; + logger.info(`Rate limit configuration: %o`, newRateLimitMaxConfiguration); + Object.freeze(newRateLimitMaxConfiguration); + rateLimitMaxConfiguration = newRateLimitMaxConfiguration; + } + } catch (error) { + logger.error(`Error syncing rate limit configurations: %o`, error); + } + }; + + const initializeBackgroundSync = () => { // sync rate limits configuration every 10 minutes - const job = new CronJob("*/10 * * * *", rateLimitSync); + const job = new CronJob("*/10 * * * *", syncRateLimitConfiguration); job.start(); return job; @@ -66,6 +88,7 @@ export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFacto return { getRateLimits, updateRateLimit, - initializeBackgroundSync + initializeBackgroundSync, + syncRateLimitConfiguration }; };