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
This commit is contained in:
fuomag9
2025-11-08 13:55:23 +01:00
parent dc8e5e262f
commit b17ae54fbd
4 changed files with 120 additions and 16 deletions

View File

@@ -658,6 +658,18 @@ function AuthentikFields({ authentik }: { authentik?: ProxyHost["authentik"] | n
size="small"
fullWidth
/>
<TextField
name="authentik_protected_paths"
label="Protected Paths (Optional)"
placeholder="/secret/*, /admin/*"
helperText="Leave empty to protect entire domain. Specify paths to protect specific routes only."
defaultValue={initial?.protectedPaths?.join(", ") ?? ""}
disabled={!enabled}
multiline
minRows={2}
size="small"
fullWidth
/>
<HiddenCheckboxField
name="authentik_set_host_header"
defaultChecked={setHostHeaderDefault}

View File

@@ -44,6 +44,7 @@ function parseAuthentikConfig(formData: FormData): ProxyHostAuthentikInput | und
const authEndpoint = parseOptionalText(formData.get("authentik_auth_endpoint"));
const copyHeaders = parseCsv(formData.get("authentik_copy_headers"));
const trustedProxies = parseCsv(formData.get("authentik_trusted_proxies"));
const protectedPaths = parseCsv(formData.get("authentik_protected_paths"));
const setHostHeader = formData.has("authentik_set_host_header_present")
? parseCheckbox(formData.get("authentik_set_host_header"))
: undefined;
@@ -67,6 +68,9 @@ function parseAuthentikConfig(formData: FormData): ProxyHostAuthentikInput | und
if (trustedProxies.length > 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;
}

View File

@@ -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<string, unknown>[] = [...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<string, unknown>[] = [...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
};
}

View File

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