Add concurrency guard calculateUserClientsForOrgs

This commit is contained in:
Owen
2026-06-29 15:02:41 -04:00
parent d5d99a4804
commit 4718c489d3
4 changed files with 43 additions and 16 deletions
@@ -23,6 +23,7 @@ import { and, eq, sql } from "drizzle-orm";
import { removeUserFromOrg } from "@server/lib/userOrg"; import { removeUserFromOrg } from "@server/lib/userOrg";
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import { isOrgRebuildRateLimited } from "@server/lib/rebuildClientAssociations";
const paramsSchema = z const paramsSchema = z
.object({ .object({
@@ -90,6 +91,15 @@ export async function unassociateOrgIdp(
); );
} }
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."
)
);
}
const orgUsersFromIdp = await db const orgUsersFromIdp = await db
.select({ .select({
userId: userOrgs.userId, userId: userOrgs.userId,
+10
View File
@@ -21,6 +21,7 @@ import { FeatureId } from "@server/lib/billing";
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
import { build } from "@server/build"; import { build } from "@server/build";
import { assignUserToOrg } from "@server/lib/userOrg"; import { assignUserToOrg } from "@server/lib/userOrg";
import { isOrgRebuildRateLimited } from "@server/lib/rebuildClientAssociations";
const acceptInviteBodySchema = z.strictObject({ const acceptInviteBodySchema = z.strictObject({
token: z.string(), token: z.string(),
@@ -147,6 +148,15 @@ export async function acceptInvite(
); );
} }
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."
)
);
}
const inviteRoleRows = await db const inviteRoleRows = await db
.select({ roleId: userInviteRoles.roleId }) .select({ roleId: userInviteRoles.roleId })
.from(userInviteRoles) .from(userInviteRoles)
+10
View File
@@ -19,6 +19,7 @@ import { isSubscribed } from "#dynamic/lib/isSubscribed";
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix"; import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
import { assignUserToOrg } from "@server/lib/userOrg"; import { assignUserToOrg } from "@server/lib/userOrg";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { isOrgRebuildRateLimited } from "@server/lib/rebuildClientAssociations";
const paramsSchema = z.strictObject({ const paramsSchema = z.strictObject({
orgId: z.string().nonempty() orgId: z.string().nonempty()
@@ -229,6 +230,15 @@ export async function createOrgUser(
); );
} }
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."
)
);
}
const [idpRes] = await db const [idpRes] = await db
.select() .select()
.from(idp) .from(idp)
+13 -16
View File
@@ -1,29 +1,17 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { import { db, orgs } from "@server/db";
db, import { userOrgs } from "@server/db";
orgs, import { and, eq } from "drizzle-orm";
resources,
siteResources,
sites,
UserOrg,
userSiteResources,
primaryDb
} from "@server/db";
import { userOrgs, userResources, users, userSites } from "@server/db";
import { and, count, eq, exists, inArray } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import logger from "@server/logger"; import logger from "@server/logger";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { build } from "@server/build";
import { UserType } from "@server/types/UserTypes";
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
import { removeUserFromOrg } from "@server/lib/userOrg"; import { removeUserFromOrg } from "@server/lib/userOrg";
import { isOrgRebuildRateLimited } from "@server/lib/rebuildClientAssociations";
const removeUserSchema = z.strictObject({ const removeUserSchema = z.strictObject({
userId: z.string(), userId: z.string(),
@@ -93,6 +81,15 @@ export async function removeUserOrg(
); );
} }
if (await isOrgRebuildRateLimited(orgId)) {
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
)
);
}
const [org] = await db const [org] = await db
.select() .select()
.from(orgs) .from(orgs)