Compare commits

..

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
0bde633c5f chore: simplify policy rule update/delete lookups 2026-06-16 23:52:22 +00:00
copilot-swe-agent[bot]
a7c99f336f refactor: dedupe resource rule value validation 2026-06-16 23:50:38 +00:00
copilot-swe-agent[bot]
0d960181a2 fix: update resource rule routes to use shared policy rules 2026-06-16 23:48:46 +00:00
copilot-swe-agent[bot]
b6862093d1 Initial plan 2026-06-16 23:43:34 +00:00
7 changed files with 454 additions and 3041 deletions

3281
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,26 +44,26 @@
"@oslojs/crypto": "1.0.1",
"@oslojs/encoding": "1.1.0",
"@radix-ui/react-avatar": "1.1.11",
"@radix-ui/react-checkbox": "1.3.5",
"@radix-ui/react-collapsible": "1.1.14",
"@radix-ui/react-dialog": "1.1.17",
"@radix-ui/react-dropdown-menu": "2.1.18",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",
"@radix-ui/react-dialog": "1.1.15",
"@radix-ui/react-dropdown-menu": "2.1.16",
"@radix-ui/react-icons": "1.3.2",
"@radix-ui/react-label": "2.1.10",
"@radix-ui/react-popover": "1.1.17",
"@radix-ui/react-progress": "1.1.10",
"@radix-ui/react-label": "2.1.8",
"@radix-ui/react-popover": "1.1.15",
"@radix-ui/react-progress": "1.1.8",
"@radix-ui/react-radio-group": "1.3.8",
"@radix-ui/react-scroll-area": "1.2.12",
"@radix-ui/react-scroll-area": "1.2.10",
"@radix-ui/react-select": "2.2.6",
"@radix-ui/react-separator": "1.1.10",
"@radix-ui/react-separator": "1.1.8",
"@radix-ui/react-slot": "1.2.4",
"@radix-ui/react-switch": "1.2.6",
"@radix-ui/react-tabs": "1.1.15",
"@radix-ui/react-toast": "1.2.17",
"@radix-ui/react-tooltip": "1.2.10",
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toast": "1.2.15",
"@radix-ui/react-tooltip": "1.2.8",
"@react-email/body": "0.3.0",
"@react-email/components": "1.0.12",
"@react-email/render": "2.0.9",
"@react-email/render": "2.0.8",
"@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "13.3.0",
"@simplewebauthn/server": "13.3.1",
@@ -91,14 +91,14 @@
"helmet": "8.2.0",
"http-errors": "2.0.1",
"input-otp": "1.4.2",
"ioredis": "5.11.1",
"ioredis": "5.11.0",
"jmespath": "0.16.0",
"js-yaml": "4.1.1",
"jsonwebtoken": "9.0.3",
"lucide-react": "1.17.0",
"maxmind": "5.0.6",
"moment": "2.30.1",
"next": "16.2.9",
"next": "16.2.6",
"next-intl": "4.13.0",
"next-themes": "0.4.6",
"nextjs-toploader": "3.9.17",
@@ -108,17 +108,17 @@
"pg": "8.21.0",
"posthog-node": "5.35.6",
"qrcode.react": "4.2.0",
"react": "19.2.7",
"react": "19.2.6",
"react-day-picker": "9.14.0",
"react-dom": "19.2.7",
"react-dom": "19.2.6",
"react-easy-sort": "1.8.0",
"react-hook-form": "7.76.1",
"react-icons": "5.6.0",
"recharts": "3.8.1",
"reodotdev": "1.1.0",
"semver": "7.8.4",
"semver": "7.8.1",
"sshpk": "1.18.0",
"stripe": "22.2.1",
"stripe": "22.2.0",
"swagger-ui-express": "5.0.1",
"tailwind-merge": "3.6.0",
"topojson-client": "3.1.0",
@@ -155,7 +155,7 @@
"@types/nodemailer": "8.0.0",
"@types/nprogress": "0.2.3",
"@types/pg": "8.20.0",
"@types/react": "19.2.17",
"@types/react": "19.2.15",
"@types/react-dom": "19.2.3",
"@types/semver": "7.7.1",
"@types/sshpk": "1.17.4",

View File

