mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-18 08:18:43 +02:00
Add validation
This commit is contained in:
parent
4a6da91faf
commit
42434ca832
2 changed files with 138 additions and 22 deletions
|
@ -480,7 +480,7 @@ async function checkRules(
|
|||
return rule.action == "ACCEPT";
|
||||
} else if (path && rule.match == "PATH") {
|
||||
// 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)) {
|
||||
return rule.action == "ACCEPT";
|
||||
}
|
||||
|
@ -489,3 +489,19 @@ async function checkRules(
|
|||
|
||||
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}$`);
|
||||
}
|
|
@ -93,9 +93,9 @@ export default function ResourceRules(props: {
|
|||
useEffect(() => {
|
||||
const fetchRules = async () => {
|
||||
try {
|
||||
const res = await api.get<AxiosResponse<ListResourceRulesResponse>>(
|
||||
`/resource/${params.resourceId}/rules`
|
||||
);
|
||||
const res = await api.get<
|
||||
AxiosResponse<ListResourceRulesResponse>
|
||||
>(`/resource/${params.resourceId}/rules`);
|
||||
if (res.status === 200) {
|
||||
setRules(res.data.data.rules);
|
||||
}
|
||||
|
@ -133,6 +133,25 @@ export default function ResourceRules(props: {
|
|||
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 = {
|
||||
...data,
|
||||
ruleId: new Date().getTime(),
|
||||
|
@ -171,11 +190,27 @@ export default function ResourceRules(props: {
|
|||
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) {
|
||||
await api.put(
|
||||
`/resource/${params.resourceId}/rule`,
|
||||
data
|
||||
);
|
||||
await api.put(`/resource/${params.resourceId}/rule`, data);
|
||||
} else if (rule.updated) {
|
||||
await api.post(
|
||||
`/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([]);
|
||||
|
||||
toast({
|
||||
|
@ -322,7 +359,9 @@ export default function ResourceRules(props: {
|
|||
<FormControl>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
onValueChange={
|
||||
field.onChange
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
|
@ -350,7 +389,9 @@ export default function ResourceRules(props: {
|
|||
<FormControl>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
onValueChange={
|
||||
field.onChange
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
|
@ -380,7 +421,8 @@ export default function ResourceRules(props: {
|
|||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
Enter CIDR or path value based on match type
|
||||
Enter CIDR or path value based
|
||||
on match type
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
|
@ -401,7 +443,8 @@ export default function ResourceRules(props: {
|
|||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.column
|
||||
.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
|
@ -413,10 +456,13 @@ export default function ResourceRules(props: {
|
|||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
{row
|
||||
.getVisibleCells()
|
||||
.map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.column
|
||||
.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
|
@ -450,3 +496,57 @@ export default function ResourceRules(props: {
|
|||
</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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue