mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-18 03:26:34 +02:00
Merge pull request #3342 from Fredkiss3/feat/geoip-country-is-not-rule
feat: add country `is not` rule in resource policies
This commit is contained in:
@@ -2543,6 +2543,7 @@
|
|||||||
"noRemoteExitNodesAvailableDescription": "No nodes are available for this organization. Create a node first to use local sites.",
|
"noRemoteExitNodesAvailableDescription": "No nodes are available for this organization. Create a node first to use local sites.",
|
||||||
"exitNode": "Exit Node",
|
"exitNode": "Exit Node",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
|
"countryIsNot": "Country Is Not",
|
||||||
"rulesMatchCountry": "Currently based on source IP",
|
"rulesMatchCountry": "Currently based on source IP",
|
||||||
"region": "Region",
|
"region": "Region",
|
||||||
"selectRegion": "Select region",
|
"selectRegion": "Select region",
|
||||||
|
|||||||
@@ -999,7 +999,17 @@ export const resourceRules = pgTable("resourceRules", {
|
|||||||
enabled: boolean("enabled").notNull().default(true),
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
priority: integer("priority").notNull(),
|
priority: integer("priority").notNull(),
|
||||||
action: varchar("action").notNull(), // ACCEPT, DROP, PASS
|
action: varchar("action").notNull(), // ACCEPT, DROP, PASS
|
||||||
match: varchar("match").notNull(), // CIDR, PATH, IP
|
match: varchar("match")
|
||||||
|
.$type<
|
||||||
|
| "CIDR"
|
||||||
|
| "PATH"
|
||||||
|
| "IP"
|
||||||
|
| "COUNTRY"
|
||||||
|
| "COUNTRY_IS_NOT"
|
||||||
|
| "ASN"
|
||||||
|
| "REGION"
|
||||||
|
>()
|
||||||
|
.notNull(), // CIDR, PATH, IP
|
||||||
value: varchar("value").notNull()
|
value: varchar("value").notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1014,7 +1024,15 @@ export const resourcePolicyRules = pgTable("resourcePolicyRules", {
|
|||||||
priority: integer("priority").notNull(),
|
priority: integer("priority").notNull(),
|
||||||
action: varchar("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
|
action: varchar("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
|
||||||
match: varchar("match")
|
match: varchar("match")
|
||||||
.$type<"CIDR" | "PATH" | "IP" | "COUNTRY" | "ASN" | "REGION">()
|
.$type<
|
||||||
|
| "CIDR"
|
||||||
|
| "PATH"
|
||||||
|
| "IP"
|
||||||
|
| "COUNTRY"
|
||||||
|
| "COUNTRY_IS_NOT"
|
||||||
|
| "ASN"
|
||||||
|
| "REGION"
|
||||||
|
>()
|
||||||
.notNull(),
|
.notNull(),
|
||||||
value: varchar("value").notNull()
|
value: varchar("value").notNull()
|
||||||
});
|
});
|
||||||
@@ -1594,3 +1612,4 @@ export type LauncherView = InferSelectModel<typeof launcherViews>;
|
|||||||
export type ResourcePolicy = InferSelectModel<typeof resourcePolicies>;
|
export type ResourcePolicy = InferSelectModel<typeof resourcePolicies>;
|
||||||
export type RolePolicy = InferSelectModel<typeof rolePolicies>;
|
export type RolePolicy = InferSelectModel<typeof rolePolicies>;
|
||||||
export type UserPolicy = InferSelectModel<typeof userPolicies>;
|
export type UserPolicy = InferSelectModel<typeof userPolicies>;
|
||||||
|
export type ResourcePolicyRule = InferSelectModel<typeof resourcePolicyRules>;
|
||||||
|
|||||||
@@ -1224,7 +1224,17 @@ export const resourceRules = sqliteTable("resourceRules", {
|
|||||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||||
priority: integer("priority").notNull(),
|
priority: integer("priority").notNull(),
|
||||||
action: text("action").notNull(), // ACCEPT, DROP, PASS
|
action: text("action").notNull(), // ACCEPT, DROP, PASS
|
||||||
match: text("match").notNull(), // CIDR, PATH, IP
|
match: text("match")
|
||||||
|
.$type<
|
||||||
|
| "CIDR"
|
||||||
|
| "PATH"
|
||||||
|
| "IP"
|
||||||
|
| "COUNTRY"
|
||||||
|
| "COUNTRY_IS_NOT"
|
||||||
|
| "ASN"
|
||||||
|
| "REGION"
|
||||||
|
>()
|
||||||
|
.notNull(), // CIDR, PATH, IP
|
||||||
value: text("value").notNull()
|
value: text("value").notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1271,7 +1281,15 @@ export const resourcePolicyRules = sqliteTable("resourcePolicyRules", {
|
|||||||
priority: integer("priority").notNull(),
|
priority: integer("priority").notNull(),
|
||||||
action: text("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
|
action: text("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
|
||||||
match: text("match")
|
match: text("match")
|
||||||
.$type<"CIDR" | "PATH" | "IP" | "COUNTRY" | "ASN" | "REGION">()
|
.$type<
|
||||||
|
| "CIDR"
|
||||||
|
| "PATH"
|
||||||
|
| "IP"
|
||||||
|
| "COUNTRY"
|
||||||
|
| "COUNTRY_IS_NOT"
|
||||||
|
| "ASN"
|
||||||
|
| "REGION"
|
||||||
|
>()
|
||||||
.notNull(),
|
.notNull(),
|
||||||
value: text("value").notNull()
|
value: text("value").notNull()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,56 +1,54 @@
|
|||||||
|
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
||||||
|
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
||||||
|
import { hashPassword } from "@server/auth/password";
|
||||||
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
|
import { build } from "@server/build";
|
||||||
import {
|
import {
|
||||||
domains,
|
|
||||||
domainNamespaces,
|
domainNamespaces,
|
||||||
|
domains,
|
||||||
orgDomains,
|
orgDomains,
|
||||||
Resource,
|
Resource,
|
||||||
resourceHeaderAuth,
|
resourceHeaderAuth,
|
||||||
resourceHeaderAuthExtendedCompatibility,
|
resourceHeaderAuthExtendedCompatibility,
|
||||||
|
resourcePassword,
|
||||||
resourcePincode,
|
resourcePincode,
|
||||||
|
resourcePolicies,
|
||||||
|
resourcePolicyHeaderAuth,
|
||||||
|
resourcePolicyPassword,
|
||||||
|
resourcePolicyPincode,
|
||||||
|
resourcePolicyRules,
|
||||||
|
resourcePolicyWhiteList,
|
||||||
resourceRules,
|
resourceRules,
|
||||||
|
resources,
|
||||||
resourceWhitelist,
|
resourceWhitelist,
|
||||||
roleActions,
|
roleActions,
|
||||||
|
rolePolicies,
|
||||||
roleResources,
|
roleResources,
|
||||||
roles,
|
roles,
|
||||||
|
sites,
|
||||||
Target,
|
Target,
|
||||||
TargetHealthCheck,
|
TargetHealthCheck,
|
||||||
targetHealthCheck,
|
targetHealthCheck,
|
||||||
|
targets,
|
||||||
Transaction,
|
Transaction,
|
||||||
userOrgs,
|
userOrgs,
|
||||||
|
userPolicies,
|
||||||
userResources,
|
userResources,
|
||||||
users,
|
users,
|
||||||
resourcePolicies,
|
type ResourceRule
|
||||||
resourcePolicyPassword,
|
|
||||||
resourcePolicyPincode,
|
|
||||||
resourcePolicyHeaderAuth,
|
|
||||||
resourcePolicyRules,
|
|
||||||
resourcePolicyWhiteList,
|
|
||||||
rolePolicies,
|
|
||||||
userPolicies
|
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { resources, targets, sites } from "@server/db";
|
|
||||||
import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm";
|
|
||||||
import {
|
|
||||||
Config,
|
|
||||||
ConfigSchema,
|
|
||||||
isTargetsOnlyResource,
|
|
||||||
TargetData
|
|
||||||
} from "./types";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
|
||||||
import { pickPort } from "@server/routers/target/helpers";
|
|
||||||
import { resourcePassword } from "@server/db";
|
|
||||||
import { getUniqueResourcePolicyName } from "@server/db/names";
|
import { getUniqueResourcePolicyName } from "@server/db/names";
|
||||||
import { hashPassword } from "@server/auth/password";
|
|
||||||
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
|
||||||
import { isValidRegionId } from "@server/db/regions";
|
import { isValidRegionId } from "@server/db/regions";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
|
||||||
import { fireHealthCheckUnknownAlert } from "@server/lib/alerts";
|
import { fireHealthCheckUnknownAlert } from "@server/lib/alerts";
|
||||||
import { tierMatrix } from "../billing/tierMatrix";
|
|
||||||
import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
|
|
||||||
import { build } from "@server/build";
|
|
||||||
import { encrypt } from "@server/lib/crypto";
|
|
||||||
import { generateId } from "@server/auth/sessions/app";
|
|
||||||
import serverConfig from "@server/lib/config";
|
import serverConfig from "@server/lib/config";
|
||||||
|
import { encrypt } from "@server/lib/crypto";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
|
||||||
|
import { pickPort } from "@server/routers/target/helpers";
|
||||||
|
import { and, asc, eq, isNotNull, ne, or } from "drizzle-orm";
|
||||||
|
import { tierMatrix } from "../billing/tierMatrix";
|
||||||
|
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
||||||
|
import { Config, isTargetsOnlyResource, TargetData } from "./types";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import next from "next";
|
import next from "next";
|
||||||
@@ -909,7 +907,7 @@ export async function updatePublicResources(
|
|||||||
.update(resourceRules)
|
.update(resourceRules)
|
||||||
.set({
|
.set({
|
||||||
action: getRuleAction(rule.action),
|
action: getRuleAction(rule.action),
|
||||||
match: rule.match.toUpperCase(),
|
match: rule.match.toUpperCase() as ResourceRule["match"],
|
||||||
value: getRuleValue(
|
value: getRuleValue(
|
||||||
rule.match.toUpperCase(),
|
rule.match.toUpperCase(),
|
||||||
rule.value
|
rule.value
|
||||||
@@ -928,7 +926,7 @@ export async function updatePublicResources(
|
|||||||
await trx.insert(resourceRules).values({
|
await trx.insert(resourceRules).values({
|
||||||
resourceId: existingResource.resourceId,
|
resourceId: existingResource.resourceId,
|
||||||
action: getRuleAction(rule.action),
|
action: getRuleAction(rule.action),
|
||||||
match: rule.match.toUpperCase(),
|
match: rule.match.toUpperCase() as ResourceRule["match"],
|
||||||
value: getRuleValue(
|
value: getRuleValue(
|
||||||
rule.match.toUpperCase(),
|
rule.match.toUpperCase(),
|
||||||
rule.value
|
rule.value
|
||||||
@@ -1011,7 +1009,7 @@ export async function updatePublicResources(
|
|||||||
} else {
|
} else {
|
||||||
// create a brand new resource
|
// create a brand new resource
|
||||||
|
|
||||||
if (build == "saas") {
|
if (build === "saas") {
|
||||||
const usage = await usageService.getUsage(
|
const usage = await usageService.getUsage(
|
||||||
orgId,
|
orgId,
|
||||||
LimitId.PUBLIC_RESOURCES
|
LimitId.PUBLIC_RESOURCES
|
||||||
@@ -1292,7 +1290,7 @@ export async function updatePublicResources(
|
|||||||
await trx.insert(resourceRules).values({
|
await trx.insert(resourceRules).values({
|
||||||
resourceId: newResource.resourceId,
|
resourceId: newResource.resourceId,
|
||||||
action: getRuleAction(rule.action),
|
action: getRuleAction(rule.action),
|
||||||
match: rule.match.toUpperCase(),
|
match: rule.match.toUpperCase() as ResourceRule["match"],
|
||||||
value: getRuleValue(
|
value: getRuleValue(
|
||||||
rule.match.toUpperCase(),
|
rule.match.toUpperCase(),
|
||||||
rule.value
|
rule.value
|
||||||
@@ -1355,7 +1353,7 @@ function getRuleAction(input: string) {
|
|||||||
|
|
||||||
function getRuleValue(match: string, value: string) {
|
function getRuleValue(match: string, value: string) {
|
||||||
// if the match is a country, uppercase the value
|
// if the match is a country, uppercase the value
|
||||||
if (match == "COUNTRY") {
|
if (match === "COUNTRY" || match === "COUNTRY_IS_NOT") {
|
||||||
return value.toUpperCase();
|
return value.toUpperCase();
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -347,12 +347,13 @@ function getRuleAction(input: string): "ACCEPT" | "DROP" | "PASS" {
|
|||||||
|
|
||||||
function getRuleMatch(
|
function getRuleMatch(
|
||||||
input: string
|
input: string
|
||||||
): "CIDR" | "IP" | "PATH" | "COUNTRY" | "ASN" | "REGION" {
|
): "CIDR" | "IP" | "PATH" | "COUNTRY" | "COUNTRY_IS_NOT" | "ASN" | "REGION" {
|
||||||
return input.toUpperCase() as
|
return input.toUpperCase() as
|
||||||
| "CIDR"
|
| "CIDR"
|
||||||
| "IP"
|
| "IP"
|
||||||
| "PATH"
|
| "PATH"
|
||||||
| "COUNTRY"
|
| "COUNTRY"
|
||||||
|
| "COUNTRY_IS_NOT"
|
||||||
| "ASN"
|
| "ASN"
|
||||||
| "REGION";
|
| "REGION";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export const RESOURCE_RULE_MATCH_TYPES = [
|
|||||||
"IP",
|
"IP",
|
||||||
"PATH",
|
"PATH",
|
||||||
"COUNTRY",
|
"COUNTRY",
|
||||||
|
"COUNTRY_IS_NOT",
|
||||||
"ASN",
|
"ASN",
|
||||||
"REGION"
|
"REGION"
|
||||||
] as const;
|
] as const;
|
||||||
@@ -96,6 +97,7 @@ export function getResourceRuleValueValidationError(
|
|||||||
case "REGION":
|
case "REGION":
|
||||||
return isValidRegionId(value) ? null : "Invalid region ID provided";
|
return isValidRegionId(value) ? null : "Invalid region ID provided";
|
||||||
case "COUNTRY":
|
case "COUNTRY":
|
||||||
|
case "COUNTRY_IS_NOT":
|
||||||
return COUNTRIES.some((country) => country.code === value)
|
return COUNTRIES.some((country) => country.code === value)
|
||||||
? null
|
? null
|
||||||
: "Invalid country code provided";
|
: "Invalid country code provided";
|
||||||
|
|||||||
@@ -1695,6 +1695,7 @@ hybridRouter.get(
|
|||||||
) {
|
) {
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
if (rule.match == "COUNTRY") {
|
if (rule.match == "COUNTRY") {
|
||||||
|
// @ts-expect-error this is for backward compatibility
|
||||||
rule.match = "GEOIP";
|
rule.match = "GEOIP";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1054,7 +1054,10 @@ async function checkRules(
|
|||||||
isPathAllowed(rule.value, path)
|
isPathAllowed(rule.value, path)
|
||||||
) {
|
) {
|
||||||
return rule.action as any;
|
return rule.action as any;
|
||||||
} else if (clientIp && rule.match == "COUNTRY") {
|
} else if (
|
||||||
|
clientIp &&
|
||||||
|
(rule.match === "COUNTRY" || rule.match === "COUNTRY_IS_NOT")
|
||||||
|
) {
|
||||||
// COUNTRY=ALL should not affect local/private/CGNAT addresses.
|
// COUNTRY=ALL should not affect local/private/CGNAT addresses.
|
||||||
if (
|
if (
|
||||||
rule.value.toUpperCase() === "ALL" &&
|
rule.value.toUpperCase() === "ALL" &&
|
||||||
@@ -1063,7 +1066,10 @@ async function checkRules(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await isIpInGeoIP(ipCC, rule.value)) {
|
const inCountry = await isIpInGeoIP(ipCC, rule.value);
|
||||||
|
const matched = rule.match === "COUNTRY" ? inCountry : !inCountry;
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
return rule.action as any;
|
return rule.action as any;
|
||||||
}
|
}
|
||||||
} else if (clientIp && rule.match == "ASN") {
|
} else if (clientIp && rule.match == "ASN") {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const resourceRuleMatchSchema = z.enum([
|
|||||||
"IP",
|
"IP",
|
||||||
"PATH",
|
"PATH",
|
||||||
"COUNTRY",
|
"COUNTRY",
|
||||||
|
"COUNTRY_IS_NOT",
|
||||||
"ASN",
|
"ASN",
|
||||||
"REGION"
|
"REGION"
|
||||||
]);
|
]);
|
||||||
@@ -144,6 +145,7 @@ export async function updateResourceRule(
|
|||||||
| "IP"
|
| "IP"
|
||||||
| "PATH"
|
| "PATH"
|
||||||
| "COUNTRY"
|
| "COUNTRY"
|
||||||
|
| "COUNTRY_IS_NOT"
|
||||||
| "ASN"
|
| "ASN"
|
||||||
| "REGION";
|
| "REGION";
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import {
|
|||||||
type PolicyAccessRule
|
type PolicyAccessRule
|
||||||
} from "./policy-access-rule-utils";
|
} from "./policy-access-rule-utils";
|
||||||
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
|
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
|
||||||
|
import type { readonly } from "zod";
|
||||||
|
|
||||||
export type PolicyAccessRulesTableProps = {
|
export type PolicyAccessRulesTableProps = {
|
||||||
rules: PolicyAccessRule[];
|
rules: PolicyAccessRule[];
|
||||||
@@ -106,7 +107,7 @@ function getColumnClassName(columnId: string) {
|
|||||||
return "w-42 max-w-42";
|
return "w-42 max-w-42";
|
||||||
}
|
}
|
||||||
if (columnId === "match") {
|
if (columnId === "match") {
|
||||||
return "w-36 max-w-36";
|
return "w-42 max-w-42";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -230,6 +231,7 @@ export function PolicyAccessRulesTable({
|
|||||||
IP: "IP",
|
IP: "IP",
|
||||||
CIDR: t("ipAddressRange"),
|
CIDR: t("ipAddressRange"),
|
||||||
COUNTRY: t("country"),
|
COUNTRY: t("country"),
|
||||||
|
COUNTRY_IS_NOT: t("countryIsNot"),
|
||||||
ASN: "ASN",
|
ASN: "ASN",
|
||||||
REGION: t("region")
|
REGION: t("region")
|
||||||
}),
|
}),
|
||||||
@@ -442,13 +444,15 @@ export function PolicyAccessRulesTable({
|
|||||||
| "IP"
|
| "IP"
|
||||||
| "PATH"
|
| "PATH"
|
||||||
| "COUNTRY"
|
| "COUNTRY"
|
||||||
|
| "COUNTRY_IS_NOT"
|
||||||
| "ASN"
|
| "ASN"
|
||||||
| "REGION"
|
| "REGION"
|
||||||
) =>
|
) =>
|
||||||
updateRule(row.original.ruleId, {
|
updateRule(row.original.ruleId, {
|
||||||
match: value,
|
match: value,
|
||||||
value:
|
value:
|
||||||
value === "COUNTRY"
|
value === "COUNTRY" ||
|
||||||
|
value === "COUNTRY_IS_NOT"
|
||||||
? "US"
|
? "US"
|
||||||
: value === "ASN"
|
: value === "ASN"
|
||||||
? "AS15169"
|
? "AS15169"
|
||||||
@@ -458,7 +462,7 @@ export function PolicyAccessRulesTable({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-8 w-full min-w-0">
|
<SelectTrigger className="h-8 w-36 min-w-0">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -470,9 +474,14 @@ export function PolicyAccessRulesTable({
|
|||||||
{RuleMatch.CIDR}
|
{RuleMatch.CIDR}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
{isMaxmindAvailable && (
|
{isMaxmindAvailable && (
|
||||||
<SelectItem value="COUNTRY">
|
<>
|
||||||
{RuleMatch.COUNTRY}
|
<SelectItem value="COUNTRY">
|
||||||
</SelectItem>
|
{RuleMatch.COUNTRY}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="COUNTRY_IS_NOT">
|
||||||
|
{RuleMatch.COUNTRY_IS_NOT}
|
||||||
|
</SelectItem>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{includeRegionMatch && isMaxmindAvailable && (
|
{includeRegionMatch && isMaxmindAvailable && (
|
||||||
<SelectItem value="REGION">
|
<SelectItem value="REGION">
|
||||||
@@ -494,14 +503,16 @@ export function PolicyAccessRulesTable({
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
let selectedCountry: (typeof COUNTRIES)[number] | undefined;
|
let selectedCountry: (typeof COUNTRIES)[number] | undefined;
|
||||||
if (
|
if (
|
||||||
row.original.match === "COUNTRY" &&
|
(row.original.match === "COUNTRY" ||
|
||||||
|
row.original.match === "COUNTRY_IS_NOT") &&
|
||||||
row.original.value
|
row.original.value
|
||||||
) {
|
) {
|
||||||
selectedCountry = COUNTRIES.find(
|
selectedCountry = COUNTRIES.find(
|
||||||
(c) => c.code === row.original.value
|
(c) => c.code === row.original.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return row.original.match === "COUNTRY" ? (
|
return row.original.match === "COUNTRY" ||
|
||||||
|
row.original.match === "COUNTRY_IS_NOT" ? (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const POLICY_RULE_MATCH_TYPES = [
|
|||||||
"IP",
|
"IP",
|
||||||
"PATH",
|
"PATH",
|
||||||
"COUNTRY",
|
"COUNTRY",
|
||||||
|
"COUNTRY_IS_NOT",
|
||||||
"ASN",
|
"ASN",
|
||||||
"REGION"
|
"REGION"
|
||||||
] as const;
|
] as const;
|
||||||
@@ -78,6 +79,7 @@ export function createPolicyRuleValueSchema(t: TranslateFn, match: string) {
|
|||||||
message: t("rulesErrorInvalidRegionDescription")
|
message: t("rulesErrorInvalidRegionDescription")
|
||||||
});
|
});
|
||||||
case "COUNTRY":
|
case "COUNTRY":
|
||||||
|
case "COUNTRY_IS_NOT":
|
||||||
return required.refine(
|
return required.refine(
|
||||||
(value) => COUNTRIES.some((country) => country.code === value),
|
(value) => COUNTRIES.some((country) => country.code === value),
|
||||||
{ message: t("rulesErrorInvalidCountryDescription") }
|
{ message: t("rulesErrorInvalidCountryDescription") }
|
||||||
|
|||||||
Reference in New Issue
Block a user