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; }