From f8e18de2fca1a6e3081fc70a3268cbdcc389445a Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 7 Mar 2026 01:12:10 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20prevent=20deleting=20resou?= =?UTF-8?q?rce=20policies=20if=20they=20have=20attached=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db/pg/schema/schema.ts | 6 ++++ server/private/routers/external.ts | 23 +++++++------ .../routers/policy/deleteResourcePolicy.ts | 33 +++++++++++++++---- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index c20ec303a..35a1d21a3 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -102,6 +102,12 @@ export const resources = pgTable("resources", { () => resourcePolicies.resourcePolicyId, { onDelete: "set null" } ), + defaultResourcePolicyId: integer("defaultResourcePolicyId").references( + () => resourcePolicies.resourcePolicyId, + { + onDelete: "restrict" + } + ), resourceGuid: varchar("resourceGuid", { length: 36 }) .unique() .notNull() diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index 688f565d4..f244a8a10 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -344,6 +344,17 @@ authenticated.get( approval.countApprovals ); +authenticated.delete( + "/resource-policy/:resourcePolicyId", + verifyResourcePolicyAccess, + verifyValidLicense, + // verifyValidSubscription(tierMatrix.loginPageDomain), // todo: use the correct subscription ? + verifyLimits, + verifyUserHasAction(ActionsEnum.deleteResourcePolicy), + logActionAudit(ActionsEnum.deleteResourcePolicy), + policy.deleteResourcePolicy +); + authenticated.get( "/org/:orgId/resource-policies", verifyValidLicense, @@ -355,18 +366,6 @@ authenticated.get( policy.listResourcePolicies ); -authenticated.delete( - "/resource-policy/:resourcePolicyId", - verifyResourcePolicyAccess, - verifyValidLicense, - // verifyValidSubscription(tierMatrix.loginPageDomain), // todo: use the correct subscription ? - verifyOrgAccess, - verifyLimits, - verifyUserHasAction(ActionsEnum.deleteResourcePolicy), - logActionAudit(ActionsEnum.deleteResourcePolicy), - policy.deleteResourcePolicy -); - authenticated.post( "/org/:orgId/resource-policy", verifyValidLicense, diff --git a/server/private/routers/policy/deleteResourcePolicy.ts b/server/private/routers/policy/deleteResourcePolicy.ts index d7887e3a0..17a9a68f9 100644 --- a/server/private/routers/policy/deleteResourcePolicy.ts +++ b/server/private/routers/policy/deleteResourcePolicy.ts @@ -11,7 +11,7 @@ * This file is not licensed under the AGPLv3. */ -import { db, resourcePolicies } from "@server/db"; +import { db, resourcePolicies, resources } from "@server/db"; import response from "@server/lib/response"; import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; @@ -56,12 +56,12 @@ export async function deleteResourcePolicy( const { resourcePolicyId } = parsedParams.data; - const [deletedResource] = await db - .delete(resourcePolicies) - .where(eq(resourcePolicies.resourcePolicyId, resourcePolicyId)) - .returning(); + const [existingResource] = await db + .select() + .from(resourcePolicies) + .where(eq(resourcePolicies.resourcePolicyId, resourcePolicyId)); - if (!deletedResource) { + if (!existingResource) { return next( createHttpError( HttpCode.NOT_FOUND, @@ -70,6 +70,27 @@ export async function deleteResourcePolicy( ); } + const totalAffectedResources = await db.$count( + db + .select() + .from(resources) + .where(eq(resources.resourcePolicyId, resourcePolicyId)) + ); + + if (totalAffectedResources > 0) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + `Cannot delete Policy '${existingResource.name}' as it's being used by at least one resource` + ) + ); + } + + // delete policy + await db + .delete(resourcePolicies) + .where(eq(resourcePolicies.resourcePolicyId, resourcePolicyId)); + return response(res, { data: null, success: true,