From 8004ae68705268842200ad8e34ff25a91c175e4e Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 23 Jun 2026 11:25:09 -0400 Subject: [PATCH] Use the policy when updating rule Fixes #3273 --- server/routers/resource/updateResourceRule.ts | 157 ++++++++++++++---- 1 file changed, 124 insertions(+), 33 deletions(-) diff --git a/server/routers/resource/updateResourceRule.ts b/server/routers/resource/updateResourceRule.ts index cc2a6fc03..84afb38b6 100644 --- a/server/routers/resource/updateResourceRule.ts +++ b/server/routers/resource/updateResourceRule.ts @@ -1,8 +1,8 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { resourceRules, resources } from "@server/db"; -import { eq } from "drizzle-orm"; +import { resourcePolicyRules, resourceRules, resources } from "@server/db"; +import { and, eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -22,13 +22,20 @@ const updateResourceRuleParamsSchema = z.strictObject({ resourceId: z.coerce.number().int().positive() }); +const resourceRuleMatchSchema = z.enum([ + "CIDR", + "IP", + "PATH", + "COUNTRY", + "ASN", + "REGION" +]); + // Define Zod schema for request body validation const updateResourceRuleSchema = z .strictObject({ action: z.enum(["ACCEPT", "DROP", "PASS"]).optional(), - match: z - .enum(["CIDR", "IP", "PATH", "COUNTRY", "ASN", "REGION"]) - .optional(), + match: resourceRuleMatchSchema.optional(), value: z.string().min(1).optional(), priority: z.int(), enabled: z.boolean().optional() @@ -123,37 +130,102 @@ export async function updateResourceRule( return next( createHttpError( HttpCode.BAD_REQUEST, - "Cannot create rule for non-http resource" + "Cannot update rule for non-http resource" ) ); } - // Verify that the rule exists and belongs to the specified resource - const [existingRule] = await db - .select() - .from(resourceRules) - .where(eq(resourceRules.ruleId, ruleId)) - .limit(1); + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; - if (!existingRule) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - `Resource rule with ID ${ruleId} not found` - ) + let existingMatch: + | "CIDR" + | "IP" + | "PATH" + | "COUNTRY" + | "ASN" + | "REGION"; + + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + const [existingRule] = await db + .select() + .from(resourcePolicyRules) + .where(eq(resourcePolicyRules.ruleId, ruleId)) + .limit(1); + + if (!existingRule) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource rule with ID ${ruleId} not found` + ) + ); + } + + if (existingRule.resourcePolicyId !== policyId) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + `Resource rule ${ruleId} does not belong to resource ${resourceId}` + ) + ); + } + + const parsedExistingMatch = resourceRuleMatchSchema.safeParse( + existingRule.match ); + if (!parsedExistingMatch.success) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Resource rule has invalid match type" + ) + ); + } + existingMatch = parsedExistingMatch.data; + } else { + // Verify that the rule exists and belongs to the specified resource + const [existingRule] = await db + .select() + .from(resourceRules) + .where(eq(resourceRules.ruleId, ruleId)) + .limit(1); + + if (!existingRule) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource rule with ID ${ruleId} not found` + ) + ); + } + + if (existingRule.resourceId !== resourceId) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + `Resource rule ${ruleId} does not belong to resource ${resourceId}` + ) + ); + } + + const parsedExistingMatch = resourceRuleMatchSchema.safeParse( + existingRule.match + ); + if (!parsedExistingMatch.success) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Resource rule has invalid match type" + ) + ); + } + existingMatch = parsedExistingMatch.data; } - if (existingRule.resourceId !== resourceId) { - return next( - createHttpError( - HttpCode.FORBIDDEN, - `Resource rule ${ruleId} does not belong to resource ${resourceId}` - ) - ); - } - - const match = updateData.match || existingRule.match; + const match = updateData.match || existingMatch; const { value } = updateData; if (value !== undefined) { @@ -197,11 +269,30 @@ export async function updateResourceRule( } // Update the rule - const [updatedRule] = await db - .update(resourceRules) - .set(updateData) - .where(eq(resourceRules.ruleId, ruleId)) - .returning(); + const [updatedRule] = isInlinePolicy + ? await db + .update(resourcePolicyRules) + .set(updateData) + .where( + and( + eq(resourcePolicyRules.ruleId, ruleId), + eq( + resourcePolicyRules.resourcePolicyId, + resource.defaultResourcePolicyId! + ) + ) + ) + .returning() + : await db + .update(resourceRules) + .set(updateData) + .where( + and( + eq(resourceRules.ruleId, ruleId), + eq(resourceRules.resourceId, resourceId) + ) + ) + .returning(); return response(res, { data: updatedRule,