mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-04 19:51:29 +02:00
first pass restyle of auth methods and rules
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
import { COUNTRIES } from "@server/db/countries";
|
||||
import { isValidRegionId } from "@server/db/regions";
|
||||
import {
|
||||
isValidCIDR,
|
||||
isValidIP,
|
||||
isValidUrlGlobPattern
|
||||
} from "@server/lib/validators";
|
||||
import z from "zod";
|
||||
|
||||
type TranslateFn = (
|
||||
key: string,
|
||||
values?: Record<string, string | number>
|
||||
) => string;
|
||||
|
||||
export type RuleValidationToast = {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function getPolicyRuleValidationMessage(
|
||||
t: TranslateFn,
|
||||
issue: z.core.$ZodIssue
|
||||
): string {
|
||||
const ruleIndex = issue.path.find((segment) => typeof segment === "number");
|
||||
if (typeof ruleIndex === "number") {
|
||||
return t("rulesErrorValidationRuleDescription", {
|
||||
ruleNumber: ruleIndex + 1,
|
||||
message: issue.message
|
||||
});
|
||||
}
|
||||
return issue.message;
|
||||
}
|
||||
|
||||
export function createPolicyRulePrioritySchema(t: TranslateFn) {
|
||||
return z.coerce
|
||||
.number({ error: t("rulesErrorInvalidPriorityDescription") })
|
||||
.int({ message: t("rulesErrorInvalidPriorityDescription") })
|
||||
.min(1, { message: t("rulesErrorInvalidPriorityDescription") });
|
||||
}
|
||||
|
||||
export function createPolicyRuleValueSchema(t: TranslateFn, match: string) {
|
||||
const required = z
|
||||
.string()
|
||||
.min(1, { message: t("rulesErrorValueRequired") });
|
||||
|
||||
switch (match) {
|
||||
case "CIDR":
|
||||
return required.refine(isValidCIDR, {
|
||||
message: t("rulesErrorInvalidIpAddressRangeDescription")
|
||||
});
|
||||
case "IP":
|
||||
return required.refine(isValidIP, {
|
||||
message: t("rulesErrorInvalidIpAddressDescription")
|
||||
});
|
||||
case "PATH":
|
||||
return required.refine(isValidUrlGlobPattern, {
|
||||
message: t("rulesErrorInvalidUrlDescription")
|
||||
});
|
||||
case "REGION":
|
||||
return required.refine(isValidRegionId, {
|
||||
message: t("rulesErrorInvalidRegionDescription")
|
||||
});
|
||||
case "COUNTRY":
|
||||
return required.refine(
|
||||
(value) => COUNTRIES.some((country) => country.code === value),
|
||||
{ message: t("rulesErrorInvalidCountryDescription") }
|
||||
);
|
||||
case "ASN":
|
||||
return required.refine((value) => /^AS\d+$/i.test(value.trim()), {
|
||||
message: t("rulesErrorInvalidAsnDescription")
|
||||
});
|
||||
default:
|
||||
return required;
|
||||
}
|
||||
}
|
||||
|
||||
export function createPolicyRuleSchema(t: TranslateFn) {
|
||||
return z
|
||||
.object({
|
||||
action: z.enum(["ACCEPT", "DROP", "PASS"]),
|
||||
match: z.string(),
|
||||
value: z.string(),
|
||||
priority: z.number().int(),
|
||||
enabled: z.boolean()
|
||||
})
|
||||
.superRefine((rule, ctx) => {
|
||||
const priorityResult = createPolicyRulePrioritySchema(t).safeParse(
|
||||
rule.priority
|
||||
);
|
||||
if (!priorityResult.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
priorityResult.error.issues[0]?.message ??
|
||||
t("rulesErrorInvalidPriorityDescription"),
|
||||
path: ["priority"]
|
||||
});
|
||||
}
|
||||
|
||||
const valueResult = createPolicyRuleValueSchema(
|
||||
t,
|
||||
rule.match
|
||||
).safeParse(rule.value);
|
||||
if (!valueResult.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
valueResult.error.issues[0]?.message ??
|
||||
t("rulesErrorValueRequired"),
|
||||
path: ["value"]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function createPolicyRulesArraySchema(t: TranslateFn) {
|
||||
return z.array(createPolicyRuleSchema(t)).superRefine((rules, ctx) => {
|
||||
const seenPriorities = new Set<number>();
|
||||
rules.forEach((rule, index) => {
|
||||
if (seenPriorities.has(rule.priority)) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: t("rulesErrorDuplicatePriorityDescription"),
|
||||
path: [index, "priority"]
|
||||
});
|
||||
}
|
||||
seenPriorities.add(rule.priority);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function createPolicyRulesSectionSchema(t: TranslateFn) {
|
||||
return z.object({
|
||||
applyRules: z.boolean(),
|
||||
rules: createPolicyRulesArraySchema(t)
|
||||
});
|
||||
}
|
||||
|
||||
export function createPolicySchemaWithI18n(
|
||||
t: TranslateFn,
|
||||
baseSchema: z.ZodObject<z.ZodRawShape>
|
||||
) {
|
||||
return baseSchema.extend({
|
||||
rules: createPolicyRulesArraySchema(t)
|
||||
});
|
||||
}
|
||||
|
||||
export function validatePolicyRulePriority(
|
||||
t: TranslateFn,
|
||||
value: unknown
|
||||
):
|
||||
| { success: true; data: number }
|
||||
| { success: false; toast: RuleValidationToast } {
|
||||
const result = createPolicyRulePrioritySchema(t).safeParse(value);
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data };
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
toast: {
|
||||
title: t("rulesErrorInvalidPriority"),
|
||||
description:
|
||||
result.error.issues[0]?.message ??
|
||||
t("rulesErrorInvalidPriorityDescription")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function validatePolicyRuleValue(
|
||||
t: TranslateFn,
|
||||
match: string,
|
||||
value: string
|
||||
):
|
||||
| { success: true; data: string }
|
||||
| { success: false; toast: RuleValidationToast } {
|
||||
const result = createPolicyRuleValueSchema(t, match).safeParse(value);
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data };
|
||||
}
|
||||
|
||||
const issue = result.error.issues[0];
|
||||
const titleKey =
|
||||
match === "CIDR"
|
||||
? "rulesErrorInvalidIpAddressRange"
|
||||
: match === "IP"
|
||||
? "rulesErrorInvalidIpAddress"
|
||||
: match === "PATH"
|
||||
? "rulesErrorInvalidUrl"
|
||||
: match === "REGION"
|
||||
? "rulesErrorInvalidRegion"
|
||||
: match === "COUNTRY"
|
||||
? "rulesErrorInvalidCountry"
|
||||
: match === "ASN"
|
||||
? "rulesErrorInvalidAsn"
|
||||
: "rulesErrorValidation";
|
||||
|
||||
return {
|
||||
success: false,
|
||||
toast: {
|
||||
title: t(titleKey),
|
||||
description: issue?.message ?? t("rulesErrorValueRequired")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function validatePolicyRulesForSave(
|
||||
t: TranslateFn,
|
||||
rules: Array<{
|
||||
action: "ACCEPT" | "DROP" | "PASS";
|
||||
match: string;
|
||||
value: string;
|
||||
priority: number;
|
||||
enabled: boolean;
|
||||
}>,
|
||||
applyRules: boolean
|
||||
): { success: true } | { success: false; toast: RuleValidationToast } {
|
||||
if (!applyRules) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const result = createPolicyRulesArraySchema(t).safeParse(rules);
|
||||
if (result.success) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const issue = result.error.issues[0];
|
||||
return {
|
||||
success: false,
|
||||
toast: {
|
||||
title: t("rulesErrorValidation"),
|
||||
description: issue
|
||||
? getPolicyRuleValidationMessage(t, issue)
|
||||
: t("rulesErrorUpdateDescription")
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user