Merge branch 'dev' into feat/update-popup

This commit is contained in:
Milo Schwartz
2025-11-14 09:12:11 -08:00
committed by GitHub
71 changed files with 2562 additions and 935 deletions

View File

@@ -30,6 +30,7 @@ import { pickPort } from "@server/routers/target/helpers";
import { resourcePassword } from "@server/db";
import { hashPassword } from "@server/auth/password";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { get } from "http";
export type ProxyResourcesResults = {
proxyResource: Resource;
@@ -544,7 +545,7 @@ export async function updateProxyResources(
if (
existingRule.action !== getRuleAction(rule.action) ||
existingRule.match !== rule.match.toUpperCase() ||
existingRule.value !== rule.value.toUpperCase()
existingRule.value !== getRuleValue(rule.match.toUpperCase(), rule.value)
) {
validateRule(rule);
await trx
@@ -552,7 +553,7 @@ export async function updateProxyResources(
.set({
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase()
value: getRuleValue(rule.match.toUpperCase(), rule.value),
})
.where(
eq(resourceRules.ruleId, existingRule.ruleId)
@@ -564,7 +565,7 @@ export async function updateProxyResources(
resourceId: existingResource.resourceId,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase(),
value: getRuleValue(rule.match.toUpperCase(), rule.value),
priority: index + 1 // start priorities at 1
});
}
@@ -722,7 +723,7 @@ export async function updateProxyResources(
resourceId: newResource.resourceId,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase(),
value: getRuleValue(rule.match.toUpperCase(), rule.value),
priority: index + 1 // start priorities at 1
});
}
@@ -752,6 +753,14 @@ function getRuleAction(input: string) {
return action;
}
function getRuleValue(match: string, value: string) {
// if the match is a country, uppercase the value
if (match == "COUNTRY") {
return value.toUpperCase();
}
return value;
}
function validateRule(rule: any) {
if (rule.match === "cidr") {
if (!isValidCIDR(rule.value)) {

View File

@@ -80,7 +80,8 @@ export async function getTraefikConfig(
subnet: sites.subnet,
exitNodeId: sites.exitNodeId,
// Domain cert resolver fields
domainCertResolver: domains.certResolver
domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert
})
.from(sites)
.innerJoin(targets, eq(targets.siteId, sites.siteId))
@@ -178,7 +179,8 @@ export async function getTraefikConfig(
rewritePathType: row.rewritePathType,
priority: priority,
// Store domain cert resolver fields
domainCertResolver: row.domainCertResolver
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert
});
}

View File

@@ -1,4 +1,5 @@
import z from "zod";
import ipaddr from "ipaddr.js";
export function isValidCIDR(cidr: string): boolean {
return z.string().cidr().safeParse(cidr).success;
@@ -68,11 +69,11 @@ export function isUrlValid(url: string | undefined) {
if (!url) return true; // the link is optional in the schema so if it's empty it's valid
var pattern = new RegExp(
"^(https?:\\/\\/)?" + // protocol
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
"(\\#[-a-z\\d_]*)?$",
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
"(\\#[-a-z\\d_]*)?$",
"i"
);
return !!pattern.test(url);
@@ -83,12 +84,15 @@ export function isTargetValid(value: string | undefined) {
const DOMAIN_REGEX =
/^[a-zA-Z0-9_](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9_])?(?:\.[a-zA-Z0-9_](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9_])?)*$/;
const IPV4_REGEX =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i;
// const IPV4_REGEX =
// /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
// const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i;
if (IPV4_REGEX.test(value) || IPV6_REGEX.test(value)) {
return true;
try {
const addr = ipaddr.parse(value);
return addr.kind() === "ipv4" || addr.kind() === "ipv6";
} catch {
// fall through to domain regex check
}
return DOMAIN_REGEX.test(value);
@@ -169,10 +173,10 @@ export function isSecondLevelDomain(domain: string): boolean {
}
const trimmedDomain = domain.trim().toLowerCase();
// Split into parts
const parts = trimmedDomain.split('.');
// Should have exactly 2 parts for a second-level domain (e.g., "example.com")
if (parts.length !== 2) {
return false;