From d3cdaa8449416aa9e545c6080a94422af2712759 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 4 Apr 2024 18:12:23 -0700 Subject: [PATCH] Add new rate limits --- backend/scripts/create-backend-file.ts | 6 +- .../routes/v1/dynamic-secret-lease-router.ts | 19 ++- .../src/ee/routes/v1/dynamic-secret-router.ts | 25 +++- ...ity-project-additional-privilege-router.ts | 31 ++++- backend/src/ee/routes/v1/ldap-router.ts | 14 +- backend/src/ee/routes/v1/license-router.ts | 84 +++++++++--- backend/src/ee/routes/v1/org-role-router.ts | 16 +++ .../src/ee/routes/v1/project-role-router.ts | 16 +++ backend/src/ee/routes/v1/project-router.ts | 13 ++ backend/src/ee/routes/v1/saml-router.ts | 16 ++- backend/src/ee/routes/v1/scim-router.ts | 10 ++ .../v1/secret-approval-policy-router.ts | 16 +++ .../v1/secret-approval-request-router.ts | 29 ++++- .../v1/secret-rotation-provider-router.ts | 6 +- .../ee/routes/v1/secret-rotation-router.ts | 17 ++- .../ee/routes/v1/secret-scanning-router.ts | 24 +++- .../src/ee/routes/v1/secret-version-router.ts | 6 +- backend/src/ee/routes/v1/snapshot-router.ts | 7 + backend/src/ee/routes/v1/trusted-ip-router.ts | 21 ++- .../v1/user-additional-privilege-router.ts | 29 ++++- backend/src/server/config/rateLimiter.ts | 29 ++++- backend/src/server/plugins/secret-scanner.ts | 4 + backend/src/server/routes/index.ts | 6 +- backend/src/server/routes/v1/admin-router.ts | 16 ++- backend/src/server/routes/v1/auth-router.ts | 12 +- backend/src/server/routes/v1/bot-router.ts | 11 +- .../routes/v1/identity-access-token-router.ts | 4 + .../src/server/routes/v1/identity-router.ts | 10 ++ backend/src/server/routes/v1/identity-ua.ts | 36 +++++- .../routes/v1/integration-auth-router.ts | 121 ++++++++++++++---- .../server/routes/v1/integration-router.ts | 16 ++- .../src/server/routes/v1/invite-org-router.ts | 3 + .../server/routes/v1/organization-router.ts | 22 ++++ .../src/server/routes/v1/password-router.ts | 17 ++- .../server/routes/v1/project-env-router.ts | 16 ++- .../server/routes/v1/project-key-router.ts | 4 + .../routes/v1/project-membership-router.ts | 21 ++- .../src/server/routes/v1/project-router.ts | 54 ++++++-- .../server/routes/v1/secret-folder-router.ts | 17 ++- .../server/routes/v1/secret-import-router.ts | 24 +++- .../src/server/routes/v1/secret-tag-router.ts | 16 ++- .../server/routes/v1/user-action-router.ts | 7 + backend/src/server/routes/v1/user-router.ts | 4 + .../src/server/routes/v1/webhook-router.ts | 16 +++ .../server/routes/v2/identity-org-router.ts | 4 + .../routes/v2/identity-project-router.ts | 13 ++ backend/src/server/routes/v2/mfa-router.ts | 9 +- .../server/routes/v2/organization-router.ts | 19 +++ .../routes/v2/project-membership-router.ts | 7 + .../src/server/routes/v2/project-router.ts | 26 +++- .../server/routes/v2/service-token-router.ts | 16 ++- backend/src/server/routes/v2/user-router.ts | 40 +++++- .../routes/v3/secret-blind-index-router.ts | 16 ++- backend/src/server/routes/v3/secret-router.ts | 74 +++++++++-- backend/src/server/routes/v3/user-router.ts | 4 + 55 files changed, 958 insertions(+), 161 deletions(-) diff --git a/backend/scripts/create-backend-file.ts b/backend/scripts/create-backend-file.ts index b821aba21..fb71994ce 100644 --- a/backend/scripts/create-backend-file.ts +++ b/backend/scripts/create-backend-file.ts @@ -103,11 +103,15 @@ export const ${dalName} = (db: TDbClient) => { `import { z } from "zod"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +import { readLimit } from "@app/server/config/rateLimiter"; export const register${pascalCase}Router = async (server: FastifyZodProvider) => { server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, schema: { params: z.object({}), response: { diff --git a/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts b/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts index c830e2a7f..5ef9f7eeb 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts @@ -5,14 +5,18 @@ import { DynamicSecretLeasesSchema } from "@app/db/schemas"; import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs"; import { daysToMillisecond } from "@app/lib/dates"; import { removeTrailingSlash } from "@app/lib/fn"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", method: "POST", + url: "/", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(), @@ -55,8 +59,11 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide }); server.route({ - url: "/:leaseId", method: "DELETE", + url: "/:leaseId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.leaseId) @@ -94,8 +101,11 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide }); server.route({ - url: "/:leaseId/renew", method: "POST", + url: "/:leaseId/renew", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.leaseId) @@ -146,6 +156,9 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide server.route({ url: "/:leaseId", method: "GET", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.leaseId) diff --git a/backend/src/ee/routes/v1/dynamic-secret-router.ts b/backend/src/ee/routes/v1/dynamic-secret-router.ts index 0cbf8a312..049370743 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-router.ts @@ -7,14 +7,18 @@ import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/pro import { DYNAMIC_SECRETS } from "@app/lib/api-docs"; import { daysToMillisecond } from "@app/lib/dates"; import { removeTrailingSlash } from "@app/lib/fn"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", method: "POST", + url: "/", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.CREATE.projectSlug), @@ -74,8 +78,11 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => }); server.route({ - url: "/:name", method: "PATCH", + url: "/:name", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.UPDATE.name) @@ -138,8 +145,11 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => }); server.route({ - url: "/:name", method: "DELETE", + url: "/:name", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.DELETE.name) @@ -173,6 +183,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => server.route({ url: "/:name", method: "GET", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ name: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.name) @@ -207,6 +220,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => server.route({ url: "/", method: "GET", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.projectSlug), @@ -235,6 +251,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => server.route({ url: "/:name/leases", method: "GET", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ name: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.name) diff --git a/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts b/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts index 1a1c92146..a1a2e36fa 100644 --- a/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts @@ -9,13 +9,17 @@ import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/service import { ProjectPermissionSet } from "@app/ee/services/permission/project-permission"; import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; import { alphaNumericNanoId } from "@app/lib/nanoid"; +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"; export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/permanent", method: "POST", + url: "/permanent", + config: { + rateLimit: writeLimit + }, schema: { description: "Create a permanent or a non expiry specific privilege for identity.", security: [ @@ -62,8 +66,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }); server.route({ - url: "/temporary", method: "POST", + url: "/temporary", + config: { + rateLimit: writeLimit + }, schema: { description: "Create a temporary or a expiring specific privilege for identity.", security: [ @@ -121,8 +128,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }); server.route({ - url: "/", method: "PATCH", + url: "/", + config: { + rateLimit: writeLimit + }, schema: { description: "Update a specific privilege of an identity.", security: [ @@ -190,8 +200,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }); server.route({ - url: "/", method: "DELETE", + url: "/", + config: { + rateLimit: writeLimit + }, schema: { description: "Delete a specific privilege of an identity.", security: [ @@ -226,8 +239,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }); server.route({ - url: "/:privilegeSlug", method: "GET", + url: "/:privilegeSlug", + config: { + rateLimit: readLimit + }, schema: { description: "Retrieve details of a specific privilege by privilege slug.", security: [ @@ -263,8 +279,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }); server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, schema: { description: "List of a specific privilege of an identity in a project.", security: [ diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index d160f97c6..c35d275ae 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -17,6 +17,7 @@ import { z } from "zod"; import { LdapConfigsSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; +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"; @@ -97,8 +98,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/config", method: "GET", + url: "/config", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { querystring: z.object({ @@ -130,8 +134,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/config", method: "POST", + url: "/config", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z.object({ @@ -164,6 +171,9 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { server.route({ url: "/config", method: "PATCH", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index bd3fdca18..fbf1af43b 100644 --- a/backend/src/ee/routes/v1/license-router.ts +++ b/backend/src/ee/routes/v1/license-router.ts @@ -3,13 +3,17 @@ // TODO(akhilmhdh): Fix this when licence service gets it type import { z } from "zod"; +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"; export const registerLicenseRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:organizationId/plans/table", method: "GET", + url: "/:organizationId/plans/table", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }), params: z.object({ organizationId: z.string().trim() }), @@ -32,8 +36,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/plan", method: "GET", + url: "/:organizationId/plan", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -54,8 +61,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/plans", method: "GET", + url: "/:organizationId/plans", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), querystring: z.object({ workspaceId: z.string().trim().optional() }), @@ -77,8 +87,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/session/trial", method: "POST", + url: "/:organizationId/session/trial", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), body: z.object({ success_url: z.string().trim() }), @@ -103,6 +116,9 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { server.route({ url: "/:organizationId/customer-portal-session", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -123,8 +139,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/plan/billing", method: "GET", + url: "/:organizationId/plan/billing", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -145,8 +164,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/plan/table", method: "GET", + url: "/:organizationId/plan/table", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -167,8 +189,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/billing-details", method: "GET", + url: "/:organizationId/billing-details", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -189,8 +214,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/billing-details", method: "PATCH", + url: "/:organizationId/billing-details", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), body: z.object({ @@ -217,8 +245,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/billing-details/payment-methods", method: "GET", + url: "/:organizationId/billing-details/payment-methods", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -239,8 +270,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/billing-details/payment-methods", method: "POST", + url: "/:organizationId/billing-details/payment-methods", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), body: z.object({ @@ -267,8 +301,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/billing-details/payment-methods/:pmtMethodId", method: "DELETE", + url: "/:organizationId/billing-details/payment-methods/:pmtMethodId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim(), @@ -293,8 +330,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/billing-details/tax-ids", method: "GET", + url: "/:organizationId/billing-details/tax-ids", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() @@ -317,8 +357,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/billing-details/tax-ids", method: "POST", + url: "/:organizationId/billing-details/tax-ids", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() @@ -347,8 +390,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/billing-details/tax-ids/:taxId", method: "DELETE", + url: "/:organizationId/billing-details/tax-ids/:taxId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim(), @@ -373,8 +419,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/invoices", method: "GET", + url: "/:organizationId/invoices", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() @@ -397,8 +446,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:organizationId/licenses", method: "GET", + url: "/:organizationId/licenses", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() diff --git a/backend/src/ee/routes/v1/org-role-router.ts b/backend/src/ee/routes/v1/org-role-router.ts index 3ecf6fbfd..380f61e23 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -2,6 +2,7 @@ import slugify from "@sindresorhus/slugify"; import { z } from "zod"; import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas"; +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"; @@ -9,6 +10,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/:organizationId/roles", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() @@ -51,6 +55,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", url: "/:organizationId/roles/:roleId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim(), @@ -95,6 +102,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:organizationId/roles/:roleId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim(), @@ -122,6 +132,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:organizationId/roles", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() @@ -151,6 +164,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:organizationId/permissions", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() diff --git a/backend/src/ee/routes/v1/project-role-router.ts b/backend/src/ee/routes/v1/project-role-router.ts index a08a9851b..c1470e7ba 100644 --- a/backend/src/ee/routes/v1/project-role-router.ts +++ b/backend/src/ee/routes/v1/project-role-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas"; +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"; @@ -8,6 +9,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/:projectId/roles", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().trim() @@ -41,6 +45,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", url: "/:projectId/roles/:roleId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().trim(), @@ -76,6 +83,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:projectId/roles/:roleId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().trim(), @@ -104,6 +114,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:projectId/roles", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ projectId: z.string().trim() @@ -134,6 +147,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:projectId/permissions", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ projectId: z.string().trim() diff --git a/backend/src/ee/routes/v1/project-router.ts b/backend/src/ee/routes/v1/project-router.ts index 3fbe3f026..bb81fd8ee 100644 --- a/backend/src/ee/routes/v1/project-router.ts +++ b/backend/src/ee/routes/v1/project-router.ts @@ -4,6 +4,7 @@ import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas"; import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs"; import { removeTrailingSlash } from "@app/lib/fn"; +import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -11,6 +12,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:workspaceId/secret-snapshots", + config: { + rateLimit: readLimit + }, schema: { description: "Return project secret snapshots ids", security: [ @@ -51,6 +55,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:workspaceId/secret-snapshots/count", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -83,6 +90,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:workspaceId/audit-logs", + config: { + rateLimit: readLimit + }, schema: { description: "Return audit logs", security: [ @@ -145,6 +155,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:workspaceId/audit-logs/filters/actors", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index b30afda13..6cae30f7a 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -17,6 +17,7 @@ import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; +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"; @@ -203,8 +204,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/config", method: "GET", + url: "/config", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { querystring: z.object({ @@ -240,8 +244,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/config", method: "POST", + url: "/config", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z.object({ @@ -270,8 +277,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/config", method: "PATCH", + url: "/config", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z diff --git a/backend/src/ee/routes/v1/scim-router.ts b/backend/src/ee/routes/v1/scim-router.ts index e3424b6a9..955c78c7f 100644 --- a/backend/src/ee/routes/v1/scim-router.ts +++ b/backend/src/ee/routes/v1/scim-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { ScimTokensSchema } from "@app/db/schemas"; +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"; @@ -20,6 +21,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { server.route({ url: "/scim-tokens", method: "POST", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z.object({ @@ -51,6 +55,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { server.route({ url: "/scim-tokens", method: "GET", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { querystring: z.object({ @@ -78,6 +85,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { server.route({ url: "/scim-tokens/:scimTokenId", method: "DELETE", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ diff --git a/backend/src/ee/routes/v1/secret-approval-policy-router.ts b/backend/src/ee/routes/v1/secret-approval-policy-router.ts index 1ea16178f..f6a955625 100644 --- a/backend/src/ee/routes/v1/secret-approval-policy-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-policy-router.ts @@ -1,6 +1,7 @@ import { nanoid } from "nanoid"; import { z } from "zod"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { sapPubSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -9,6 +10,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi server.route({ url: "/", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { body: z .object({ @@ -47,6 +51,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi server.route({ url: "/:sapId", method: "PATCH", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ sapId: z.string() @@ -85,6 +92,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi server.route({ url: "/:sapId", method: "DELETE", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ sapId: z.string() @@ -111,6 +121,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi server.route({ url: "/", method: "GET", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ workspaceId: z.string().trim() @@ -137,6 +150,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi server.route({ url: "/board", method: "GET", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ workspaceId: z.string().trim(), diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index 16b6d205b..2a9cc405d 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -10,13 +10,17 @@ import { } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types"; +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"; export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ workspaceId: z.string().trim(), @@ -62,8 +66,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv }); server.route({ - url: "/count", method: "GET", + url: "/count", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ workspaceId: z.string().trim() @@ -93,6 +100,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv server.route({ url: "/:id/merge", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ id: z.string() @@ -117,8 +127,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv }); server.route({ - url: "/:id/review", method: "POST", + url: "/:id/review", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ id: z.string() @@ -147,8 +160,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv }); server.route({ - url: "/:id/status", method: "POST", + url: "/:id/status", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ id: z.string() @@ -203,8 +219,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv .array() .optional(); server.route({ - url: "/:id", method: "GET", + url: "/:id", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ id: z.string() diff --git a/backend/src/ee/routes/v1/secret-rotation-provider-router.ts b/backend/src/ee/routes/v1/secret-rotation-provider-router.ts index 516885936..58419d3b7 100644 --- a/backend/src/ee/routes/v1/secret-rotation-provider-router.ts +++ b/backend/src/ee/routes/v1/secret-rotation-provider-router.ts @@ -1,12 +1,16 @@ import { z } from "zod"; +import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerSecretRotationProviderRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:workspaceId", method: "GET", + url: "/:workspaceId", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() diff --git a/backend/src/ee/routes/v1/secret-rotation-router.ts b/backend/src/ee/routes/v1/secret-rotation-router.ts index 447280cf8..d951eb744 100644 --- a/backend/src/ee/routes/v1/secret-rotation-router.ts +++ b/backend/src/ee/routes/v1/secret-rotation-router.ts @@ -2,13 +2,17 @@ import { z } from "zod"; import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas"; import { removeTrailingSlash } from "@app/lib/fn"; +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"; export const registerSecretRotationRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", method: "POST", + url: "/", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ workspaceId: z.string().trim(), @@ -52,6 +56,9 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = server.route({ url: "/restart", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ id: z.string().trim() @@ -86,6 +93,9 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = server.route({ url: "/", method: "GET", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ workspaceId: z.string().trim() @@ -136,8 +146,11 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = }); server.route({ - url: "/:id", method: "DELETE", + url: "/:id", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ id: z.string().trim() diff --git a/backend/src/ee/routes/v1/secret-scanning-router.ts b/backend/src/ee/routes/v1/secret-scanning-router.ts index 1f8d56c74..2604d7232 100644 --- a/backend/src/ee/routes/v1/secret-scanning-router.ts +++ b/backend/src/ee/routes/v1/secret-scanning-router.ts @@ -2,13 +2,17 @@ import { z } from "zod"; import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas"; import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types"; +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"; export const registerSecretScanningRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/create-installation-session/organization", method: "POST", + url: "/create-installation-session/organization", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ organizationId: z.string().trim() }), response: { @@ -31,8 +35,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = }); server.route({ - url: "/link-installation", method: "POST", + url: "/link-installation", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ installationId: z.string(), @@ -56,8 +63,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = }); server.route({ - url: "/installation-status/organization/:organizationId", method: "GET", + url: "/installation-status/organization/:organizationId", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -80,6 +90,9 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = server.route({ url: "/organization/:organizationId/risks", method: "GET", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -100,8 +113,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = }); server.route({ - url: "/organization/:organizationId/risks/:riskId/status", method: "POST", + url: "/organization/:organizationId/risks/:riskId/status", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim(), riskId: z.string().trim() }), body: z.object({ status: z.nativeEnum(SecretScanningRiskStatus) }), diff --git a/backend/src/ee/routes/v1/secret-version-router.ts b/backend/src/ee/routes/v1/secret-version-router.ts index 630af10cf..0604135ba 100644 --- a/backend/src/ee/routes/v1/secret-version-router.ts +++ b/backend/src/ee/routes/v1/secret-version-router.ts @@ -1,13 +1,17 @@ import { z } from "zod"; import { SecretVersionsSchema } from "@app/db/schemas"; +import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerSecretVersionRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:secretId/secret-versions", method: "GET", + url: "/:secretId/secret-versions", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ secretId: z.string() diff --git a/backend/src/ee/routes/v1/snapshot-router.ts b/backend/src/ee/routes/v1/snapshot-router.ts index 902d707af..7eee44f7e 100644 --- a/backend/src/ee/routes/v1/snapshot-router.ts +++ b/backend/src/ee/routes/v1/snapshot-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas"; import { PROJECTS } from "@app/lib/api-docs"; +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"; @@ -9,6 +10,9 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:secretSnapshotId", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ secretSnapshotId: z.string().trim() @@ -58,6 +62,9 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/:secretSnapshotId/rollback", + config: { + rateLimit: writeLimit + }, schema: { description: "Roll back project secrets to those captured in a secret snapshot version.", security: [ diff --git a/backend/src/ee/routes/v1/trusted-ip-router.ts b/backend/src/ee/routes/v1/trusted-ip-router.ts index 3d4ac8010..b6fc3cc90 100644 --- a/backend/src/ee/routes/v1/trusted-ip-router.ts +++ b/backend/src/ee/routes/v1/trusted-ip-router.ts @@ -2,13 +2,17 @@ import { z } from "zod"; import { TrustedIpsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +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"; export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:workspaceId/trusted-ips", method: "GET", + url: "/:workspaceId/trusted-ips", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -33,8 +37,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/trusted-ips", method: "POST", + url: "/:workspaceId/trusted-ips", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -78,8 +85,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/trusted-ips/:trustedIpId", method: "PATCH", + url: "/:workspaceId/trusted-ips/:trustedIpId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim(), @@ -124,8 +134,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/trusted-ips/:trustedIpId", method: "DELETE", + url: "/:workspaceId/trusted-ips/:trustedIpId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim(), diff --git a/backend/src/ee/routes/v1/user-additional-privilege-router.ts b/backend/src/ee/routes/v1/user-additional-privilege-router.ts index 3db812c79..7225caecf 100644 --- a/backend/src/ee/routes/v1/user-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/user-additional-privilege-router.ts @@ -6,6 +6,7 @@ import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas"; import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types"; import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; import { alphaNumericNanoId } from "@app/lib/nanoid"; +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"; @@ -13,6 +14,9 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr server.route({ url: "/permanent", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId), @@ -52,8 +56,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }); server.route({ - url: "/temporary", method: "POST", + url: "/temporary", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId), @@ -104,8 +111,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }); server.route({ - url: "/:privilegeId", method: "PATCH", + url: "/:privilegeId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ privilegeId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.privilegeId) @@ -158,8 +168,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }); server.route({ - url: "/:privilegeId", method: "DELETE", + url: "/:privilegeId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.DELETE.privilegeId) @@ -184,8 +197,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }); server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ projectMembershipId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.LIST.projectMembershipId) @@ -210,8 +226,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }); server.route({ - url: "/:privilegeId", method: "GET", + url: "/:privilegeId", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.GET_BY_PRIVILEGEID.privilegeId) diff --git a/backend/src/server/config/rateLimiter.ts b/backend/src/server/config/rateLimiter.ts index 76429b0a5..d8069b9db 100644 --- a/backend/src/server/config/rateLimiter.ts +++ b/backend/src/server/config/rateLimiter.ts @@ -18,6 +18,28 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => { }; }; +// GET endpoints +export const readLimit: RateLimitOptions = { + timeWindow: 60 * 1000, + max: 600, + keyGenerator: (req) => req.realIp +}; + +// POST, PATCH, PUT, DELETE endpoints +export const writeLimit: RateLimitOptions = { + timeWindow: 60 * 1000, + max: 50, + keyGenerator: (req) => req.realIp +}; + +// special endpoints +export const secretsLimit: RateLimitOptions = { + // secrets, folders, secret imports + timeWindow: 60 * 1000, + max: 600, + keyGenerator: (req) => req.realIp +}; + export const authRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, max: 60, @@ -26,12 +48,13 @@ export const authRateLimit: RateLimitOptions = { export const inviteUserRateLimit: RateLimitOptions = { timeWindow: 60 * 1000, - max: 10, + max: 30, keyGenerator: (req) => req.realIp }; -export const passwordRateLimit: RateLimitOptions = { +export const creationLimit: RateLimitOptions = { + // identity, project, org timeWindow: 60 * 1000, - max: 600, + max: 30, keyGenerator: (req) => req.realIp }; diff --git a/backend/src/server/plugins/secret-scanner.ts b/backend/src/server/plugins/secret-scanner.ts index 8790d54d4..d20008de7 100644 --- a/backend/src/server/plugins/secret-scanner.ts +++ b/backend/src/server/plugins/secret-scanner.ts @@ -4,6 +4,7 @@ import SmeeClient from "smee-client"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; +import { writeLimit } from "@app/server/config/rateLimiter"; export const registerSecretScannerGhApp = async (server: FastifyZodProvider) => { const probotApp = (app: Probot) => { @@ -49,6 +50,9 @@ export const registerSecretScannerGhApp = async (server: FastifyZodProvider) => server.route({ method: "POST", url: "/", + config: { + rateLimit: writeLimit + }, handler: async (req, res) => { const eventName = req.headers["x-github-event"]; const signatureSHA256 = req.headers["x-hub-signature-256"] as string; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index a8e0e74a5..f7fdb9205 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -49,6 +49,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 { 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"; @@ -671,8 +672,11 @@ export const registerRoutes = async ( await server.register(injectAuditLogInfo); server.route({ - url: "/api/status", method: "GET", + url: "/api/status", + config: { + rateLimit: readLimit + }, schema: { response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index 730ba6cae..e70822128 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -11,8 +12,11 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; export const registerAdminRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/config", method: "GET", + url: "/config", + config: { + rateLimit: readLimit + }, schema: { response: { 200: z.object({ @@ -30,8 +34,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/config", method: "PATCH", + url: "/config", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ allowSignUp: z.boolean().optional(), @@ -55,8 +62,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/signup", method: "POST", + url: "/signup", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ email: z.string().email().trim(), diff --git a/backend/src/server/routes/v1/auth-router.ts b/backend/src/server/routes/v1/auth-router.ts index 16448b8ca..7f09a904b 100644 --- a/backend/src/server/routes/v1/auth-router.ts +++ b/backend/src/server/routes/v1/auth-router.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; -import { authRateLimit } from "@app/server/config/rateLimiter"; +import { authRateLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; @@ -38,8 +38,11 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => { }); server.route({ - url: "/checkAuth", method: "POST", + url: "/checkAuth", + config: { + rateLimit: writeLimit + }, schema: { response: { 200: z.object({ @@ -52,8 +55,11 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => { }); server.route({ - url: "/token", method: "POST", + url: "/token", + config: { + rateLimit: writeLimit + }, schema: { response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/bot-router.ts b/backend/src/server/routes/v1/bot-router.ts index 7e1d6ec94..34a34f843 100644 --- a/backend/src/server/routes/v1/bot-router.ts +++ b/backend/src/server/routes/v1/bot-router.ts @@ -1,13 +1,17 @@ import { z } from "zod"; import { ProjectBotsSchema } from "@app/db/schemas"; +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"; export const registerProjectBotRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:projectId", method: "GET", + url: "/:projectId", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ projectId: z.string().trim() @@ -38,8 +42,11 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:botId/active", method: "PATCH", + url: "/:botId/active", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ isActive: z.boolean(), diff --git a/backend/src/server/routes/v1/identity-access-token-router.ts b/backend/src/server/routes/v1/identity-access-token-router.ts index 5cd0b27a1..387c54c13 100644 --- a/backend/src/server/routes/v1/identity-access-token-router.ts +++ b/backend/src/server/routes/v1/identity-access-token-router.ts @@ -1,11 +1,15 @@ import { z } from "zod"; import { UNIVERSAL_AUTH } from "@app/lib/api-docs"; +import { writeLimit } from "@app/server/config/rateLimiter"; export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => { server.route({ url: "/token/renew", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { description: "Renew access token", body: z.object({ diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index 3ce13ca0a..e174cf974 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { IDENTITIES } from "@app/lib/api-docs"; +import { creationLimit, writeLimit } from "@app/server/config/rateLimiter"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -12,6 +13,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/", + config: { + rateLimit: creationLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Create identity", @@ -71,6 +75,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", url: "/:identityId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Update identity", @@ -121,6 +128,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:identityId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Delete identity", diff --git a/backend/src/server/routes/v1/identity-ua.ts b/backend/src/server/routes/v1/identity-ua.ts index ac5c78a08..670f52416 100644 --- a/backend/src/server/routes/v1/identity-ua.ts +++ b/backend/src/server/routes/v1/identity-ua.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { UNIVERSAL_AUTH } from "@app/lib/api-docs"; +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"; import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; @@ -22,8 +23,11 @@ export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/universal-auth/login", method: "POST", + url: "/universal-auth/login", + config: { + rateLimit: writeLimit + }, schema: { description: "Login with Universal Auth", body: z.object({ @@ -66,8 +70,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/universal-auth/identities/:identityId", method: "POST", + url: "/universal-auth/identities/:identityId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Attach Universal Auth configuration onto identity", @@ -156,8 +163,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/universal-auth/identities/:identityId", method: "PATCH", + url: "/universal-auth/identities/:identityId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Update Universal Auth configuration on identity", @@ -239,8 +249,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/universal-auth/identities/:identityId", method: "GET", + url: "/universal-auth/identities/:identityId", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Retrieve Universal Auth configuration on identity", @@ -283,8 +296,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/universal-auth/identities/:identityId/client-secrets", method: "POST", + url: "/universal-auth/identities/:identityId/client-secrets", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Create Universal Auth Client Secret for identity", @@ -335,8 +351,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/universal-auth/identities/:identityId/client-secrets", method: "GET", + url: "/universal-auth/identities/:identityId/client-secrets", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "List Universal Auth Client Secrets for identity", @@ -378,8 +397,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke", method: "POST", + url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Revoke Universal Auth Client Secrets for identity", diff --git a/backend/src/server/routes/v1/integration-auth-router.ts b/backend/src/server/routes/v1/integration-auth-router.ts index a68479074..604ac0bc2 100644 --- a/backend/src/server/routes/v1/integration-auth-router.ts +++ b/backend/src/server/routes/v1/integration-auth-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { INTEGRATION_AUTH } from "@app/lib/api-docs"; +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"; @@ -9,8 +10,11 @@ import { integrationAuthPubSchema } from "../sanitizedSchemas"; export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/integration-options", method: "GET", + url: "/integration-options", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "List of integrations available.", @@ -43,8 +47,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId", method: "GET", + url: "/:integrationAuthId", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Get details of an integration authorization by auth object id.", @@ -75,8 +82,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/", method: "DELETE", + url: "/", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Remove all integration's auth object from the project.", @@ -121,8 +131,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId", method: "DELETE", + url: "/:integrationAuthId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Remove an integration auth object by object id.", @@ -165,8 +178,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/oauth-token", method: "POST", + url: "/oauth-token", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z.object({ @@ -206,8 +222,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/access-token", method: "POST", + url: "/access-token", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Create the integration authentication object required for syncing secrets.", @@ -256,8 +275,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/apps", method: "GET", + url: "/:integrationAuthId/apps", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -293,8 +315,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/teams", method: "GET", + url: "/:integrationAuthId/teams", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -324,8 +349,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/vercel/branches", method: "GET", + url: "/:integrationAuthId/vercel/branches", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -354,8 +382,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/checkly/groups", method: "GET", + url: "/:integrationAuthId/checkly/groups", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -384,8 +415,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/github/orgs", method: "GET", + url: "/:integrationAuthId/github/orgs", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -412,8 +446,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/github/envs", method: "GET", + url: "/:integrationAuthId/github/envs", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -446,8 +483,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/qovery/orgs", method: "GET", + url: "/:integrationAuthId/qovery/orgs", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -472,8 +512,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/qovery/projects", method: "GET", + url: "/:integrationAuthId/qovery/projects", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -502,8 +545,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/qovery/environments", method: "GET", + url: "/:integrationAuthId/qovery/environments", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -532,8 +578,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/qovery/apps", method: "GET", + url: "/:integrationAuthId/qovery/apps", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -562,8 +611,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/qovery/containers", method: "GET", + url: "/:integrationAuthId/qovery/containers", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -592,8 +644,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/qovery/jobs", method: "GET", + url: "/:integrationAuthId/qovery/jobs", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -622,8 +677,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/heroku/pipelines", method: "GET", + url: "/:integrationAuthId/heroku/pipelines", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -654,8 +712,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/railway/environments", method: "GET", + url: "/:integrationAuthId/railway/environments", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -684,8 +745,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/railway/services", method: "GET", + url: "/:integrationAuthId/railway/services", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -714,8 +778,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/bitbucket/workspaces", method: "GET", + url: "/:integrationAuthId/bitbucket/workspaces", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -750,8 +817,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/northflank/secret-groups", method: "GET", + url: "/:integrationAuthId/northflank/secret-groups", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -785,8 +855,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:integrationAuthId/teamcity/build-configs", method: "GET", + url: "/:integrationAuthId/teamcity/build-configs", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index 0bc434dbe..cf8fbf41f 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -4,6 +4,7 @@ import { IntegrationsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { INTEGRATION } from "@app/lib/api-docs"; import { removeTrailingSlash, shake } from "@app/lib/fn"; +import { writeLimit } from "@app/server/config/rateLimiter"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -11,8 +12,11 @@ import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telem export const registerIntegrationRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", method: "POST", + url: "/", + config: { + rateLimit: writeLimit + }, schema: { description: "Create an integration to sync secrets.", security: [ @@ -112,8 +116,11 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:integrationId", method: "PATCH", + url: "/:integrationId", + config: { + rateLimit: writeLimit + }, schema: { description: "Update an integration by integration id", security: [ @@ -159,8 +166,11 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:integrationId", method: "DELETE", + url: "/:integrationId", + config: { + rateLimit: writeLimit + }, schema: { description: "Remove an integration using the integration object ID", security: [ diff --git a/backend/src/server/routes/v1/invite-org-router.ts b/backend/src/server/routes/v1/invite-org-router.ts index 89fc561e6..873710f10 100644 --- a/backend/src/server/routes/v1/invite-org-router.ts +++ b/backend/src/server/routes/v1/invite-org-router.ts @@ -56,6 +56,9 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => { server.route({ url: "/verify", method: "POST", + config: { + rateLimit: inviteUserRateLimit + }, schema: { body: z.object({ email: z.string().trim().email(), diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 42e64e9d7..651b76bac 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { IncidentContactsSchema, OrganizationsSchema, OrgMembershipsSchema, UsersSchema } from "@app/db/schemas"; +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"; @@ -8,6 +9,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/", + config: { + rateLimit: readLimit + }, schema: { response: { 200: z.object({ @@ -25,6 +29,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:organizationId", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() @@ -50,6 +57,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:organizationId/users", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() @@ -87,6 +97,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", url: "/:organizationId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), body: z.object({ @@ -128,6 +141,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:organizationId/incidentContactOrg", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), response: { @@ -151,6 +167,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/:organizationId/incidentContactOrg", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() }), body: z.object({ email: z.string().email().trim() }), @@ -176,6 +195,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:organizationId/incidentContactOrg/:incidentContactId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }), response: { diff --git a/backend/src/server/routes/v1/password-router.ts b/backend/src/server/routes/v1/password-router.ts index d5c5054df..a8ef3fb77 100644 --- a/backend/src/server/routes/v1/password-router.ts +++ b/backend/src/server/routes/v1/password-router.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { BackupPrivateKeySchema, UsersSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { passwordRateLimit } from "@app/server/config/rateLimiter"; +import { authRateLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { validateSignUpAuthorization } from "@app/services/auth/auth-fns"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -12,7 +12,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => { method: "POST", url: "/srp1", config: { - rateLimit: passwordRateLimit + rateLimit: authRateLimit }, schema: { body: z.object({ @@ -39,7 +39,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => { method: "POST", url: "/change-password", config: { - rateLimit: passwordRateLimit + rateLimit: authRateLimit }, schema: { body: z.object({ @@ -78,7 +78,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => { method: "POST", url: "/email/password-reset", config: { - rateLimit: passwordRateLimit + rateLimit: authRateLimit }, schema: { body: z.object({ @@ -103,7 +103,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => { method: "POST", url: "/email/password-reset-verify", config: { - rateLimit: passwordRateLimit + rateLimit: authRateLimit }, schema: { body: z.object({ @@ -133,7 +133,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => { method: "POST", url: "/backup-private-key", config: { - rateLimit: passwordRateLimit + rateLimit: authRateLimit }, onRequest: verifyAuth([AuthMode.JWT]), schema: { @@ -168,7 +168,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => { method: "GET", url: "/backup-private-key", config: { - rateLimit: passwordRateLimit + rateLimit: authRateLimit }, schema: { response: { @@ -190,6 +190,9 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/password-reset", + config: { + rateLimit: authRateLimit + }, schema: { body: z.object({ protectedKey: z.string().trim(), diff --git a/backend/src/server/routes/v1/project-env-router.ts b/backend/src/server/routes/v1/project-env-router.ts index 68a5a9001..e1efa1637 100644 --- a/backend/src/server/routes/v1/project-env-router.ts +++ b/backend/src/server/routes/v1/project-env-router.ts @@ -3,13 +3,17 @@ import { z } from "zod"; import { ProjectEnvironmentsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ENVIRONMENTS } from "@app/lib/api-docs"; +import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:workspaceId/environments", method: "POST", + url: "/:workspaceId/environments", + config: { + rateLimit: writeLimit + }, schema: { description: "Create environment", security: [ @@ -64,8 +68,11 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/environments/:id", method: "PATCH", + url: "/:workspaceId/environments/:id", + config: { + rateLimit: writeLimit + }, schema: { description: "Update environment", security: [ @@ -128,8 +135,11 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/environments/:id", method: "DELETE", + url: "/:workspaceId/environments/:id", + config: { + rateLimit: writeLimit + }, schema: { description: "Delete environment", security: [ diff --git a/backend/src/server/routes/v1/project-key-router.ts b/backend/src/server/routes/v1/project-key-router.ts index 440d45140..bb35794e9 100644 --- a/backend/src/server/routes/v1/project-key-router.ts +++ b/backend/src/server/routes/v1/project-key-router.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -7,6 +8,9 @@ export const registerProjectKeyRouter = async (server: FastifyZodProvider) => { server.route({ url: "/:workspaceId/key", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index f277e556c..2b281e6fe 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -10,14 +10,18 @@ import { } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { PROJECTS } from "@app/lib/api-docs"; +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"; import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types"; export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:workspaceId/memberships", method: "GET", + url: "/:workspaceId/memberships", + config: { + rateLimit: readLimit + }, schema: { description: "Return project user memberships", security: [ @@ -75,8 +79,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider }); server.route({ - url: "/:workspaceId/memberships", method: "POST", + url: "/:workspaceId/memberships", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -126,8 +133,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider }); server.route({ - url: "/:workspaceId/memberships/:membershipId", method: "PATCH", + url: "/:workspaceId/memberships/:membershipId", + config: { + rateLimit: writeLimit + }, schema: { description: "Update project user membership", security: [ @@ -197,8 +207,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider }); server.route({ - url: "/:workspaceId/memberships/:membershipId", method: "DELETE", + url: "/:workspaceId/memberships/:membershipId", + config: { + rateLimit: writeLimit + }, schema: { description: "Delete project user membership", security: [ diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index ca01820a9..a4df0416e 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -8,6 +8,7 @@ import { UsersSchema } from "@app/db/schemas"; import { INTEGRATION_AUTH, PROJECTS } from "@app/lib/api-docs"; +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"; import { ProjectFilterType } from "@app/services/project/project-types"; @@ -24,8 +25,11 @@ const projectWithEnv = ProjectsSchema.merge( export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:workspaceId/keys", method: "GET", + url: "/:workspaceId/keys", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -55,8 +59,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/users", method: "GET", + url: "/:workspaceId/users", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -108,8 +115,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, schema: { response: { 200: z.object({ @@ -125,8 +135,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId", method: "GET", + url: "/:workspaceId", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim().describe(PROJECTS.GET.workspaceId) @@ -154,8 +167,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId", method: "DELETE", + url: "/:workspaceId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim().describe(PROJECTS.DELETE.workspaceId) @@ -185,6 +201,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ url: "/:workspaceId/name", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -217,8 +236,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId", method: "PATCH", + url: "/:workspaceId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim().describe(PROJECTS.UPDATE.workspaceId) @@ -261,8 +283,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/auto-capitalization", method: "POST", + url: "/:workspaceId/auto-capitalization", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -295,8 +320,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/integrations", method: "GET", + url: "/:workspaceId/integrations", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() @@ -329,8 +357,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/authorizations", method: "GET", + url: "/:workspaceId/authorizations", + config: { + rateLimit: readLimit + }, schema: { description: "List integration auth objects for a workspace.", security: [ @@ -361,8 +392,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:workspaceId/service-token-data", method: "GET", + url: "/:workspaceId/service-token-data", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ workspaceId: z.string().trim() diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index bd202b70b..36544de1c 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -4,6 +4,7 @@ import { SecretFoldersSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { FOLDERS } from "@app/lib/api-docs"; import { removeTrailingSlash } from "@app/lib/fn"; +import { readLimit, secretsLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -11,6 +12,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => server.route({ url: "/", method: "POST", + config: { + rateLimit: secretsLimit + }, schema: { description: "Create folders", security: [ @@ -65,6 +69,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => server.route({ url: "/:folderId", method: "PATCH", + config: { + rateLimit: secretsLimit + }, schema: { description: "Update folder", security: [ @@ -124,8 +131,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => // TODO(daniel): Expose this route in api reference and write docs for it. server.route({ - url: "/:folderIdOrName", method: "DELETE", + url: "/:folderIdOrName", + config: { + rateLimit: secretsLimit + }, schema: { description: "Delete a folder", security: [ @@ -181,8 +191,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }); server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, schema: { description: "Get folders", security: [ diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v1/secret-import-router.ts index bd47a7424..0d3f547b3 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v1/secret-import-router.ts @@ -4,13 +4,17 @@ import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { SECRET_IMPORTS } from "@app/lib/api-docs"; import { removeTrailingSlash } from "@app/lib/fn"; +import { readLimit, secretsLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerSecretImportRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", method: "POST", + url: "/", + config: { + rateLimit: secretsLimit + }, schema: { description: "Create secret imports", security: [ @@ -71,8 +75,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => }); server.route({ - url: "/:secretImportId", method: "PATCH", + url: "/:secretImportId", + config: { + rateLimit: secretsLimit + }, schema: { description: "Update secret imports", security: [ @@ -143,8 +150,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => }); server.route({ - url: "/:secretImportId", method: "DELETE", + url: "/:secretImportId", + config: { + rateLimit: secretsLimit + }, schema: { description: "Delete secret imports", security: [ @@ -204,8 +214,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => }); server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, schema: { description: "Get secret imports", security: [ @@ -262,6 +275,9 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => server.route({ url: "/secrets", method: "GET", + config: { + rateLimit: secretsLimit + }, schema: { querystring: z.object({ workspaceId: z.string().trim(), diff --git a/backend/src/server/routes/v1/secret-tag-router.ts b/backend/src/server/routes/v1/secret-tag-router.ts index 519b257ae..1715aa3c3 100644 --- a/backend/src/server/routes/v1/secret-tag-router.ts +++ b/backend/src/server/routes/v1/secret-tag-router.ts @@ -2,13 +2,17 @@ import { z } from "zod"; import { SecretTagsSchema } from "@app/db/schemas"; import { SECRET_TAGS } from "@app/lib/api-docs"; +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"; export const registerSecretTagRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:projectId/tags", method: "GET", + url: "/:projectId/tags", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ projectId: z.string().trim().describe(SECRET_TAGS.LIST.projectId) @@ -33,8 +37,11 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:projectId/tags", method: "POST", + url: "/:projectId/tags", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId) @@ -65,8 +72,11 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:projectId/tags/:tagId", method: "DELETE", + url: "/:projectId/tags/:tagId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().trim().describe(SECRET_TAGS.DELETE.projectId), diff --git a/backend/src/server/routes/v1/user-action-router.ts b/backend/src/server/routes/v1/user-action-router.ts index c730cdb91..5a2ae484e 100644 --- a/backend/src/server/routes/v1/user-action-router.ts +++ b/backend/src/server/routes/v1/user-action-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { UserActionsSchema } from "@app/db/schemas"; +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"; @@ -8,6 +9,9 @@ export const registerUserActionRouter = async (server: FastifyZodProvider) => { server.route({ url: "/", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ action: z.string().trim() @@ -29,6 +33,9 @@ export const registerUserActionRouter = async (server: FastifyZodProvider) => { server.route({ url: "/", method: "GET", + config: { + rateLimit: readLimit + }, schema: { querystring: z.object({ action: z.string().trim() diff --git a/backend/src/server/routes/v1/user-router.ts b/backend/src/server/routes/v1/user-router.ts index 031bcc941..bdede8a3a 100644 --- a/backend/src/server/routes/v1/user-router.ts +++ b/backend/src/server/routes/v1/user-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas"; +import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -8,6 +9,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/", + config: { + rateLimit: readLimit + }, schema: { response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 2a5ab49fb..1698c0c4b 100644 --- a/backend/src/server/routes/v1/webhook-router.ts +++ b/backend/src/server/routes/v1/webhook-router.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { WebhooksSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { removeTrailingSlash } from "@app/lib/fn"; +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"; @@ -27,6 +28,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z.object({ @@ -75,6 +79,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", url: "/:webhookId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -122,6 +129,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:webhookId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -159,6 +169,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/:webhookId/test", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ @@ -186,6 +199,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { querystring: z.object({ diff --git a/backend/src/server/routes/v2/identity-org-router.ts b/backend/src/server/routes/v2/identity-org-router.ts index 440130e13..671cca0a1 100644 --- a/backend/src/server/routes/v2/identity-org-router.ts +++ b/backend/src/server/routes/v2/identity-org-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas"; import { ORGANIZATIONS } from "@app/lib/api-docs"; +import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -9,6 +10,9 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:orgId/identity-memberships", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Return organization identity memberships", diff --git a/backend/src/server/routes/v2/identity-project-router.ts b/backend/src/server/routes/v2/identity-project-router.ts index d170f87e6..a4068053f 100644 --- a/backend/src/server/routes/v2/identity-project-router.ts +++ b/backend/src/server/routes/v2/identity-project-router.ts @@ -8,6 +8,7 @@ import { ProjectUserMembershipRolesSchema } from "@app/db/schemas"; import { PROJECTS } from "@app/lib/api-docs"; +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"; import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types"; @@ -16,6 +17,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) server.route({ method: "POST", url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { params: z.object({ @@ -48,6 +52,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) server.route({ method: "PATCH", url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Update project identity memberships", @@ -103,6 +110,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) server.route({ method: "DELETE", url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Delete project identity memberships", @@ -137,6 +147,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) server.route({ method: "GET", url: "/:projectId/identity-memberships", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Return project identity memberships", diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index d8e46d0da..973804c7c 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -2,6 +2,7 @@ import jwt from "jsonwebtoken"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; +import { writeLimit } from "@app/server/config/rateLimiter"; import { AuthModeMfaJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; export const registerMfaRouter = async (server: FastifyZodProvider) => { @@ -30,8 +31,11 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/mfa/send", method: "POST", + url: "/mfa/send", + config: { + rateLimit: writeLimit + }, schema: { response: { 200: z.object({ @@ -48,6 +52,9 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { server.route({ url: "/mfa/verify", method: "POST", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ mfaToken: z.string().trim() diff --git a/backend/src/server/routes/v2/organization-router.ts b/backend/src/server/routes/v2/organization-router.ts index 83ab6792e..a4a66e992 100644 --- a/backend/src/server/routes/v2/organization-router.ts +++ b/backend/src/server/routes/v2/organization-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { OrganizationsSchema, OrgMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas"; import { ORGANIZATIONS } from "@app/lib/api-docs"; +import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { ActorType, AuthMode } from "@app/services/auth/auth-type"; @@ -9,6 +10,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:organizationId/memberships", + config: { + rateLimit: readLimit + }, schema: { description: "Return organization user memberships", security: [ @@ -55,6 +59,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:organizationId/workspaces", + config: { + rateLimit: readLimit + }, schema: { description: "Return projects in organization that user is part of", security: [ @@ -101,6 +108,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", url: "/:organizationId/memberships/:membershipId", + config: { + rateLimit: writeLimit + }, schema: { description: "Update organization user memberships", security: [ @@ -141,6 +151,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:organizationId/memberships/:membershipId", + config: { + rateLimit: writeLimit + }, schema: { description: "Delete organization user memberships", security: [ @@ -177,6 +190,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/", + config: { + rateLimit: creationLimit + }, schema: { body: z.object({ name: z.string().trim() @@ -204,6 +220,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:organizationId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ organizationId: z.string().trim() diff --git a/backend/src/server/routes/v2/project-membership-router.ts b/backend/src/server/routes/v2/project-membership-router.ts index 51bae6d2b..cb9aa73a9 100644 --- a/backend/src/server/routes/v2/project-membership-router.ts +++ b/backend/src/server/routes/v2/project-membership-router.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { ProjectMembershipsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { PROJECTS } from "@app/lib/api-docs"; +import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -10,6 +11,9 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider server.route({ method: "POST", url: "/:projectId/memberships", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().describe(PROJECTS.INVITE_MEMBER.projectId) @@ -56,6 +60,9 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider server.route({ method: "DELETE", url: "/:projectId/memberships", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().describe(PROJECTS.REMOVE_MEMBER.projectId) diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 19ffc57ed..26c7cf30c 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { ProjectKeysSchema, ProjectsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { PROJECTS } from "@app/lib/api-docs"; +import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -28,8 +29,11 @@ const slugSchema = z export const registerProjectRouter = async (server: FastifyZodProvider) => { /* Get project key */ server.route({ - url: "/:workspaceId/encrypted-key", method: "GET", + url: "/:workspaceId/encrypted-key", + config: { + rateLimit: readLimit + }, schema: { description: "Return encrypted project key", security: [ @@ -77,8 +81,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { /* Start upgrade of a project */ server.route({ - url: "/:projectId/upgrade", method: "POST", + url: "/:projectId/upgrade", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().trim() @@ -107,6 +114,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ url: "/:projectId/upgrade/status", method: "GET", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ projectId: z.string().trim() @@ -135,6 +145,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/", + config: { + rateLimit: creationLimit + }, schema: { body: z.object({ projectName: z.string().trim().describe(PROJECTS.CREATE.projectName), @@ -183,6 +196,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:slug", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ slug: slugSchema.describe("The slug of the project to delete.") @@ -214,6 +230,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/:slug", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ slug: slugSchema.describe("The slug of the project to get.") @@ -244,6 +263,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", url: "/:slug", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ slug: slugSchema.describe("The slug of the project to update.") diff --git a/backend/src/server/routes/v2/service-token-router.ts b/backend/src/server/routes/v2/service-token-router.ts index c4d08d104..fb10f17db 100644 --- a/backend/src/server/routes/v2/service-token-router.ts +++ b/backend/src/server/routes/v2/service-token-router.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { ServiceTokensSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { removeTrailingSlash } from "@app/lib/fn"; +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"; @@ -17,8 +18,11 @@ export const sanitizedServiceTokenSchema = ServiceTokensSchema.omit({ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, onRequest: verifyAuth([AuthMode.SERVICE_TOKEN]), schema: { description: "Return Infisical Token data", @@ -69,8 +73,11 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => }); server.route({ - url: "/", method: "POST", + url: "/", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z.object({ @@ -122,8 +129,11 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => }); server.route({ - url: "/:serviceTokenId", method: "DELETE", + url: "/:serviceTokenId", + config: { + rateLimit: writeLimit + }, onRequest: verifyAuth([AuthMode.JWT]), schema: { params: z.object({ diff --git a/backend/src/server/routes/v2/user-router.ts b/backend/src/server/routes/v2/user-router.ts index 12c9b703d..217e32ddf 100644 --- a/backend/src/server/routes/v2/user-router.ts +++ b/backend/src/server/routes/v2/user-router.ts @@ -2,13 +2,17 @@ import { z } from "zod"; import { AuthTokenSessionsSchema, OrganizationsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas"; import { ApiKeysSchema } from "@app/db/schemas/api-keys"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMethod, AuthMode } from "@app/services/auth/auth-type"; export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/me/mfa", method: "PATCH", + url: "/me/mfa", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ isMfaEnabled: z.boolean() @@ -27,8 +31,11 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/me/name", method: "PATCH", + url: "/me/name", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ firstName: z.string().trim(), @@ -48,8 +55,11 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/me/auth-methods", method: "PUT", + url: "/me/auth-methods", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ authMethods: z.nativeEnum(AuthMethod).array().min(1) @@ -70,6 +80,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/me/organizations", + config: { + rateLimit: readLimit + }, schema: { description: "Return organizations that current user is part of", security: [ @@ -93,6 +106,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/me/api-keys", + config: { + rateLimit: readLimit + }, schema: { response: { 200: ApiKeysSchema.omit({ secretHash: true }).array() @@ -108,6 +124,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/me/api-keys", + config: { + rateLimit: writeLimit + }, schema: { body: z.object({ name: z.string().trim(), @@ -130,6 +149,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/me/api-keys/:apiKeyDataId", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ apiKeyDataId: z.string().trim() @@ -150,6 +172,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/me/sessions", + config: { + rateLimit: readLimit + }, schema: { response: { 200: AuthTokenSessionsSchema.array() @@ -165,6 +190,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/me/sessions", + config: { + rateLimit: writeLimit + }, schema: { response: { 200: z.object({ @@ -184,6 +212,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/me", + config: { + rateLimit: readLimit + }, schema: { description: "Retrieve the current user on the request", security: [ @@ -207,6 +238,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/me", + config: { + rateLimit: writeLimit + }, schema: { response: { 200: z.object({ diff --git a/backend/src/server/routes/v3/secret-blind-index-router.ts b/backend/src/server/routes/v3/secret-blind-index-router.ts index 664eaa2c9..cfb27a58e 100644 --- a/backend/src/server/routes/v3/secret-blind-index-router.ts +++ b/backend/src/server/routes/v3/secret-blind-index-router.ts @@ -1,13 +1,17 @@ import { z } from "zod"; import { SecretsSchema } from "@app/db/schemas"; +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"; export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/:projectId/secrets/blind-index-status", method: "GET", + url: "/:projectId/secrets/blind-index-status", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ projectId: z.string().trim() @@ -30,8 +34,11 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:projectId/secrets", method: "GET", + url: "/:projectId/secrets", + config: { + rateLimit: readLimit + }, schema: { params: z.object({ projectId: z.string().trim() @@ -63,8 +70,11 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) }); server.route({ - url: "/:projectId/secrets/names", method: "POST", + url: "/:projectId/secrets/names", + config: { + rateLimit: writeLimit + }, schema: { params: z.object({ projectId: z.string().trim() diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 5344be43d..8f4b2b728 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -13,6 +13,7 @@ import { CommitType } from "@app/ee/services/secret-approval-request/secret-appr import { RAW_SECRETS, SECRETS } from "@app/lib/api-docs"; import { BadRequestError } from "@app/lib/errors"; import { removeTrailingSlash } from "@app/lib/fn"; +import { secretsLimit, writeLimit } from "@app/server/config/rateLimiter"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { getUserAgentType } from "@app/server/plugins/audit-log"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -24,8 +25,11 @@ import { secretRawSchema } from "../sanitizedSchemas"; export const registerSecretRouter = async (server: FastifyZodProvider) => { server.route({ - url: "/tags/:secretName", method: "POST", + url: "/tags/:secretName", + config: { + rateLimit: writeLimit + }, schema: { description: "Attach tags to a secret", security: [ @@ -83,8 +87,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/tags/:secretName", method: "DELETE", + url: "/tags/:secretName", + config: { + rateLimit: writeLimit + }, schema: { description: "Detach tags from a secret", security: [ @@ -142,8 +149,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/raw", method: "GET", + url: "/raw", + config: { + rateLimit: secretsLimit + }, schema: { description: "List secrets", security: [ @@ -261,8 +271,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/raw/:secretName", method: "GET", + url: "/raw/:secretName", + config: { + rateLimit: secretsLimit + }, schema: { description: "Get a secret by name", security: [ @@ -353,8 +366,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/raw/:secretName", method: "POST", + url: "/raw/:secretName", + config: { + rateLimit: secretsLimit + }, schema: { description: "Create secret", security: [ @@ -439,8 +455,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/raw/:secretName", method: "PATCH", + url: "/raw/:secretName", + config: { + rateLimit: secretsLimit + }, schema: { description: "Update secret", security: [ @@ -522,8 +541,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/raw/:secretName", method: "DELETE", + url: "/raw/:secretName", + config: { + rateLimit: secretsLimit + }, schema: { description: "Delete secret", security: [ @@ -599,8 +621,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/", method: "GET", + url: "/", + config: { + rateLimit: secretsLimit + }, schema: { querystring: z.object({ workspaceId: z.string().trim(), @@ -711,8 +736,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:secretName", method: "GET", + url: "/:secretName", + config: { + rateLimit: secretsLimit + }, schema: { params: z.object({ secretName: z.string().trim() @@ -789,6 +817,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { server.route({ url: "/:secretName", method: "POST", + config: { + rateLimit: secretsLimit + }, schema: { body: z.object({ workspaceId: z.string().trim(), @@ -955,8 +986,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:secretName", method: "PATCH", + url: "/:secretName", + config: { + rateLimit: secretsLimit + }, schema: { params: z.object({ secretName: z.string() @@ -1139,8 +1173,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/:secretName", method: "DELETE", + url: "/:secretName", + config: { + rateLimit: secretsLimit + }, schema: { params: z.object({ secretName: z.string() @@ -1260,8 +1297,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/batch", method: "POST", + url: "/batch", + config: { + rateLimit: secretsLimit + }, schema: { body: z.object({ workspaceId: z.string().trim(), @@ -1383,8 +1423,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/batch", method: "PATCH", + url: "/batch", + config: { + rateLimit: secretsLimit + }, schema: { body: z.object({ workspaceId: z.string().trim(), @@ -1506,8 +1549,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/batch", method: "DELETE", + url: "/batch", + config: { + rateLimit: secretsLimit + }, schema: { body: z.object({ workspaceId: z.string().trim(), diff --git a/backend/src/server/routes/v3/user-router.ts b/backend/src/server/routes/v3/user-router.ts index 1672405b1..a9fdba358 100644 --- a/backend/src/server/routes/v3/user-router.ts +++ b/backend/src/server/routes/v3/user-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { ApiKeysSchema } from "@app/db/schemas/api-keys"; +import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -8,6 +9,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", url: "/me/api-keys", + config: { + rateLimit: readLimit + }, schema: { response: { 200: z.object({