diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 81fc0c541..126cc0bad 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -14,6 +14,7 @@ import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-con import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service"; +import { TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service"; import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service"; import { TScimServiceFactory } from "@app/ee/services/scim/scim-service"; import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; @@ -48,7 +49,6 @@ import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service"; import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service"; -import { TRateLimitServiceFactory } from "@app/services/rate-limit/rate-limit-service"; import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index 16e23eb88..e7c8f2dfe 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -10,6 +10,7 @@ import { registerLicenseRouter } from "./license-router"; import { registerOrgRoleRouter } from "./org-role-router"; import { registerProjectRoleRouter } from "./project-role-router"; import { registerProjectRouter } from "./project-router"; +import { registerRateLimitRouter } from "./rate-limit-router"; import { registerSamlRouter } from "./saml-router"; import { registerScimRouter } from "./scim-router"; import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router"; @@ -45,6 +46,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => { await server.register(registerAccessApprovalPolicyRouter, { prefix: "/access-approvals/policies" }); await server.register(registerAccessApprovalRequestRouter, { prefix: "/access-approvals/requests" }); + await server.register(registerRateLimitRouter, { prefix: "/rate-limit" }); await server.register( async (dynamicSecretRouter) => { diff --git a/backend/src/server/routes/v1/rate-limit-router.ts b/backend/src/ee/routes/v1/rate-limit-router.ts similarity index 100% rename from backend/src/server/routes/v1/rate-limit-router.ts rename to backend/src/ee/routes/v1/rate-limit-router.ts diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index 47b46d010..46931468f 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -575,6 +575,9 @@ export const licenseServiceFactory = ({ getInstanceType() { return instanceType; }, + get onPremFeatures() { + return onPremFeatures; + }, getPlan, updateSubscriptionOrgMemberCount, refreshPlan, diff --git a/backend/src/services/rate-limit/rate-limit-dal.ts b/backend/src/ee/services/rate-limit/rate-limit-dal.ts similarity index 100% rename from backend/src/services/rate-limit/rate-limit-dal.ts rename to backend/src/ee/services/rate-limit/rate-limit-dal.ts diff --git a/backend/src/services/rate-limit/rate-limit-service.ts b/backend/src/ee/services/rate-limit/rate-limit-service.ts similarity index 82% rename from backend/src/services/rate-limit/rate-limit-service.ts rename to backend/src/ee/services/rate-limit/rate-limit-service.ts index 742628061..df90ca03f 100644 --- a/backend/src/services/rate-limit/rate-limit-service.ts +++ b/backend/src/ee/services/rate-limit/rate-limit-service.ts @@ -2,6 +2,7 @@ import { CronJob } from "cron"; import { logger } from "@app/lib/logger"; +import { TLicenseServiceFactory } from "../license/license-service"; import { TRateLimitDALFactory } from "./rate-limit-dal"; import { TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types"; @@ -24,11 +25,12 @@ export const getRateLimiterConfig = () => { type TRateLimitServiceFactoryDep = { rateLimitDAL: TRateLimitDALFactory; + licenseService: Pick; }; export type TRateLimitServiceFactory = ReturnType; -export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFactoryDep) => { +export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateLimitServiceFactoryDep) => { const DEFAULT_RATE_LIMIT_CONFIG_ID = "00000000-0000-0000-0000-000000000000"; const getRateLimits = async (): Promise => { @@ -78,7 +80,16 @@ export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFacto } }; - const initializeBackgroundSync = () => { + const initializeBackgroundSync = async () => { + if (!licenseService.onPremFeatures.customRateLimits) { + logger.info("Current license does not support custom rate limit configuration"); + return; + } + + logger.info("Setting up background sync process for rate limits"); + // initial sync upon startup + await syncRateLimitConfiguration(); + // sync rate limits configuration every 10 minutes const job = new CronJob("*/10 * * * *", syncRateLimitConfiguration); job.start(); diff --git a/backend/src/services/rate-limit/rate-limit-types.ts b/backend/src/ee/services/rate-limit/rate-limit-types.ts similarity index 100% rename from backend/src/services/rate-limit/rate-limit-types.ts rename to backend/src/ee/services/rate-limit/rate-limit-types.ts diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 19f5986fb..863162c2b 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -17,8 +17,6 @@ import { Logger } from "pino"; import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; -import { rateLimitDALFactory } from "@app/services/rate-limit/rate-limit-dal"; -import { rateLimitServiceFactory } from "@app/services/rate-limit/rate-limit-service"; import { TSmtpService } from "@app/services/smtp/smtp-service"; import { globalRateLimiterCfg } from "./config/rateLimiter"; @@ -71,9 +69,6 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => { // Rate limiters and security headers if (appCfg.isProductionMode) { - const rateLimitDAL = rateLimitDALFactory(db); - const rateLimitService = rateLimitServiceFactory({ rateLimitDAL }); - await rateLimitService.syncRateLimitConfiguration(); await server.register(ratelimiter, globalRateLimiterCfg()); } diff --git a/backend/src/server/config/rateLimiter.ts b/backend/src/server/config/rateLimiter.ts index 819aa617f..ef0ad891f 100644 --- a/backend/src/server/config/rateLimiter.ts +++ b/backend/src/server/config/rateLimiter.ts @@ -1,8 +1,8 @@ import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit"; import { Redis } from "ioredis"; +import { getRateLimiterConfig } from "@app/ee/services/rate-limit/rate-limit-service"; import { getConfig } from "@app/lib/config/env"; -import { getRateLimiterConfig } from "@app/services/rate-limit/rate-limit-service"; export const globalRateLimiterCfg = (): RateLimitPluginOptions => { const appCfg = getConfig(); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 2f2de829f..395f6fbf4 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -34,6 +34,8 @@ import { permissionDALFactory } from "@app/ee/services/permission/permission-dal import { permissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { projectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal"; import { projectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service"; +import { rateLimitDALFactory } from "@app/ee/services/rate-limit/rate-limit-dal"; +import { rateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service"; import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal"; import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service"; import { scimDALFactory } from "@app/ee/services/scim/scim-dal"; @@ -122,8 +124,6 @@ import { projectMembershipServiceFactory } from "@app/services/project-membershi import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal"; import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service"; -import { rateLimitDALFactory } from "@app/services/rate-limit/rate-limit-dal"; -import { rateLimitServiceFactory } from "@app/services/rate-limit/rate-limit-service"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretQueueFactory } from "@app/services/secret/secret-queue"; @@ -449,7 +449,8 @@ export const registerRoutes = async ( keyStore }); const rateLimitService = rateLimitServiceFactory({ - rateLimitDAL + rateLimitDAL, + licenseService }); const apiKeyService = apiKeyServiceFactory({ apiKeyDAL, userDAL }); @@ -910,7 +911,10 @@ export const registerRoutes = async ( const cronJobs: CronJob[] = []; if (appCfg.isProductionMode) { - cronJobs.push(rateLimitService.initializeBackgroundSync()); + const rateLimitSyncJob = await rateLimitService.initializeBackgroundSync(); + if (rateLimitSyncJob) { + cronJobs.push(rateLimitSyncJob); + } } server.decorate("store", { diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index fd8255e63..cbf67ce79 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -17,7 +17,6 @@ import { registerProjectEnvRouter } from "./project-env-router"; import { registerProjectKeyRouter } from "./project-key-router"; import { registerProjectMembershipRouter } from "./project-membership-router"; import { registerProjectRouter } from "./project-router"; -import { registerRateLimitRouter } from "./rate-limit-router"; import { registerSecretFolderRouter } from "./secret-folder-router"; import { registerSecretImportRouter } from "./secret-import-router"; import { registerSecretSharingRouter } from "./secret-sharing-router"; @@ -44,7 +43,6 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerPasswordRouter, { prefix: "/password" }); await server.register(registerOrgRouter, { prefix: "/organization" }); await server.register(registerAdminRouter, { prefix: "/admin" }); - await server.register(registerRateLimitRouter, { prefix: "/rate-limit" }); await server.register(registerUserRouter, { prefix: "/user" }); await server.register(registerInviteOrgRouter, { prefix: "/invite-org" }); await server.register(registerUserActionRouter, { prefix: "/user-action" }); diff --git a/frontend/src/views/admin/DashboardPage/RateLimitPanel.tsx b/frontend/src/views/admin/DashboardPage/RateLimitPanel.tsx index eacb26203..3979e002b 100644 --- a/frontend/src/views/admin/DashboardPage/RateLimitPanel.tsx +++ b/frontend/src/views/admin/DashboardPage/RateLimitPanel.tsx @@ -3,7 +3,9 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { Button, ContentLoader, FormControl, Input } from "@app/components/v2"; +import { Button, ContentLoader, FormControl, Input, UpgradePlanModal } from "@app/components/v2"; +import { useSubscription } from "@app/context"; +import { usePopUp } from "@app/hooks"; import { useGetRateLimit, useUpdateRateLimit } from "@app/hooks/api"; const formSchema = z.object({ @@ -21,7 +23,9 @@ type TRateLimitForm = z.infer; export const RateLimitPanel = () => { const { data: rateLimit, isLoading } = useGetRateLimit(); + const { subscription } = useSubscription(); const { mutateAsync: updateRateLimit } = useUpdateRateLimit(); + const { handlePopUpToggle, handlePopUpOpen, popUp } = usePopUp(["upgradePlan"] as const); const { control, @@ -44,6 +48,11 @@ export const RateLimitPanel = () => { const onRateLimitFormSubmit = async (formData: TRateLimitForm) => { try { + if (subscription && !subscription.customRateLimits) { + handlePopUpOpen("upgradePlan"); + return; + } + const { readRateLimit, writeRateLimit, @@ -86,9 +95,7 @@ export const RateLimitPanel = () => { onSubmit={handleSubmit(onRateLimitFormSubmit)} >
-
- Configure rate limits -
+
Configure rate limits
{ + handlePopUpToggle("upgradePlan", isOpen)} + text="You can configure custom rate limits if you switch to Infisical's Enterprise plan." + /> ); };