Use the policy when updating rule

Fixes #3273
This commit is contained in:
Owen
2026-06-23 11:25:09 -04:00
parent 19faa3a29c
commit 8004ae6870
+124 -33
View File
@@ -1,8 +1,8 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { resourceRules, resources } from "@server/db"; import { resourcePolicyRules, resourceRules, resources } from "@server/db";
import { eq } from "drizzle-orm"; import { and, eq } 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";
@@ -22,13 +22,20 @@ const updateResourceRuleParamsSchema = z.strictObject({
resourceId: z.coerce.number().int().positive() resourceId: z.coerce.number().int().positive()
}); });
const resourceRuleMatchSchema = z.enum([
"CIDR",
"IP",
"PATH",
"COUNTRY",
"ASN",
"REGION"
]);
// Define Zod schema for request body validation // Define Zod schema for request body validation
const updateResourceRuleSchema = z const updateResourceRuleSchema = z
.strictObject({ .strictObject({
action: z.enum(["ACCEPT", "DROP", "PASS"]).optional(), action: z.enum(["ACCEPT", "DROP", "PASS"]).optional(),
match: z match: resourceRuleMatchSchema.optional(),
.enum(["CIDR", "IP", "PATH", "COUNTRY", "ASN", "REGION"])
.optional(),
value: z.string().min(1).optional(), value: z.string().min(1).optional(),
priority: z.int(), priority: z.int(),
enabled: z.boolean().optional() enabled: z.boolean().optional()
@@ -123,37 +130,102 @@ export async function updateResourceRule(
return next( return next(
createHttpError( createHttpError(
HttpCode.BAD_REQUEST, 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 isInlinePolicy =
const [existingRule] = await db resource.resourcePolicyId === null &&
.select() resource.defaultResourcePolicyId !== null;
.from(resourceRules)
.where(eq(resourceRules.ruleId, ruleId))
.limit(1);
if (!existingRule) { let existingMatch:
return next( | "CIDR"
createHttpError( | "IP"
HttpCode.NOT_FOUND, | "PATH"
`Resource rule with ID ${ruleId} not found` | "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) { const match = updateData.match || existingMatch;
return next(
createHttpError(
HttpCode.FORBIDDEN,
`Resource rule ${ruleId} does not belong to resource ${resourceId}`
)
);
}
const match = updateData.match || existingRule.match;
const { value } = updateData; const { value } = updateData;
if (value !== undefined) { if (value !== undefined) {
@@ -197,11 +269,30 @@ export async function updateResourceRule(
} }
// Update the rule // Update the rule
const [updatedRule] = await db const [updatedRule] = isInlinePolicy
.update(resourceRules) ? await db
.set(updateData) .update(resourcePolicyRules)
.where(eq(resourceRules.ruleId, ruleId)) .set(updateData)
.returning(); .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, { return response(res, {
data: updatedRule, data: updatedRule,