♻️ fix types and validate destination Host correcly

This commit is contained in:
Fred KISSIE
2026-09-23 21:00:11 +02:00
parent 14fda9cec0
commit 0b0f62d8de
4 changed files with 37 additions and 12 deletions
+6 -2
View File
@@ -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<Target, "path" | "pathMatchType" | "rewritePath" | "rewritePathType"> & {
Pick<
Target,
"path" | "pathMatchType" | "rewritePath" | "rewritePathType"
> & {
/** Sanitized resource name used in router/service names */
name: string;
/** Sanitized resourceId + path config, unique per router */
+1 -1
View File
@@ -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";
+9 -6
View File
@@ -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"
});
/**
+21 -3
View File
@@ -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 (
<div className="flex items-center gap-2 min-w-0">
<InfoPopup
info={t("rdirectDomainNotFoundDescription")}
text={t("domainNotFound")}
/>
</div>
);
}
return (
<div className="flex items-center gap-2 min-w-0">
{certDomainId && host ? (
@@ -479,9 +490,14 @@ function RedirectEnabledForm({
redirect.enabled
);
const missingDomain = Boolean(
redirect.resourceId && !redirect.resourceDomainId
);
const formRef = useRef<ComponentRef<"form">>(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 (
<form action={submitAction} ref={formRef}>
<Switch
checked={optimisticEnabled}
disabled={optimisticEnabled !== redirect.enabled}
checked={!missingDomain && optimisticEnabled}
disabled={
missingDomain || optimisticEnabled !== redirect.enabled
}
name="enabled"
onCheckedChange={() => formRef.current?.requestSubmit()}
/>