rules server validation, enabled toggle, fix wildcard

This commit is contained in:
Milo Schwartz 2025-02-11 23:59:13 -05:00
parent f14ecf50e4
commit fdf1dfdeba
No known key found for this signature in database
13 changed files with 467 additions and 196 deletions

View file

@ -17,7 +17,7 @@ import { eq, and } from "drizzle-orm";
import stoi from "@server/lib/stoi";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
import { subdomainSchema } from "@server/lib/schemas";
import config from "@server/lib/config";
const createResourceParamsSchema = z

View file

@ -8,12 +8,19 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import {
isValidCIDR,
isValidIP,
isValidUrlGlobPattern
} from "@server/lib/validators";
const createResourceRuleSchema = z
.object({
action: z.enum(["ACCEPT", "DROP"]),
match: z.enum(["CIDR", "IP", "PATH"]),
value: z.string().min(1)
value: z.string().min(1),
priority: z.number().int(),
enabled: z.boolean().optional()
})
.strict();
@ -42,7 +49,7 @@ export async function createResourceRule(
);
}
const { action, match, value } = parsedBody.data;
const { action, match, value, priority, enabled } = parsedBody.data;
const parsedParams = createResourceRuleParamsSchema.safeParse(
req.params
@ -74,6 +81,41 @@ export async function createResourceRule(
);
}
if (!resource.http) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Cannot create rule for non-http resource"
)
);
}
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"
)
);
}
}
// Create the new resource rule
const [newRule] = await db
.insert(resourceRules)
@ -81,7 +123,9 @@ export async function createResourceRule(
resourceId,
action,
match,
value
value,
priority,
enabled
})
.returning();

View file

@ -40,12 +40,14 @@ function queryResourceRules(resourceId: number) {
resourceId: resourceRules.resourceId,
action: resourceRules.action,
match: resourceRules.match,
value: resourceRules.value
value: resourceRules.value,
priority: resourceRules.priority,
enabled: resourceRules.enabled
})
.from(resourceRules)
.leftJoin(resources, eq(resourceRules.resourceId, resources.resourceId))
.where(eq(resourceRules.resourceId, resourceId));
return baseQuery;
}
@ -71,7 +73,9 @@ export async function listResourceRules(
}
const { limit, offset } = parsedQuery.data;
const parsedParams = listResourceRulesParamsSchema.safeParse(req.params);
const parsedParams = listResourceRulesParamsSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
@ -99,16 +103,19 @@ export async function listResourceRules(
}
const baseQuery = queryResourceRules(resourceId);
let countQuery = db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId));
const rulesList = await baseQuery.limit(limit).offset(offset);
let rulesList = await baseQuery.limit(limit).offset(offset);
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
// sort rules list by the priority in ascending order
rulesList = rulesList.sort((a, b) => a.priority - b.priority);
return response<ListResourceRulesResponse>(res, {
data: {
rules: rulesList,
@ -129,4 +136,4 @@ export async function listResourceRules(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}
}

View file

@ -8,8 +8,8 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
import config from "@server/lib/config";
import { subdomainSchema } from "@server/lib/schemas";
const updateResourceParamsSchema = z
.object({

View file

@ -8,14 +8,16 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import {
isValidCIDR,
isValidIP,
isValidUrlGlobPattern
} from "@server/lib/validators";
// Define Zod schema for request parameters validation
const updateResourceRuleParamsSchema = z
.object({
ruleId: z
.string()
.transform(Number)
.pipe(z.number().int().positive()),
ruleId: z.string().transform(Number).pipe(z.number().int().positive()),
resourceId: z
.string()
.transform(Number)
@ -28,7 +30,9 @@ const updateResourceRuleSchema = z
.object({
action: z.enum(["ACCEPT", "DROP"]).optional(),
match: z.enum(["CIDR", "IP", "PATH"]).optional(),
value: z.string().min(1).optional()
value: z.string().min(1).optional(),
priority: z.number().int(),
enabled: z.boolean().optional()
})
.strict()
.refine((data) => Object.keys(data).length > 0, {
@ -42,7 +46,9 @@ export async function updateResourceRule(
): Promise<any> {
try {
// Validate path parameters
const parsedParams = updateResourceRuleParamsSchema.safeParse(req.params);
const parsedParams = updateResourceRuleParamsSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
@ -82,6 +88,15 @@ export async function updateResourceRule(
);
}
if (!resource.http) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Cannot create rule for non-http resource"
)
);
}
// Verify that the rule exists and belongs to the specified resource
const [existingRule] = await db
.select()
@ -107,6 +122,40 @@ export async function updateResourceRule(
);
}
const match = updateData.match || existingRule.match;
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"
)
);
}
}
}
// Update the rule
const [updatedRule] = await db
.update(resourceRules)
@ -127,4 +176,4 @@ export async function updateResourceRule(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}
}