Add org rebuild rate limit

This commit is contained in:
Owen
2026-06-29 14:59:05 -04:00
parent faee9e6330
commit d5d99a4804
23 changed files with 413 additions and 20 deletions

View File

@@ -8,7 +8,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
const addClientToSiteResourceBodySchema = z
.object({
@@ -128,6 +131,15 @@ export async function addClientToSiteResource(
);
}
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// Check if client already exists in site resource
const existingEntry = await db
.select()

View File

@@ -9,7 +9,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
const addRoleToSiteResourceBodySchema = z
.object({
@@ -104,6 +107,15 @@ export async function addRoleToSiteResource(
);
}
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// verify the role exists and belongs to the same org
const [role] = await db
.select()

View File

@@ -9,7 +9,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
const addUserToSiteResourceBodySchema = z
.object({
@@ -104,6 +107,15 @@ export async function addUserToSiteResource(
);
}
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// Check if user already exists in site resource
const existingEntry = await db
.select()

View File

@@ -15,7 +15,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and, inArray } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromClient,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
const batchAddClientToSiteResourcesParamsSchema = z
.object({
@@ -186,6 +189,15 @@ export async function batchAddClientToSiteResources(
);
}
if (await isOrgRebuildRateLimited(client.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
if (client.userId !== null) {
return next(
createHttpError(

View File

@@ -21,7 +21,10 @@ import {
} from "@server/lib/ip";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
import response from "@server/lib/response";
import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
@@ -339,6 +342,15 @@ export async function createSiteResource(
);
}
if (await isOrgRebuildRateLimited(org.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// Only check if destination is an IP address
const isIp = z
.union([z.ipv4(), z.ipv6()])

View File

@@ -8,7 +8,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
const removeClientFromSiteResourceBodySchema = z
.object({
@@ -106,6 +109,14 @@ export async function removeClientFromSiteResource(
);
}
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// Check if client exists and has a userId
const [client] = await db
.select()

View File

@@ -9,7 +9,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
const removeRoleFromSiteResourceBodySchema = z
.object({
@@ -106,6 +109,15 @@ export async function removeRoleFromSiteResource(
);
}
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// Check if the role is an admin role
const [roleToCheck] = await db
.select()

View File

@@ -9,7 +9,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
const removeUserFromSiteResourceBodySchema = z
.object({
@@ -106,6 +109,15 @@ export async function removeUserFromSiteResource(
);
}
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// Check if user exists in site resource
const existingEntry = await db
.select()

View File

@@ -8,7 +8,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, inArray } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
const setSiteResourceClientsBodySchema = z
.object({
@@ -107,6 +110,15 @@ export async function setSiteResourceClients(
);
}
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// Check if any clients have a userId (associated with a user)
if (clientIds.length > 0) {
const clientsWithUsers = await db

View File

@@ -9,7 +9,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and, ne, inArray } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import { rebuildClientAssociationsFromSiteResource, isOrgRebuildRateLimited } from "@server/lib/rebuildClientAssociations";
const setSiteResourceRolesBodySchema = z
.object({
@@ -167,6 +167,15 @@ export async function setSiteResourceRoles(
}
});
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
rebuildClientAssociationsFromSiteResource(siteResource).catch((e) => {
logger.error(
`Failed to rebuild client associations for site resource ${siteResourceId}. Error: ${e}`

View File

@@ -9,7 +9,10 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import {
rebuildClientAssociationsFromSiteResource,
isOrgRebuildRateLimited
} from "@server/lib/rebuildClientAssociations";
import { error } from "node:console";
const setSiteResourceUsersBodySchema = z
@@ -109,6 +112,15 @@ export async function setSiteResourceUsers(
);
}
if (await isOrgRebuildRateLimited(siteResource.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
await db.transaction(async (trx) => {
await trx
.delete(userSiteResources)

View File

@@ -19,6 +19,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { isIpInCidr, portRangeStringSchema } from "@server/lib/ip";
import {
handleMessagingForUpdatedSiteResource,
isOrgRebuildRateLimited,
rebuildClientAssociationsFromSiteResource,
waitForSiteResourceRebuildIdle
} from "@server/lib/rebuildClientAssociations";
@@ -345,6 +346,15 @@ export async function updateSiteResource(
);
}
if (await isOrgRebuildRateLimited(org.orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
// Verify the site exists and belongs to the org
const sitesToAssign = await db
.select()