Add excluded paths support for forward auth (fixes #108)

Allow users to exclude specific paths from Authentik/CPM forward auth
protection. When excluded_paths is set, all paths require authentication
EXCEPT the excluded ones — useful for apps like Navidrome that need
/share/* and /rest/* to bypass auth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fuomag9
2026-04-17 10:11:24 +02:00
parent 390840dbd9
commit 8f4c24119e
8 changed files with 376 additions and 6 deletions
@@ -217,6 +217,78 @@ describe('proxy-hosts boolean fields', () => {
});
});
// ---------------------------------------------------------------------------
// Authentik forward auth meta round-trip
// ---------------------------------------------------------------------------
describe('proxy-hosts authentik meta', () => {
it('stores and retrieves authentik config with excluded_paths', async () => {
const meta = {
authentik: {
enabled: true,
outpost_domain: 'outpost.goauthentik.io',
outpost_upstream: 'http://authentik:9000',
excluded_paths: ['/share/*', '/rest/*'],
},
};
const host = await insertHost({ meta: JSON.stringify(meta) });
const row = await db.query.proxyHosts.findFirst({ where: (t, { eq }) => eq(t.id, host.id) });
const parsed = JSON.parse(row!.meta!);
expect(parsed.authentik.enabled).toBe(true);
expect(parsed.authentik.excluded_paths).toEqual(['/share/*', '/rest/*']);
});
it('stores authentik config with protected_paths (no excluded_paths)', async () => {
const meta = {
authentik: {
enabled: true,
outpost_domain: 'outpost.goauthentik.io',
outpost_upstream: 'http://authentik:9000',
protected_paths: ['/admin/*', '/secret/*'],
},
};
const host = await insertHost({ meta: JSON.stringify(meta) });
const row = await db.query.proxyHosts.findFirst({ where: (t, { eq }) => eq(t.id, host.id) });
const parsed = JSON.parse(row!.meta!);
expect(parsed.authentik.protected_paths).toEqual(['/admin/*', '/secret/*']);
expect(parsed.authentik.excluded_paths).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// CPM forward auth meta round-trip
// ---------------------------------------------------------------------------
describe('proxy-hosts CPM forward auth meta', () => {
it('stores and retrieves cpm_forward_auth config with excluded_paths', async () => {
const meta = {
cpm_forward_auth: {
enabled: true,
excluded_paths: ['/api/public/*', '/health'],
},
};
const host = await insertHost({ meta: JSON.stringify(meta) });
const row = await db.query.proxyHosts.findFirst({ where: (t, { eq }) => eq(t.id, host.id) });
const parsed = JSON.parse(row!.meta!);
expect(parsed.cpm_forward_auth.enabled).toBe(true);
expect(parsed.cpm_forward_auth.excluded_paths).toEqual(['/api/public/*', '/health']);
});
it('stores cpm_forward_auth config with protected_paths (no excluded_paths)', async () => {
const meta = {
cpm_forward_auth: {
enabled: true,
protected_paths: ['/admin/*'],
},
};
const host = await insertHost({ meta: JSON.stringify(meta) });
const row = await db.query.proxyHosts.findFirst({ where: (t, { eq }) => eq(t.id, host.id) });
const parsed = JSON.parse(row!.meta!);
expect(parsed.cpm_forward_auth.protected_paths).toEqual(['/admin/*']);
expect(parsed.cpm_forward_auth.excluded_paths).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// Null meta field
// ---------------------------------------------------------------------------