mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-06 12:19:50 +00:00
Add org rebuild rate limit
This commit is contained in:
@@ -24,7 +24,10 @@ import { isIpInCidr } from "@server/lib/ip";
|
||||
import { listExitNodes } from "#dynamic/lib/exitNodes";
|
||||
import { generateId } from "@server/auth/sessions/app";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||
import {
|
||||
rebuildClientAssociationsFromClient,
|
||||
isOrgRebuildRateLimited
|
||||
} from "@server/lib/rebuildClientAssociations";
|
||||
import { getUniqueClientName } from "@server/db/names";
|
||||
import { build } from "@server/build";
|
||||
|
||||
@@ -154,6 +157,15 @@ export async function createClient(
|
||||
);
|
||||
}
|
||||
|
||||
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 updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`; // we want the block size of the whole org
|
||||
|
||||
// make sure the subnet is unique
|
||||
|
||||
@@ -21,7 +21,10 @@ import { isValidIP } from "@server/lib/validators";
|
||||
import { isIpInCidr } from "@server/lib/ip";
|
||||
import { listExitNodes } from "#dynamic/lib/exitNodes";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||
import {
|
||||
rebuildClientAssociationsFromClient,
|
||||
isOrgRebuildRateLimited
|
||||
} from "@server/lib/rebuildClientAssociations";
|
||||
import { getUniqueClientName } from "@server/db/names";
|
||||
|
||||
const paramsSchema = z
|
||||
@@ -146,6 +149,15 @@ export async function createUserClient(
|
||||
);
|
||||
}
|
||||
|
||||
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 updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`; // we want the block size of the whole org
|
||||
|
||||
// make sure the subnet is unique
|
||||
|
||||
@@ -9,7 +9,10 @@ import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||
import {
|
||||
rebuildClientAssociationsFromClient,
|
||||
isOrgRebuildRateLimited
|
||||
} from "@server/lib/rebuildClientAssociations";
|
||||
import { sendTerminateClient } from "./terminate";
|
||||
import { OlmErrorCodes } from "../olm/error";
|
||||
|
||||
@@ -76,6 +79,15 @@ export async function deleteClient(
|
||||
);
|
||||
}
|
||||
|
||||
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."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Only allow deletion of machine clients (clients without userId)
|
||||
if (client.userId) {
|
||||
return next(
|
||||
|
||||
@@ -9,7 +9,7 @@ import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||
import { rebuildClientAssociationsFromClient, isOrgRebuildRateLimited } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const paramsSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
@@ -60,6 +60,15 @@ export async function rebuildClientAssociationsCacheRoute(
|
||||
);
|
||||
}
|
||||
|
||||
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."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
rebuildClientAssociationsFromClient(client).catch((e) => {
|
||||
logger.error(
|
||||
`Failed to rebuild client associations for client ${clientId}: ${e}`
|
||||
|
||||
@@ -9,7 +9,10 @@ import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import logger from "@server/logger";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||
import {
|
||||
rebuildClientAssociationsFromClient,
|
||||
isOrgRebuildRateLimited
|
||||
} from "@server/lib/rebuildClientAssociations";
|
||||
import { sendTerminateClient } from "../client/terminate";
|
||||
import { OlmErrorCodes } from "./error";
|
||||
|
||||
@@ -64,6 +67,30 @@ export async function deleteUserOlm(
|
||||
|
||||
const { olmId } = parsedParams.data;
|
||||
|
||||
// get the client first
|
||||
const [client] = await db
|
||||
.select()
|
||||
.from(clients)
|
||||
.where(eq(clients.olmId, olmId));
|
||||
|
||||
if (!client) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`No client found for olmId ${olmId}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let deletedClient: Client | undefined;
|
||||
// Delete associated clients and the OLM in a transaction
|
||||
await db.transaction(async (trx) => {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()])
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -10,7 +10,10 @@ import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||
import {
|
||||
rebuildClientAssociationsFromClient,
|
||||
isOrgRebuildRateLimited
|
||||
} from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
/** Legacy path param order: /role/:roleId/add/:userId */
|
||||
const addUserRoleLegacyParamsSchema = z.strictObject({
|
||||
@@ -127,6 +130,15 @@ export async function addUserRoleLegacy(
|
||||
);
|
||||
}
|
||||
|
||||
if (await isOrgRebuildRateLimited(role.orgId)) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.TOO_MANY_REQUESTS,
|
||||
"Too many concurrent rebuild operations for this organization. Please retry after a moment."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let orgClientsToRebuild: Client[] = [];
|
||||
|
||||
await db.transaction(async (trx) => {
|
||||
|
||||
Reference in New Issue
Block a user