From e9d8979cf4295511c568324517dd98514ec1f334 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 9 May 2025 11:29:53 -0400 Subject: [PATCH] add rate limit to all email-sending endpoints --- .../src/ee/routes/v1/access-approval-request-router.ts | 4 ++++ backend/src/ee/routes/v1/ldap-router.ts | 3 +++ backend/src/ee/routes/v1/saml-router.ts | 9 +++++++++ backend/src/ee/routes/v1/scim-router.ts | 3 +++ backend/src/ee/services/audit-log/audit-log-types.ts | 2 +- backend/src/server/config/rateLimiter.ts | 10 +++++++++- backend/src/server/routes/v1/org-admin-router.ts | 4 ++-- backend/src/server/routes/v1/project-router.ts | 4 ++-- 8 files changed, 33 insertions(+), 6 deletions(-) diff --git a/backend/src/ee/routes/v1/access-approval-request-router.ts b/backend/src/ee/routes/v1/access-approval-request-router.ts index 8a7ccfdef..b0914d5c4 100644 --- a/backend/src/ee/routes/v1/access-approval-request-router.ts +++ b/backend/src/ee/routes/v1/access-approval-request-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { AccessApprovalRequestsReviewersSchema, AccessApprovalRequestsSchema, UsersSchema } from "@app/db/schemas"; import { ApprovalStatus } from "@app/ee/services/access-approval-request/access-approval-request-types"; +import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -18,6 +19,9 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv server.route({ url: "/", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ permissions: z.any().array(), diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index 5f80ad02b..57c5736df 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -98,6 +98,9 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { server.route({ url: "/login", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ organizationSlug: z.string().trim() diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index f2df2fb89..8648ade7c 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -166,6 +166,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { server.route({ url: "/redirect/saml2/organizations/:orgSlug", method: "GET", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ orgSlug: z.string().trim() @@ -192,6 +195,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { server.route({ url: "/redirect/saml2/:samlConfigId", method: "GET", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ samlConfigId: z.string().trim() @@ -218,6 +224,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { server.route({ url: "/saml2/:samlConfigId", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ samlConfigId: z.string().trim() diff --git a/backend/src/ee/routes/v1/scim-router.ts b/backend/src/ee/routes/v1/scim-router.ts index cd5f2f9f3..5fa0d19e8 100644 --- a/backend/src/ee/routes/v1/scim-router.ts +++ b/backend/src/ee/routes/v1/scim-router.ts @@ -196,6 +196,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { server.route({ url: "/Users", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ schemas: z.array(z.string()), diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index a2caf2bff..126fd2323 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -22,6 +22,7 @@ import { ActorType } from "@app/services/auth/auth-type"; import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types"; import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; +import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types"; import { PkiItemType } from "@app/services/pki-collection/pki-collection-types"; import { SecretSync, SecretSyncImportBehavior } from "@app/services/secret-sync/secret-sync-enums"; import { @@ -34,7 +35,6 @@ import { WorkflowIntegration } from "@app/services/workflow-integration/workflow import { KmipPermission } from "../kmip/kmip-enum"; import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types"; -import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types"; export type TListProjectAuditLogDTO = { filter: { diff --git a/backend/src/server/config/rateLimiter.ts b/backend/src/server/config/rateLimiter.ts index 42bf37c71..b12c9b0d3 100644 --- a/backend/src/server/config/rateLimiter.ts +++ b/backend/src/server/config/rateLimiter.ts @@ -104,6 +104,14 @@ export const publicSshCaLimit: RateLimitOptions = { export const invalidateCacheLimit: RateLimitOptions = { timeWindow: 60 * 1000, hook: "preValidation", - max: 1, + max: 2, + keyGenerator: (req) => req.realIp +}; + +// Makes spamming "request access" harder, preventing email DDoS +export const requestAccessLimit: RateLimitOptions = { + timeWindow: 60 * 1000, + hook: "preValidation", + max: 10, keyGenerator: (req) => req.realIp }; diff --git a/backend/src/server/routes/v1/org-admin-router.ts b/backend/src/server/routes/v1/org-admin-router.ts index 2d28b09bd..cc0543d4c 100644 --- a/backend/src/server/routes/v1/org-admin-router.ts +++ b/backend/src/server/routes/v1/org-admin-router.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { ProjectMembershipsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; -import { readLimit } from "@app/server/config/rateLimiter"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -47,7 +47,7 @@ export const registerOrgAdminRouter = async (server: FastifyZodProvider) => { method: "POST", url: "/projects/:projectId/grant-admin-access", config: { - rateLimit: readLimit + rateLimit: writeLimit }, schema: { params: z.object({ diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index fdc729548..99e681764 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -19,7 +19,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs"; import { CharacterType, characterValidator } from "@app/lib/validator/validate-string"; import { re2Validator } from "@app/lib/zod"; -import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { readLimit, requestAccessLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { ActorType, AuthMode } from "@app/services/auth/auth-type"; import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns"; @@ -1006,7 +1006,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { method: "POST", url: "/:workspaceId/project-access", config: { - rateLimit: writeLimit + rateLimit: requestAccessLimit }, schema: { params: z.object({