Add validation

This commit is contained in:
Owen 2025-02-08 17:54:01 -05:00
parent 4a6da91faf
commit 42434ca832
No known key found for this signature in database
GPG key ID: 8271FDFFD9E0CCBD
2 changed files with 138 additions and 22 deletions

View file

@ -480,7 +480,7 @@ async function checkRules(
return rule.action == "ACCEPT"; return rule.action == "ACCEPT";
} else if (path && rule.match == "PATH") { } else if (path && rule.match == "PATH") {
// rule.value is a regex, match on the path and see if it matches // rule.value is a regex, match on the path and see if it matches
const re = new RegExp(rule.value); const re = urlGlobToRegex(rule.value);
if (re.test(path)) { if (re.test(path)) {
return rule.action == "ACCEPT"; return rule.action == "ACCEPT";
} }
@ -489,3 +489,19 @@ async function checkRules(
return false; return false;
} }
function urlGlobToRegex(pattern: string): RegExp {
// Remove leading slash if present (we'll add it to the regex pattern)
pattern = pattern.startsWith('/') ? pattern.slice(1) : pattern;
// Escape special regex characters except *
const escapedPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
// Replace * with regex pattern for any valid URL segment characters
const regexPattern = escapedPattern.replace(/\*/g, '[a-zA-Z0-9_-]+');
// Create the final pattern that:
// 1. Optionally matches leading slash
// 2. Matches the entire string
return new RegExp(`^/?${regexPattern}$`);
}

View file

@ -93,9 +93,9 @@ export default function ResourceRules(props: {
useEffect(() => { useEffect(() => {
const fetchRules = async () => { const fetchRules = async () => {
try { try {
const res = await api.get<AxiosResponse<ListResourceRulesResponse>>( const res = await api.get<
`/resource/${params.resourceId}/rules` AxiosResponse<ListResourceRulesResponse>
); >(`/resource/${params.resourceId}/rules`);
if (res.status === 200) { if (res.status === 200) {
setRules(res.data.data.rules); setRules(res.data.data.rules);
} }
@ -133,6 +133,25 @@ export default function ResourceRules(props: {
return; return;
} }
if (data.match === "CIDR" && !isValidCIDR(data.value)) {
toast({
variant: "destructive",
title: "Invalid CIDR",
description: "Please enter a valid CIDR value"
});
setLoading(false);
return;
}
if (data.match === "PATH" && !isValidUrlGlobPattern(data.value)) {
toast({
variant: "destructive",
title: "Invalid URL path",
description: "Please enter a valid URL path value"
});
setLoading(false);
return;
}
const newRule: LocalRule = { const newRule: LocalRule = {
...data, ...data,
ruleId: new Date().getTime(), ruleId: new Date().getTime(),
@ -171,11 +190,27 @@ export default function ResourceRules(props: {
value: rule.value value: rule.value
}; };
if (rule.match === "CIDR" && !isValidCIDR(rule.value)) {
toast({
variant: "destructive",
title: "Invalid CIDR",
description: "Please enter a valid CIDR value"
});
setLoading(false);
return;
}
if (rule.match === "PATH" && !isValidUrlGlobPattern(rule.value)) {
toast({
variant: "destructive",
title: "Invalid URL path",
description: "Please enter a valid URL path value"
});
setLoading(false);
return;
}
if (rule.new) { if (rule.new) {
await api.put( await api.put(`/resource/${params.resourceId}/rule`, data);
`/resource/${params.resourceId}/rule`,
data
);
} else if (rule.updated) { } else if (rule.updated) {
await api.post( await api.post(
`/resource/${params.resourceId}/rule/${rule.ruleId}`, `/resource/${params.resourceId}/rule/${rule.ruleId}`,
@ -190,7 +225,9 @@ export default function ResourceRules(props: {
); );
} }
setRules(rules.map(rule => ({ ...rule, new: false, updated: false }))); setRules(
rules.map((rule) => ({ ...rule, new: false, updated: false }))
);
setRulesToRemove([]); setRulesToRemove([]);
toast({ toast({
@ -322,7 +359,9 @@ export default function ResourceRules(props: {
<FormControl> <FormControl>
<Select <Select
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={
field.onChange
}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@ -350,7 +389,9 @@ export default function ResourceRules(props: {
<FormControl> <FormControl>
<Select <Select
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={
field.onChange
}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@ -380,7 +421,8 @@ export default function ResourceRules(props: {
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
Enter CIDR or path value based on match type Enter CIDR or path value based
on match type
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@ -401,7 +443,8 @@ export default function ResourceRules(props: {
{header.isPlaceholder {header.isPlaceholder
? null ? null
: flexRender( : flexRender(
header.column.columnDef.header, header.column
.columnDef.header,
header.getContext() header.getContext()
)} )}
</TableHead> </TableHead>
@ -413,14 +456,17 @@ export default function ResourceRules(props: {
{table.getRowModel().rows?.length ? ( {table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => ( table.getRowModel().rows.map((row) => (
<TableRow key={row.id}> <TableRow key={row.id}>
{row.getVisibleCells().map((cell) => ( {row
<TableCell key={cell.id}> .getVisibleCells()
{flexRender( .map((cell) => (
cell.column.columnDef.cell, <TableCell key={cell.id}>
cell.getContext() {flexRender(
)} cell.column
</TableCell> .columnDef.cell,
))} cell.getContext()
)}
</TableCell>
))}
</TableRow> </TableRow>
)) ))
) : ( ) : (
@ -449,4 +495,58 @@ export default function ResourceRules(props: {
</SettingsSection> </SettingsSection>
</SettingsContainer> </SettingsContainer>
); );
} }
function isValidCIDR(cidr: string): boolean {
// Match CIDR pattern (e.g., "192.168.0.0/24")
const cidrPattern =
/^([0-9]{1,3}\.){3}[0-9]{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/;
if (!cidrPattern.test(cidr)) {
return false;
}
// Validate IP address part
const ipPart = cidr.split("/")[0];
const octets = ipPart.split(".");
return octets.every((octet) => {
const num = parseInt(octet, 10);
return num >= 0 && num <= 255;
});
}
function isValidUrlGlobPattern(pattern: string): boolean {
// Remove leading slash if present
pattern = pattern.startsWith('/') ? pattern.slice(1) : pattern;
// Empty string is not valid
if (!pattern) {
return false;
}
// Split path into segments
const segments = pattern.split('/');
// Check each segment
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
// Empty segments are not allowed (double slashes)
if (!segment && i !== segments.length - 1) {
return false;
}
// If segment contains *, it must be exactly *
if (segment.includes('*') && segment !== '*') {
return false;
}
// Check for invalid characters
if (!/^[a-zA-Z0-9_*-]*$/.test(segment)) {
return false;
}
}
return true;
}