misc: migrated to structured singleton pattern

This commit is contained in:
Sheen Capadngan
2024-06-14 02:32:21 +08:00
parent 46b48cea63
commit bc3f21809e
3 changed files with 56 additions and 55 deletions
+2 -2
View File
@@ -73,8 +73,8 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
if (appCfg.isProductionMode) { if (appCfg.isProductionMode) {
const rateLimitDAL = rateLimitDALFactory(db); const rateLimitDAL = rateLimitDALFactory(db);
const rateLimitService = rateLimitServiceFactory({ rateLimitDAL }); const rateLimitService = rateLimitServiceFactory({ rateLimitDAL });
const rateLimits = await rateLimitService.getRateLimits(); await rateLimitService.syncRateLimitConfiguration();
await server.register<FastifyRateLimitOptions>(ratelimiter, globalRateLimiterCfg(rateLimits)); await server.register<FastifyRateLimitOptions>(ratelimiter, globalRateLimiterCfg());
} }
await server.register(helmet, { contentSecurityPolicy: false }); await server.register(helmet, { contentSecurityPolicy: false });
+10 -32
View File
@@ -1,31 +1,20 @@
import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit"; import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit";
import { Redis } from "ioredis"; import { Redis } from "ioredis";
import { TRateLimit } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { getRateLimiterConfig } from "@app/services/rate-limit/rate-limit-service";
export const rateLimitMaxConfiguration = {
readLimit: 60,
publicEndpointLimit: 30,
writeLimit: 200,
secretsLimit: 60,
authRateLimit: 60,
inviteUserRateLimit: 30,
mfaRateLimit: 20,
creationLimit: 30
};
// GET endpoints // GET endpoints
export const readLimit: RateLimitOptions = { export const readLimit: RateLimitOptions = {
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: () => rateLimitMaxConfiguration.readLimit, max: () => getRateLimiterConfig().readLimit,
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };
// POST, PATCH, PUT, DELETE endpoints // POST, PATCH, PUT, DELETE endpoints
export const writeLimit: RateLimitOptions = { export const writeLimit: RateLimitOptions = {
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: () => rateLimitMaxConfiguration.writeLimit, // (too low, FA having issues so increasing it - maidul) max: () => getRateLimiterConfig().writeLimit,
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };
@@ -33,25 +22,25 @@ export const writeLimit: RateLimitOptions = {
export const secretsLimit: RateLimitOptions = { export const secretsLimit: RateLimitOptions = {
// secrets, folders, secret imports // secrets, folders, secret imports
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: () => rateLimitMaxConfiguration.secretsLimit, max: () => getRateLimiterConfig().secretsLimit,
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };
export const authRateLimit: RateLimitOptions = { export const authRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: () => rateLimitMaxConfiguration.authRateLimit, max: () => getRateLimiterConfig().authRateLimit,
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };
export const inviteUserRateLimit: RateLimitOptions = { export const inviteUserRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: () => rateLimitMaxConfiguration.inviteUserRateLimit, max: () => getRateLimiterConfig().inviteUserRateLimit,
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };
export const mfaRateLimit: RateLimitOptions = { export const mfaRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: () => rateLimitMaxConfiguration.mfaRateLimit, max: () => getRateLimiterConfig().mfaRateLimit,
keyGenerator: (req) => { keyGenerator: (req) => {
return req.headers.authorization?.split(" ")[1] || req.realIp; return req.headers.authorization?.split(" ")[1] || req.realIp;
} }
@@ -60,7 +49,7 @@ export const mfaRateLimit: RateLimitOptions = {
export const creationLimit: RateLimitOptions = { export const creationLimit: RateLimitOptions = {
// identity, project, org // identity, project, org
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: () => rateLimitMaxConfiguration.creationLimit, max: () => getRateLimiterConfig().creationLimit,
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };
@@ -68,27 +57,16 @@ export const creationLimit: RateLimitOptions = {
export const publicEndpointLimit: RateLimitOptions = { export const publicEndpointLimit: RateLimitOptions = {
// Shared Secrets // Shared Secrets
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: () => rateLimitMaxConfiguration.publicEndpointLimit, max: () => getRateLimiterConfig().publicEndpointLimit,
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };
export const globalRateLimiterCfg = async (customRateLimits?: TRateLimit): Promise<RateLimitPluginOptions> => { export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
const appCfg = getConfig(); const appCfg = getConfig();
const redis = appCfg.isRedisConfigured const redis = appCfg.isRedisConfigured
? new Redis(appCfg.REDIS_URL, { connectTimeout: 500, maxRetriesPerRequest: 1 }) ? new Redis(appCfg.REDIS_URL, { connectTimeout: 500, maxRetriesPerRequest: 1 })
: null; : 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 { return {
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: 600, max: 600,
@@ -1,11 +1,27 @@
import { CronJob } from "cron"; import { CronJob } from "cron";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { rateLimitMaxConfiguration } from "@app/server/config/rateLimiter";
import { TRateLimitDALFactory } from "./rate-limit-dal"; import { TRateLimitDALFactory } from "./rate-limit-dal";
import { TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types"; 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 = { type TRateLimitServiceFactoryDep = {
rateLimitDAL: TRateLimitDALFactory; rateLimitDAL: TRateLimitDALFactory;
}; };
@@ -37,27 +53,33 @@ export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFacto
return rateLimitDAL.updateById(DEFAULT_RATE_LIMIT_CONFIG_ID, updates); return rateLimitDAL.updateById(DEFAULT_RATE_LIMIT_CONFIG_ID, updates);
}; };
const initializeBackgroundSync = () => { const syncRateLimitConfiguration = async () => {
const rateLimitSync = async () => { try {
try { const rateLimit = await getRateLimits();
const rateLimit = await getRateLimits(); if (rateLimit) {
if (rateLimit) { const newRateLimitMaxConfiguration: typeof rateLimitMaxConfiguration = {
rateLimitMaxConfiguration.readLimit = rateLimit.readRateLimit; readLimit: rateLimit.readRateLimit,
rateLimitMaxConfiguration.publicEndpointLimit = rateLimit.publicEndpointLimit; publicEndpointLimit: rateLimit.publicEndpointLimit,
rateLimitMaxConfiguration.writeLimit = rateLimit.writeRateLimit; writeLimit: rateLimit.writeRateLimit,
rateLimitMaxConfiguration.secretsLimit = rateLimit.secretsRateLimit; secretsLimit: rateLimit.secretsRateLimit,
rateLimitMaxConfiguration.authRateLimit = rateLimit.authRateLimit; authRateLimit: rateLimit.authRateLimit,
rateLimitMaxConfiguration.inviteUserRateLimit = rateLimit.inviteUserRateLimit; inviteUserRateLimit: rateLimit.inviteUserRateLimit,
rateLimitMaxConfiguration.mfaRateLimit = rateLimit.mfaRateLimit; mfaRateLimit: rateLimit.mfaRateLimit,
rateLimitMaxConfiguration.creationLimit = rateLimit.creationLimit; creationLimit: rateLimit.creationLimit
} };
} catch (error) {
logger.error(`Error syncing rate limit configurations: %o`, error);
}
};
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 // sync rate limits configuration every 10 minutes
const job = new CronJob("*/10 * * * *", rateLimitSync); const job = new CronJob("*/10 * * * *", syncRateLimitConfiguration);
job.start(); job.start();
return job; return job;
@@ -66,6 +88,7 @@ export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFacto
return { return {
getRateLimits, getRateLimits,
updateRateLimit, updateRateLimit,
initializeBackgroundSync initializeBackgroundSync,
syncRateLimitConfiguration
}; };
}; };