From 0b0f62d8de6f758b2a98d638e3f24359c37e7c7f Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 23 Sep 2026 21:00:11 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20fix=20types=20and=20valida?= =?UTF-8?q?te=20destination=20Host=20correcly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/lib/traefik/types.ts | 8 ++++++-- server/routers/badger/verifySession.ts | 2 +- server/routers/redirect/validation.ts | 15 +++++++++------ src/components/RedirectsTable.tsx | 24 +++++++++++++++++++++--- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/server/lib/traefik/types.ts b/server/lib/traefik/types.ts index 122746419..e6a552d9c 100644 --- a/server/lib/traefik/types.ts +++ b/server/lib/traefik/types.ts @@ -36,7 +36,8 @@ export type ResourceWithTargets = Pick< | "tlsServerName" | "setHostHeader" | "enableProxy" - | "headers" + | "requestHeaders" + | "responseHeaders" | "proxyProtocol" | "wildcard" | "mode" @@ -46,7 +47,10 @@ export type ResourceWithTargets = Pick< | "maintenanceMessage" | "maintenanceEstimatedTime" > & - Pick & { + Pick< + Target, + "path" | "pathMatchType" | "rewritePath" | "rewritePathType" + > & { /** Sanitized resource name used in router/service names */ name: string; /** Sanitized resourceId + path config, unique per router */ diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index f6e279d77..1e6fc94f6 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -41,7 +41,7 @@ import { } from "@server/db"; import config from "@server/lib/config"; import { isIpInCidr, stripPortFromHost } from "@server/lib/ip"; -import { isPathAllowed, type isPathAllowed } from "@server/lib/pathMatch"; +import { isPathAllowed } from "@server/lib/pathMatch"; import { matchesPath } from "@server/lib/traefik/rule"; import { rewriteRequestPath } from "@server/lib/traefik/middleware"; import { parseHttpMethodList } from "@server/lib/validators"; diff --git a/server/routers/redirect/validation.ts b/server/routers/redirect/validation.ts index 324ddf6ff..fa304fd9e 100644 --- a/server/routers/redirect/validation.ts +++ b/server/routers/redirect/validation.ts @@ -39,9 +39,7 @@ export function isValidMatchPath( matchPath: string | null | undefined, pathMatchType: string | null | undefined ): boolean { - return ( - pathMatchType !== "regex" || !matchPath || isValidRegex(matchPath) - ); + return pathMatchType !== "regex" || !matchPath || isValidRegex(matchPath); } export const redirectRewritePathSchema = z.string().nonempty(); @@ -54,15 +52,20 @@ export const redirectPrioritySchema = z.int().min(1).max(1000); * the request path (after any rewrite) is appended to it by badger. */ export function isValidDestinationHost(value: string): boolean { - const match = /^https?:\/\/([^/:?#]+)(:\d{1,5})?$/.exec(value); - return match !== null && isValidDomain(match[1]); + if (URL.canParse(value)) { + const url = new URL(value); + /** URL origin contains the destination with no path or query */ + return value === url.origin; + } + return false; } export const redirectDestinationHostSchema = z .string() .nonempty() .refine(isValidDestinationHost, { - message: "Invalid destination, expected scheme://host such as https://example.com" + message: + "Invalid destination, expected scheme://host such as https://example.com" }); /** diff --git a/src/components/RedirectsTable.tsx b/src/components/RedirectsTable.tsx index 76de82872..4579e120e 100644 --- a/src/components/RedirectsTable.tsx +++ b/src/components/RedirectsTable.tsx @@ -43,7 +43,7 @@ import { type ComponentRef } from "react"; import { useDebouncedCallback } from "use-debounce"; -import { wait } from "@app/lib/wait"; +import { InfoPopup } from "./ui/info-popup"; export type RedirectRow = { redirectId: number; @@ -265,6 +265,17 @@ export default function RedirectsTable({ const certDomainId = redirect.resourceDomainId ?? redirect.domainId; + if (redirect.resourceId && !redirect.resourceDomainId) { + return ( +
+ +
+ ); + } + return (
{certDomainId && host ? ( @@ -479,9 +490,14 @@ function RedirectEnabledForm({ redirect.enabled ); + const missingDomain = Boolean( + redirect.resourceId && !redirect.resourceDomainId + ); + const formRef = useRef>(null); async function submitAction(formData: FormData) { + if (missingDomain) return; const newEnabled = !(formData.get("enabled") === "on"); setOptimisticEnabled(newEnabled); await onToggleEnabled(newEnabled, redirect.redirectId); @@ -490,8 +506,10 @@ function RedirectEnabledForm({ return (
formRef.current?.requestSubmit()} />