From fadbe104202fed4037949458902cf088833b0115 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 3 Nov 2026 01:16:35 -0800 Subject: [PATCH 1/4] fix(authentik): census and repair the OAuth defaults on every proxy provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A proxy provider built by hand in `ak shell` never runs ProxyProvider.set_oauth_defaults() — the REST serializer does, the ORM does not — so the row looks fully configured while redirect_uris is empty (they are derived from external_host by _get_callback_url()), grant_types is [], and authorization_flow is NULL. Each one kills a guarded login in a different, unrelated-looking way, all before any credential is requested: authentik's own "Redirect URI Error" page, then a bodiless HTTP 400 from /application/o/authorize/ once the URIs are filled by hand, and a 500 NoneType.slug in the flow planner. abs-proxy (abs.franksdank.cloud) hit the first two on 2026-11-03. The script calls the vendor's own set_oauth_defaults() and points the authorization flow at default-provider-authorization-implicit-consent for every proxy provider, idempotently; a provider that already matches is untouched, so a deliberate deviation is never flattened. `--check` is the census (exit 1 on drift, writes nothing) for gate use. Applied live 2026-11-03: abs-proxy fixed, the other ten (REST-created) already compliant, census now reports drift: 0. This is authentik DB state and no deployable file carries it — a blueprint re-import or an ak-shell re-creation reverts it and the symptom returns, so the law and the failure signatures are recorded on the outpost page as provisioning rule 3. --- scripts/authentik-proxy-provider-defaults.sh | 95 +++++++++++++++++++ .../Authentik Outpost Reference.md | 12 ++- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100755 scripts/authentik-proxy-provider-defaults.sh diff --git a/scripts/authentik-proxy-provider-defaults.sh b/scripts/authentik-proxy-provider-defaults.sh new file mode 100755 index 0000000..4e1cfe6 --- /dev/null +++ b/scripts/authentik-proxy-provider-defaults.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Census and repair the OAuth2 defaults on every authentik PROXY provider. +# +# A proxy provider built by hand in `ak shell` never runs +# ProxyProvider.set_oauth_defaults() — the REST serializer calls it, the ORM does +# not — so the row looks perfectly configured (external_host, internal_host, +# application bound, embedded-outpost claim present) while three OAuth facts are +# blank. Each one kills the guarded login in a different, unrelated-looking way, +# all BEFORE any credential is asked for: +# +# * redirect_uris empty -> authentik answers its own page: +# "Redirect URI Error — The request fails due to a missing, invalid, or +# mismatching redirection URI (redirect_uri)". Measured on abs-proxy +# (abs.franksdank.cloud) 2026-11-03. +# * grant_types empty -> the authorize endpoint refuses the outpost's +# authorization-code request with a bodiless HTTP 400 (Chromium renders +# "This page isn't working / HTTP ERROR 400"). Same provider, one step +# later, after the URIs were filled in by hand. +# * authorization_flow NULL -> the authorize view crashes building its flow +# planner: AttributeError 'NoneType' object has no attribute 'slug', HTTP +# 500. That is what authentik-grafana-sso-fix.sh exists for. +# +# So: a guard that shows ANY of those three pages is not an Anubis problem and +# not a Caddy problem — run this script before touching the route. +# +# Repair calls the VENDOR's own set_oauth_defaults() (grant_types = authz code + +# client credentials + password, confidential client, the stock scope mappings, +# redirect_uris derived from external_host by _get_callback_url) and points the +# authorization flow at default-provider-authorization-implicit-consent, the flow +# every working provider here uses. Providers that already match are untouched, +# so a deliberate per-provider deviation is never silently flattened. +# +# This is authentik DB state. It is NOT in any deployable file: a blueprint +# re-import, or an ak-shell re-creation of a provider, reverts these facts and +# the symptom returns verbatim. Re-run after any such rebuild. Idempotent. +# +# Usage: authentik-proxy-provider-defaults.sh [--check] +# no argument repair what drifts, print one line per provider +# --check census only, exit 1 if anything drifts (gate use) +# override host: AUTHENTIK_HOST=… +set -euo pipefail +HOST="${AUTHENTIK_HOST:-dankclaw}" +MODE="${1:-}" +case "$MODE" in + "" | --check) : ;; + *) echo "usage: $(basename "$0") [--check]" >&2; exit 2 ;; +esac + +ssh "$HOST" 'podman exec -i authentik-server ak shell' < grants %s, %d redirect URIs, flow %s" + % ([str(g) for g in after.grant_types], len(after.redirect_uris), + after.authorization_flow and after.authorization_flow.slug)) + +print("providers checked, drift: %d" % drift) +raise SystemExit(1 if (CHECK and drift) else 0) +PYEOF + +echo "Verify: walk the guarded vhost in a browser — Anubis solves, authentik" +echo "renders its login form (no Redirect URI Error, no HTTP 400), and the" +echo "callback lands back on the app." diff --git a/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md b/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md index 596ac89..da56f5f 100644 --- a/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md +++ b/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md @@ -56,7 +56,7 @@ correctly — the outpost matches on hostname. ## Provisioning rules for proxy providers -Two provisioning rules, each verified live by an outage: +Three provisioning rules, each verified live by an outage or a dead login: 1. **All three flows are required.** A ProxyProvider created via raw API/ORM with any of `authentication_flow`/`authorization_flow`/`invalidation_flow` @@ -80,6 +80,16 @@ Two provisioning rules, each verified live by an outage: the app. No error is logged anywhere — the SSO "works" and the app never appears. Create the Application with `provider_id` set (the UI does this automatically; raw API/ORM drives must do it explicitly). +3. **A raw ORM create skips the OAuth defaults.** The REST serializer calls + `ProxyProvider.set_oauth_defaults()`; `ProxyProvider.objects.create()` does not. The row + then looks fully configured — external_host, internal_host, application bound, outpost + claimed — while `redirect_uris` is empty (those URIs are *derived* from `external_host` by + `_get_callback_url`) and `grant_types` is `[]`. Both kill the guard before any credential, + in this order: authentik renders its own **"Redirect URI Error"** page, and once the URIs + are filled by hand the next wall is a bodiless **HTTP 400** out of `/application/o/authorize/` + (both measured on `abs-proxy`, 2026-11-03). Census and repair in one idempotent pass, all + providers at once: `scripts/authentik-proxy-provider-defaults.sh`; `--check` prints the + census and exits 1 on drift, and a provider that already matches is left alone. ## OAuth2 relying-party quirks (this fork; corrected live 2026-10-20) -- 2.54.0 From 5ef141eb17b66bd420bc931f07ec80f22533c26b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 3 Nov 2026 01:16:40 -0800 Subject: [PATCH 2/4] fix(caddy): tell Audiobookshelf its real scheme so the OIDC callback validates ABS checks every OIDC callback against the origin it believes it is served from: getRequestOrigin() is x-forwarded-proto://Host (dist-server/server/utils/requestUtils.js, read in the pinned image digest 2026-11-03). The post-auth http://:8017 listener is fed over plain http, so Caddy's own X-Forwarded-Proto is http, ABS derives http://abs.franksdank.cloud, and answers its own https callback with 400 {"message":"Invalid callback URL - must be same-origin"} Measured at the app with the callback held fixed: Host abs.franksdank.cloud alone reproduces the 400 (as does every other Host, including the bare LAN IP), and adding `X-Forwarded-Proto: https` turns it into a 302 to /application/o/authorize/ carrying the registered redirect_uri. The edge for a guarded name is TLS-only, so https is the truthful value, not a spoof; the LAN door needs nothing because there browser and server already agree on http, which is why only the guarded path was broken. --- dankclaw/Caddyfile | 11 ++++++++++- serverAppConfigs-docs/Dankserver/Audiobookshelf.md | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dankclaw/Caddyfile b/dankclaw/Caddyfile index 2243ca6..9d85fcb 100644 --- a/dankclaw/Caddyfile +++ b/dankclaw/Caddyfile @@ -429,7 +429,16 @@ http://:8017 { # convenience hop, taken post-auth so nothing reaches the app # pre-authentication. redir / /audiobookshelf/ permanent - reverse_proxy 192.168.0.235:8000 + # Audiobookshelf validates every OIDC callback against the origin it + # believes it is served from — getRequestOrigin() is + # x-forwarded-proto://Host — and a mismatch is a bodiless 400 + # "Invalid callback URL - must be same-origin" before the IdP is ever + # contacted (measured 2026-11-03). This listener is fed over plain http + # by the outpost, so Caddy's own X-Forwarded-Proto here says http; the + # edge for this name is TLS-only, so https is the truthful value. + reverse_proxy 192.168.0.235:8000 { + header_up X-Forwarded-Proto https + } } @prowlarr host prowlarr.franksdank.cloud diff --git a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md index 0e397df..f6479b3 100644 --- a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md +++ b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md @@ -140,6 +140,14 @@ Both halves — authentik's provider row and ABS's settings — are asserted end second wall appears after the fix ships (measured 2026-11-02: the browser reached authentik, got `code`+`state`, and ABS still bounced to `/login?error=undefined`). The script sets `authorization_code` + `refresh_token`; a blueprint re-import reverts the row silently. +- **The guarded door has to tell the truth about its scheme.** ABS validates every OIDC + `callback` against the origin it believes it is served from — `getRequestOrigin()` is + `x-forwarded-proto://Host` (`server/utils/requestUtils.js`, read in the pinned digest + 2026-11-03) — so behind a TLS edge that reaches the container over plain http it computes + `http://abs.franksdank.cloud` and answers its own https callback with a 400 "Invalid callback + URL - must be same-origin" before the IdP is ever contacted. The Caddy `:8017` route pins + `header_up X-Forwarded-Proto https` for exactly that reason ([[Caddy]] *Guard a new vhost*); + the LAN door needs nothing, because there browser and server agree on `http`. - **The claim mapping is not the whole `property_mappings` list.** A provider holding only the custom mapping grants a token scoped shorter than the scopes ABS requests (`openid profile email abs_groups`): the code is issued and exchanged normally, and ABS's -- 2.54.0 From 71d5804eaff8e5c2533729ca7dfc7104d2f8d9fb Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 3 Nov 2026 01:17:12 -0800 Subject: [PATCH 3/4] docs(caddy): put both auth walls in the guard-a-new-vhost procedure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 1 gains the closing sentence the abs-proxy incident demands: the ak-shell snippet that creates a ProxyProvider with objects.create() must be followed by scripts/authentik-proxy-provider-defaults.sh, or the guard fails before any credential. Step 2 gains the forwarded-scheme clause for any app that derives its own origin from the request. Each fact keeps one owner — the laws stay on [[Authentik Outpost Reference]] (provisioning rule 3) and [[Audiobookshelf]]; this page only says where in the procedure they bite. Outpost page timestamp bumped to match its substantive edit. --- .../Infrastructure/Authentik Outpost Reference.md | 2 +- serverAppConfigs-docs/Infrastructure/Caddy.md | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md b/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md index da56f5f..fd4d2b7 100644 --- a/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md +++ b/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md @@ -4,7 +4,7 @@ description: "Outpost, proxy-provider, OAuth2, LDAP and flow configuration for t tags: - infrastructure - authentik -timestamp: 2026-10-19 +timestamp: 2026-11-03 --- # Authentik Outpost Reference diff --git a/serverAppConfigs-docs/Infrastructure/Caddy.md b/serverAppConfigs-docs/Infrastructure/Caddy.md index 06ed99b..0780a66 100644 --- a/serverAppConfigs-docs/Infrastructure/Caddy.md +++ b/serverAppConfigs-docs/Infrastructure/Caddy.md @@ -125,10 +125,24 @@ authentik's error page instead of the app. outpost routes on the ORIGINAL `Host` header alone, so anything else matches zero providers. Never print a provider's fields wholesale — a proxy provider carries a client secret, and it lands in whatever transcript the command runs in. + + `ProxyProvider.objects.create()` is not the API: the serializer's + `set_oauth_defaults()` never runs, so the row lands with `redirect_uris` and + `grant_types` empty, and the guard fails *before* any credential — authentik's + "Redirect URI Error" page, then a bodiless HTTP 400 once the URIs are filled + by hand. Run `scripts/authentik-proxy-provider-defaults.sh` after the snippet + (idempotent, covers every provider; `--check` is the census); law and failure + signatures in [[Authentik Outpost Reference]]. 2. **Then the route.** The public site block proxies the whole request to `anubis:8923` with `header_up X-Real-Ip {remote_host}`, and the post-auth `http://:8017` listener gains a `@ host ` handler pointing at the app — copy `romm`/`files`, which are the same shape including a Dankserver LAN-port upstream. + An app that derives its own origin from the request needs the scheme pinned: + Audiobookshelf checks every OIDC callback against `x-forwarded-proto://Host`; this + listener is fed over plain http so Caddy's own `X-Forwarded-Proto` is `http`, and the + app answers a 400 "Invalid callback URL - must be same-origin" for its own https + callback. The edge for a guarded name is TLS-only, so `header_up X-Forwarded-Proto + https` is the truthful value ([[Audiobookshelf]]). 3. **Then the name and the audit.** A `dankclaw/pihole/addn-hosts` line (the `pihole` seam fails the build if a Caddyfile vhost has no record), a Porkbun A record if the name must resolve off-LAN ([[LAN-DNS]]), and the subdomain added to `GUARDED` in -- 2.54.0 From 23ad0ca317975ea552f0d5399f0a0bb23bad3c46 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 3 Nov 2026 01:23:54 -0800 Subject: [PATCH 4/4] chore(abs): name the Audiobookshelf door audiobooks.franksdank.cloud MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `abs.` was a three-letter abbreviation of the container; the library it serves is /mnt/entropy/Audiobooks, and `audiobooks.` says what the door is. Renamed in one commit on purpose: the pihole seam fails the suite if a Caddyfile vhost has no addn-hosts record, so any split would leave a red commit, and the `abs` name was never deployed at the IdP with this fix, so nothing has to keep answering it — no alias, no redirect, no stale URI. Moves together: the Caddyfile site block, its matcher (@abs -> @audiobooks), the LAN record, ABS_EXTRA_BASES in authentik-audiobookshelf-sso.sh (that script REPLACES the provider's redirect-URI list, so the old origin's callback leaves the RP when it is next run), the homepage admin tile, the guard spec's GUARDED list and leak marker, and the three vault pages that name the door. authentik's provider row follows in the deploy window (external_host + name), which re-derives its redirect_uris through scripts/authentik-proxy-provider-defaults.sh; the Application row keeps its own name, "Audiobookshelf Guard" — it names the app, not the vhost. --- dankclaw/Caddyfile | 13 +++++++------ dankclaw/pihole/addn-hosts | 2 +- scripts/authentik-audiobookshelf-sso.sh | 6 +++--- scripts/authentik-proxy-provider-defaults.sh | 4 ++-- serverAppConfigs-docs/Dankserver/Audiobookshelf.md | 8 ++++---- .../Infrastructure/Authentik Outpost Reference.md | 2 +- serverAppConfigs-docs/Infrastructure/Caddy.md | 2 +- shared/homepage/services.json | 2 +- tests/e2e/specs/14-guard.spec.ts | 6 +++--- 9 files changed, 23 insertions(+), 22 deletions(-) diff --git a/dankclaw/Caddyfile b/dankclaw/Caddyfile index 9d85fcb..7e24d00 100644 --- a/dankclaw/Caddyfile +++ b/dankclaw/Caddyfile @@ -268,10 +268,11 @@ files.franksdank.cloud { # Anubis → Authentik → app, like romm/files. The container carries # ROUTER_BASE_PATH=/audiobookshelf, so this vhost serves the app under that # path; the bare root is redirected onto it in the post-auth listener, never -# pre-auth. Guard state in the IdP: proxy provider `abs-proxy` -# (external_host https://abs.franksdank.cloud/, internal_host http://caddy:8017, -# application "Audiobookshelf Guard"), claimed by the embedded outpost. -abs.franksdank.cloud { +# pre-auth. Guard state in the IdP: proxy provider `audiobooks-proxy` +# (external_host https://audiobooks.franksdank.cloud/, internal_host +# http://caddy:8017, application "Audiobookshelf Guard"), claimed by the +# embedded outpost. +audiobooks.franksdank.cloud { reverse_proxy anubis:8923 { header_up X-Real-Ip {remote_host} } @@ -423,8 +424,8 @@ http://:8017 { reverse_proxy 192.168.0.235:3923 } - @abs host abs.franksdank.cloud - handle @abs { + @audiobooks host audiobooks.franksdank.cloud + handle @audiobooks { # The app answers only under its ROUTER_BASE_PATH; the bare root is a # convenience hop, taken post-auth so nothing reaches the app # pre-authentication. diff --git a/dankclaw/pihole/addn-hosts b/dankclaw/pihole/addn-hosts index 51abc29..c2421cd 100644 --- a/dankclaw/pihole/addn-hosts +++ b/dankclaw/pihole/addn-hosts @@ -26,7 +26,7 @@ 192.168.0.143 romm.franksdank.cloud 192.168.0.143 files.franksdank.cloud 192.168.0.143 jelly.franksdank.cloud -192.168.0.143 abs.franksdank.cloud +192.168.0.143 audiobooks.franksdank.cloud 192.168.0.143 radarr.franksdank.cloud 192.168.0.143 lidarr.franksdank.cloud 192.168.0.143 readarr.franksdank.cloud diff --git a/scripts/authentik-audiobookshelf-sso.sh b/scripts/authentik-audiobookshelf-sso.sh index 7dbba89..2f3df52 100755 --- a/scripts/authentik-audiobookshelf-sso.sh +++ b/scripts/authentik-audiobookshelf-sso.sh @@ -79,8 +79,8 @@ # origin, reached directly, which is also what the phone # app is configured with) # ABS_EXTRA_BASES space-separated additional origins, base path included -# (default: https://abs.franksdank.cloud/audiobookshelf — -# the guarded vhost, dankclaw/Caddyfile). +# (default: https://audiobooks.franksdank.cloud/audiobookshelf +# — the guarded vhost, dankclaw/Caddyfile). # ABS builds its redirect_uri from the ORIGIN of the # request, so each origin the app is served at needs its # own registered URI — an unregistered origin is a @@ -100,7 +100,7 @@ SSO_USER="${ABS_OIDC_USERNAME:-akadmin}" MEMBERS="${ABS_OIDC_USERS:-}" E2E_ADMIN="${ABS_E2E_ADMIN:-qa-e2e-admin}" BASE="${ABS_BASE:-http://192.168.0.235:8000/audiobookshelf}" -EXTRA_BASES="${ABS_EXTRA_BASES:-https://abs.franksdank.cloud/audiobookshelf}" +EXTRA_BASES="${ABS_EXTRA_BASES:-https://audiobooks.franksdank.cloud/audiobookshelf}" HOST="${AUTHENTIK_HOST:-dankclaw}" # The claim expression: translate group membership into ABS role names. It is stored diff --git a/scripts/authentik-proxy-provider-defaults.sh b/scripts/authentik-proxy-provider-defaults.sh index 4e1cfe6..74883ba 100755 --- a/scripts/authentik-proxy-provider-defaults.sh +++ b/scripts/authentik-proxy-provider-defaults.sh @@ -10,8 +10,8 @@ # # * redirect_uris empty -> authentik answers its own page: # "Redirect URI Error — The request fails due to a missing, invalid, or -# mismatching redirection URI (redirect_uri)". Measured on abs-proxy -# (abs.franksdank.cloud) 2026-11-03. +# mismatching redirection URI (redirect_uri)". Measured on the Audiobookshelf +# guard provider, 2026-11-03. # * grant_types empty -> the authorize endpoint refuses the outpost's # authorization-code request with a bodiless HTTP 400 (Chromium renders # "This page isn't working / HTTP ERROR 400"). Same provider, one step diff --git a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md index f6479b3..6f67bea 100644 --- a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md +++ b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md @@ -17,7 +17,7 @@ server — it shares nothing with [[Plex]] or [[Jellyfin]] and owns no files on array. **Host:** Dankserver — **Unit:** `audiobookshelf.service` (rootless Quadlet, user manager) — -**Doors:** LAN `http://192.168.0.235:8000/audiobookshelf` (direct, what the phone app uses) and guarded `https://abs.franksdank.cloud/audiobookshelf/`. +**Doors:** LAN `http://192.168.0.235:8000/audiobookshelf` (direct, what the phone app uses) and guarded `https://audiobooks.franksdank.cloud/audiobookshelf/`. ## Shape @@ -98,7 +98,7 @@ the local form, and the phone app offers SSO on its server-address screen. The l | The ABS half: issuer, the four IdP URLs, claim, auto-register | `scripts/audiobookshelf-init.sh`, armed automatically when that secret exists in the credential file | | Role at first SSO sign-in | the `abs_groups` claim: `audiobookshelf_admins` → `admin`, `audiobookshelf_users` → `user`, in neither → denied (fail closed) | | Admin group members | `akadmin` (the operator's own authentik admin account — sign in as it and the claim grants site admin) and `qa-e2e-admin` (the identity the deploy gate signs in as, so the script re-adds it every run; drop it and the gate fails, not a phone) | -| The guard in front of the vhost | proxy provider `abs-proxy` (external_host `https://abs.franksdank.cloud/`, internal_host `http://caddy:8017`) + application "Audiobookshelf Guard", claimed by the embedded outpost — the ak-shell recipe in [[Caddy]] *Guard a new vhost* creates it; without that row the chain answers authentik's error page, not the login | +| The guard in front of the vhost | proxy provider `audiobooks-proxy` (external_host `https://audiobooks.franksdank.cloud/`, internal_host `http://caddy:8017`) + application "Audiobookshelf Guard" (its own name tracks the app, not the vhost), claimed by the embedded outpost — the ak-shell recipe in [[Caddy]] *Guard a new vhost* creates it; without that row the chain answers authentik's error page, not the login | The IdP saying yes is not the same as being allowed in: with `authOpenIDAutoRegister` on, a first sign-in creates the account, and the claim alone decides the type. A user in @@ -144,7 +144,7 @@ Both halves — authentik's provider row and ABS's settings — are asserted end `callback` against the origin it believes it is served from — `getRequestOrigin()` is `x-forwarded-proto://Host` (`server/utils/requestUtils.js`, read in the pinned digest 2026-11-03) — so behind a TLS edge that reaches the container over plain http it computes - `http://abs.franksdank.cloud` and answers its own https callback with a 400 "Invalid callback + `http://audiobooks.franksdank.cloud` and answers its own https callback with a 400 "Invalid callback URL - must be same-origin" before the IdP is ever contacted. The Caddy `:8017` route pins `header_up X-Forwarded-Proto https` for exactly that reason ([[Caddy]] *Guard a new vhost*); the LAN door needs nothing, because there browser and server agree on `http`. @@ -197,7 +197,7 @@ Both halves — authentik's provider row and ABS's settings — are asserted end service across a reboot ([[Quadlet Lifecycle]]). - **Two doors, one app.** `http://192.168.0.235:8000/audiobookshelf` is the raw LAN address — plain HTTP, nothing in front, what the phone app is configured - with; `https://abs.franksdank.cloud/audiobookshelf/` is the guarded vhost + with; `https://audiobooks.franksdank.cloud/audiobookshelf/` is the guarded vhost (Caddy → Anubis → the Authentik outpost → Caddy `:8017` → this container), the house chain for browser traffic — [[Caddy]] *Guard a new vhost*. That chain serves browsers only: the phone app is a non-browser client and solves diff --git a/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md b/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md index fd4d2b7..6bbb7fa 100644 --- a/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md +++ b/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md @@ -87,7 +87,7 @@ Three provisioning rules, each verified live by an outage or a dead login: `_get_callback_url`) and `grant_types` is `[]`. Both kill the guard before any credential, in this order: authentik renders its own **"Redirect URI Error"** page, and once the URIs are filled by hand the next wall is a bodiless **HTTP 400** out of `/application/o/authorize/` - (both measured on `abs-proxy`, 2026-11-03). Census and repair in one idempotent pass, all + (both measured on the Audiobookshelf guard provider, 2026-11-03). Census and repair in one idempotent pass, all providers at once: `scripts/authentik-proxy-provider-defaults.sh`; `--check` prints the census and exits 1 on drift, and a provider that already matches is left alone. diff --git a/serverAppConfigs-docs/Infrastructure/Caddy.md b/serverAppConfigs-docs/Infrastructure/Caddy.md index 0780a66..c116a2e 100644 --- a/serverAppConfigs-docs/Infrastructure/Caddy.md +++ b/serverAppConfigs-docs/Infrastructure/Caddy.md @@ -107,7 +107,7 @@ authentik's error page instead of the app. from authentik.providers.proxy.models import ProxyProvider from authentik.core.models import Application from authentik.outposts.models import Outpost - NAME, EXT = "abs", "https://abs.franksdank.cloud/" + NAME, EXT = "audiobooks", "https://audiobooks.franksdank.cloud/" pp = ProxyProvider.objects.filter(name=f"{NAME}-proxy").first() if pp is None: pp = ProxyProvider.objects.create(name=f"{NAME}-proxy", external_host=EXT, diff --git a/shared/homepage/services.json b/shared/homepage/services.json index 99fd6a2..b7e87d9 100644 --- a/shared/homepage/services.json +++ b/shared/homepage/services.json @@ -516,7 +516,7 @@ "category": "admin", "section": "Media Library Apps", "description": "Audiobook server for the entropy library: its own SQLite library, its own login, per-listener positions.", - "url": "http://abs.franksdank.cloud/audiobookshelf/", + "url": "http://audiobooks.franksdank.cloud/audiobookshelf/", "docs": "", "probe": "192.168.0.235:8000", "method": "http", diff --git a/tests/e2e/specs/14-guard.spec.ts b/tests/e2e/specs/14-guard.spec.ts index 7d1fe57..7e322c6 100644 --- a/tests/e2e/specs/14-guard.spec.ts +++ b/tests/e2e/specs/14-guard.spec.ts @@ -15,11 +15,11 @@ const BASE = "https://www.franksdank.cloud"; // Every vhost that Caddy routes through anubis:8923 (see dankclaw/Caddyfile). // Keep this list in lockstep with the Caddyfile: a new guarded vhost must be // added here or the guard-chain audit is incomplete. -const GUARDED = ["www", "search", "romm", "files", "seerr", "grafana", "prometheus", "loki", "notebook", "abs"]; +const GUARDED = ["www", "search", "romm", "files", "seerr", "grafana", "prometheus", "loki", "notebook", "audiobooks"]; // Markers that prove app content leaked past the guard to an anonymous user. Each // marker must be a string the APP renders that the gate itself could never print — -// for abs that is the Audiobookshelf login's own SSO button label (the app's OIDC +// for audiobooks that is the Audiobookshelf login's own SSO button label (the app's // door), which authentik's gate never renders because it IS the IdP. const LEAK_MARKERS: Record = { "search.franksdank.cloud": ["SearXNG preferences"], @@ -29,7 +29,7 @@ const LEAK_MARKERS: Record = { "grafana.franksdank.cloud": ["Dashboards"], "prometheus.franksdank.cloud": ["Expression Browser", "Graph"], "notebook.franksdank.cloud": ["Open Notebook"], - "abs.franksdank.cloud": ["Sign in with Authentik"], + "audiobooks.franksdank.cloud": ["Sign in with Authentik"], }; test("homepage root (/) is guarded for unauthenticated users", async ({ page }) => { -- 2.54.0