chore(abs): name the Audiobookshelf door audiobooks.franksdank.cloud
`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.
This commit is contained in:
1 parent
71d5804eaf
commit
23ad0ca317
9 files changed
+23
-22
No files matched your search
+7
-6
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<string, string[]> = {
|
||||
"search.franksdank.cloud": ["SearXNG preferences"],
|
||||
@@ -29,7 +29,7 @@ const LEAK_MARKERS: Record<string, string[]> = {
|
||||
"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 }) => {
|
||||
|
||||
Reference in new issue
Block a user