mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-24 11:18:48 +02:00
♻️ fix types and validate destination Host correcly
This commit is contained in:
@@ -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 */
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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"
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user