From b17ae54fbd9fed85498c811d5dfcf435ae8400bd Mon Sep 17 00:00:00 2001
From: fuomag9 <1580624+fuomag9@users.noreply.github.com>
Date: Sat, 8 Nov 2025 13:55:23 +0100
Subject: [PATCH] Squashed commit of the following:
commit b5a751005850115c84fd8fddb83f32a52835a422
Author: fuomag9 <1580624+fuomag9@users.noreply.github.com>
Date: Sat Nov 8 13:54:22 2025 +0100
Update ProxyHostsClient.tsx
commit c93b3898c31b9c206fba74605dad5a578e326ce4
Author: fuomag9 <1580624+fuomag9@users.noreply.github.com>
Date: Sat Nov 8 13:43:00 2025 +0100
test-protected-paths
---
.../proxy-hosts/ProxyHostsClient.tsx | 12 +++
app/(dashboard)/proxy-hosts/actions.ts | 4 +
src/lib/caddy.ts | 91 ++++++++++++++++---
src/lib/models/proxy-hosts.ts | 29 +++++-
4 files changed, 120 insertions(+), 16 deletions(-)
diff --git a/app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx b/app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx
index 84235985..68dc8519 100644
--- a/app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx
+++ b/app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx
@@ -658,6 +658,18 @@ function AuthentikFields({ authentik }: { authentik?: ProxyHost["authentik"] | n
size="small"
fullWidth
/>
+
0 || formData.has("authentik_trusted_proxies")) {
result.trustedProxies = trustedProxies;
}
+ if (protectedPaths.length > 0 || formData.has("authentik_protected_paths")) {
+ result.protectedPaths = protectedPaths;
+ }
if (setHostHeader !== undefined) {
result.setOutpostHostHeader = setHostHeader;
}
diff --git a/src/lib/caddy.ts b/src/lib/caddy.ts
index 4e69977d..2b6309eb 100644
--- a/src/lib/caddy.ts
+++ b/src/lib/caddy.ts
@@ -68,6 +68,7 @@ type ProxyHostAuthentikMeta = {
copy_headers?: string[];
trusted_proxies?: string[];
set_outpost_host_header?: boolean;
+ protected_paths?: string[];
};
type AuthentikRouteConfig = {
@@ -78,6 +79,7 @@ type AuthentikRouteConfig = {
copyHeaders: string[];
trustedProxies: string[];
setOutpostHostHeader: boolean;
+ protectedPaths: string[] | null;
};
type RedirectHostRow = {
@@ -494,22 +496,75 @@ function buildProxyRoutes(
forwardAuthHandler.trusted_proxies = trustedProxies;
}
- handlers.push(forwardAuthHandler);
+ // Path-based authentication support
+ if (authentik.protectedPaths && authentik.protectedPaths.length > 0) {
+ // Create separate routes for each protected path
+ for (const protectedPath of authentik.protectedPaths) {
+ const protectedHandlers: Record[] = [...handlers];
+ const protectedReverseProxy = JSON.parse(JSON.stringify(reverseProxyHandler));
+
+ protectedHandlers.push(forwardAuthHandler);
+ protectedHandlers.push(protectedReverseProxy);
+
+ hostRoutes.push({
+ match: [
+ {
+ host: domains,
+ path: [protectedPath]
+ }
+ ],
+ handle: protectedHandlers,
+ terminal: true
+ });
+ }
+
+ // Create a catch-all route for non-protected paths (without forward auth)
+ const unprotectedHandlers: Record[] = [...handlers];
+ unprotectedHandlers.push(reverseProxyHandler);
+
+ hostRoutes.push({
+ match: [
+ {
+ host: domains
+ }
+ ],
+ handle: unprotectedHandlers,
+ terminal: true
+ });
+ } else {
+ // No path-based protection: protect entire domain (backward compatibility)
+ handlers.push(forwardAuthHandler);
+ handlers.push(reverseProxyHandler);
+
+ const route: CaddyHttpRoute = {
+ match: [
+ {
+ host: domains
+ }
+ ],
+ handle: handlers,
+ terminal: true
+ };
+
+ hostRoutes.push(route);
+ }
+ } else {
+ // No Authentik: standard reverse proxy
+ handlers.push(reverseProxyHandler);
+
+ const route: CaddyHttpRoute = {
+ match: [
+ {
+ host: domains
+ }
+ ],
+ handle: handlers,
+ terminal: true
+ };
+
+ hostRoutes.push(route);
}
- handlers.push(reverseProxyHandler);
-
- const route: CaddyHttpRoute = {
- match: [
- {
- host: domains
- }
- ],
- handle: handlers,
- terminal: true
- };
-
- hostRoutes.push(route);
routes.push(...hostRoutes);
}
@@ -960,6 +1015,11 @@ function parseAuthentikConfig(meta: ProxyHostAuthentikMeta | undefined | null):
const setOutpostHostHeader =
meta.set_outpost_host_header !== undefined ? Boolean(meta.set_outpost_host_header) : true;
+ const protectedPaths =
+ Array.isArray(meta.protected_paths) && meta.protected_paths.length > 0
+ ? meta.protected_paths.map((path) => path?.trim()).filter((path): path is string => Boolean(path))
+ : null;
+
return {
enabled: true,
outpostDomain,
@@ -967,6 +1027,7 @@ function parseAuthentikConfig(meta: ProxyHostAuthentikMeta | undefined | null):
authEndpoint,
copyHeaders,
trustedProxies,
- setOutpostHostHeader
+ setOutpostHostHeader,
+ protectedPaths
};
}
diff --git a/src/lib/models/proxy-hosts.ts b/src/lib/models/proxy-hosts.ts
index c5c9081a..c213176f 100644
--- a/src/lib/models/proxy-hosts.ts
+++ b/src/lib/models/proxy-hosts.ts
@@ -29,6 +29,7 @@ export type ProxyHostAuthentikConfig = {
copyHeaders: string[];
trustedProxies: string[];
setOutpostHostHeader: boolean;
+ protectedPaths: string[] | null;
};
export type ProxyHostAuthentikInput = {
@@ -39,6 +40,7 @@ export type ProxyHostAuthentikInput = {
copyHeaders?: string[] | null;
trustedProxies?: string[] | null;
setOutpostHostHeader?: boolean | null;
+ protectedPaths?: string[] | null;
};
type ProxyHostAuthentikMeta = {
@@ -49,6 +51,7 @@ type ProxyHostAuthentikMeta = {
copy_headers?: string[];
trusted_proxies?: string[];
set_outpost_host_header?: boolean;
+ protected_paths?: string[];
};
type ProxyHostMeta = {
@@ -150,6 +153,13 @@ function sanitizeAuthentikMeta(meta: ProxyHostAuthentikMeta | undefined): ProxyH
normalized.set_outpost_host_header = Boolean(meta.set_outpost_host_header);
}
+ if (Array.isArray(meta.protected_paths)) {
+ const paths = meta.protected_paths.map((path) => path?.trim()).filter((path): path is string => Boolean(path));
+ if (paths.length > 0) {
+ normalized.protected_paths = paths;
+ }
+ }
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
}
@@ -263,6 +273,17 @@ function normalizeAuthentikInput(
next.set_outpost_host_header = Boolean(input.setOutpostHostHeader);
}
+ if (input.protectedPaths !== undefined) {
+ const paths = (input.protectedPaths ?? [])
+ .map((path) => path?.trim())
+ .filter((path): path is string => Boolean(path));
+ if (paths.length > 0) {
+ next.protected_paths = paths;
+ } else {
+ delete next.protected_paths;
+ }
+ }
+
if ((next.enabled ?? false) && next.outpost_domain && !next.auth_endpoint) {
next.auth_endpoint = `/${next.outpost_domain}/auth/caddy`;
}
@@ -321,6 +342,8 @@ function hydrateAuthentik(meta: ProxyHostAuthentikMeta | undefined): ProxyHostAu
: DEFAULT_AUTHENTIK_TRUSTED_PROXIES;
const setOutpostHostHeader =
meta.set_outpost_host_header !== undefined ? Boolean(meta.set_outpost_host_header) : true;
+ const protectedPaths =
+ Array.isArray(meta.protected_paths) && meta.protected_paths.length > 0 ? meta.protected_paths : null;
return {
enabled,
@@ -329,7 +352,8 @@ function hydrateAuthentik(meta: ProxyHostAuthentikMeta | undefined): ProxyHostAu
authEndpoint,
copyHeaders,
trustedProxies,
- setOutpostHostHeader
+ setOutpostHostHeader,
+ protectedPaths
};
}
@@ -358,6 +382,9 @@ function dehydrateAuthentik(config: ProxyHostAuthentikConfig | null): ProxyHostA
meta.trusted_proxies = [...config.trustedProxies];
}
meta.set_outpost_host_header = config.setOutpostHostHeader;
+ if (config.protectedPaths && config.protectedPaths.length > 0) {
+ meta.protected_paths = [...config.protectedPaths];
+ }
return meta;
}