@@ -154,12 +154,8 @@ export async function createResourceRule(
}
// Create the new resource rule
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
if (isInlinePolicy) {
const policyId = resource.defaultResourcePolicyId!;
if (resource.resourcePolicyId !== null) {
const policyId = resource.resourcePolicyId;
const [newRule] = await db
.insert(resourcePolicyRules)
.values({

View File

@@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { resourceRules, resourcePolicyRules, resources } from "@server/db";
import { eq } from "drizzle-orm";
import { and, eq } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
@@ -73,14 +73,18 @@ export async function deleteResourceRule(
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
if (isInlinePolicy) {
if (resource.resourcePolicyId !== null) {
const [deletedRule] = await db
.delete(resourcePolicyRules)
.where(eq(resourcePolicyRules.ruleId, ruleId))
.where(
and(
eq(resourcePolicyRules.ruleId, ruleId),
eq(
resourcePolicyRules.resourcePolicyId,
resource.resourcePolicyId
)
)
)
.returning();
if (!deletedRule) {

View File

@@ -141,16 +141,10 @@ export async function getResource(
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
let returnData = resource;
if (isInlinePolicy) {
if (resource.resourcePolicyId !== null) {
// get the policy
const policy = await queryInlinePolicy(
resource.defaultResourcePolicyId!
);
const policy = await queryInlinePolicy(resource.resourcePolicyId);
returnData = {
...returnData,
sso: policy?.sso || null,

View File

@@ -140,15 +140,11 @@ export async function listResourceRules(
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
let rulesList: Awaited<ReturnType<typeof queryResourceRules>>;
let totalCount: number;
if (isInlinePolicy) {
const policyId = resource.defaultResourcePolicyId!;
if (resource.resourcePolicyId !== null) {
const policyId = resource.resourcePolicyId;
const policyRules = await queryPolicyRules(policyId)
.limit(limit)
.offset(offset);

View File

@@ -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";
@@ -37,6 +37,29 @@ const updateResourceRuleSchema = z
error: "At least one field must be provided for update"
});
function getRuleValueValidationError(
match: "CIDR" | "IP" | "PATH" | "COUNTRY" | "ASN" | "REGION",
value: string
): string | null {
if (match === "CIDR" && !isValidCIDR(value)) {
return "Invalid CIDR provided";
}
if (match === "IP" && !isValidIP(value)) {
return "Invalid IP provided";
}
if (match === "PATH" && !isValidUrlGlobPattern(value)) {
return "Invalid URL glob pattern provided";
}
if (match === "REGION" && !isValidRegionId(value)) {
return "Invalid region ID provided";
}
return null;
}
registry.registerPath({
method: "post",
path: "/resource/{resourceId}/rule/{ruleId}",
@@ -128,6 +151,68 @@ export async function updateResourceRule(
);
}
if (resource.resourcePolicyId !== null) {
const [existingRule] = await db
.select()
.from(resourcePolicyRules)
.where(
and(
eq(resourcePolicyRules.ruleId, ruleId),
eq(
resourcePolicyRules.resourcePolicyId,
resource.resourcePolicyId
)
)
)
.limit(1);
if (!existingRule) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource rule with ID ${ruleId} not found`
)
);
}
const match = updateData.match || existingRule.match;
const { value } = updateData;
if (value !== undefined) {
const validationError = getRuleValueValidationError(
match,
value
);
if (validationError) {
return next(
createHttpError(HttpCode.BAD_REQUEST, validationError)
);
}
}
const [updatedRule] = await db
.update(resourcePolicyRules)
.set(updateData)
.where(
and(
eq(resourcePolicyRules.ruleId, ruleId),
eq(
resourcePolicyRules.resourcePolicyId,
resource.resourcePolicyId
)
)
)
.returning();
return response(res, {
data: updatedRule,
success: true,
error: false,
message: "Resource rule updated successfully",
status: HttpCode.OK
});
}
// Verify that the rule exists and belongs to the specified resource
const [existingRule] = await db
.select()
@@ -157,42 +242,11 @@ export async function updateResourceRule(
const { value } = updateData;
if (value !== undefined) {
if (match === "CIDR") {
if (!isValidCIDR(value)) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid CIDR provided"
)
);
}
} else if (match === "IP") {
if (!isValidIP(value)) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid IP provided"
)
);
}
} else if (match === "PATH") {
if (!isValidUrlGlobPattern(value)) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid URL glob pattern provided"
)
);
}
} else if (match === "REGION") {
if (!isValidRegionId(value)) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid region ID provided"
)
);
}
const validationError = getRuleValueValidationError(match, value);
if (validationError) {
return next(
createHttpError(HttpCode.BAD_REQUEST, validationError)
);
}
}