feat(caddy): give Audiobookshelf a guarded abs.franksdank.cloud address #130

Merged
dank merged 5 commits from feat/abs-vhost into main 2026-11-03 00:57:47 -08:00
Showing only changes of commit ef89f77ea7 - Show all commits
+27 -11
View File
@@ -11,12 +11,15 @@
# dist-server/server/auth/OidcAuthStrategy.js + objects/settings/ServerSettings.js) # dist-server/server/auth/OidcAuthStrategy.js + objects/settings/ServerSettings.js)
# rather than a forum, because each of these is a dead-login trap: # rather than a forum, because each of these is a dead-login trap:
# #
# * TWO redirect URIs on the provider: <base>/auth/openid/callback (browser) and # * The browser callback URI <base>/auth/openid/callback for EVERY origin ABS is
# <base>/auth/openid/mobile-redirect (mobile). ABS SWAPS the app's own # reached at, plus <base>/auth/openid/mobile-redirect on ABS_BASE only. ABS SWAPS
# audiobookshelf://oauth for the mobile-redirect URL before the IdP ever sees a # the app's own audiobookshelf://oauth for the mobile-redirect URL before the IdP
# redirect_uri, so registering the app scheme with authentik — the obvious move — # ever sees a redirect_uri, so registering the app scheme with authentik — the
# is what makes phone logins fail. The app scheme belongs in ABS's # obvious move — is what makes phone logins fail. The app scheme belongs in ABS's
# authOpenIDMobileRedirectURIs list (its built-in default), not here. # authOpenIDMobileRedirectURIs list (its built-in default), not here. The mobile
# URI is registered on the LAN origin only because the phone is configured with
# the LAN address: it is a non-browser client and cannot solve the guard
# challenge that sits in front of the vhost.
# * <base> CARRIES THE BASE PATH. ABS builds redirect_uri as # * <base> CARRIES THE BASE PATH. ABS builds redirect_uri as
# <origin><authOpenIDSubfolderForRedirectURLs>/auth/openid/callback, and that subfolder is # <origin><authOpenIDSubfolderForRedirectURLs>/auth/openid/callback, and that subfolder is
# the ROUTER_BASE_PATH the container is mounted at — /audiobookshelf, which is also the only # the ROUTER_BASE_PATH the container is mounted at — /audiobookshelf, which is also the only
@@ -72,8 +75,17 @@
# (qa-e2e-admin — tests/e2e/specs/23-audiobookshelf-sso.spec.ts # (qa-e2e-admin — tests/e2e/specs/23-audiobookshelf-sso.spec.ts
# signs in as this identity, so a re-run restores the fixture) # signs in as this identity, so a re-run restores the fixture)
# ABS_BASE ABS URL as the browser sees it, base path included # ABS_BASE ABS URL as the browser sees it, base path included
# (http://192.168.0.235:8000/audiobookshelf — LAN-only by policy: # (http://192.168.0.235:8000/audiobookshelf — the LAN
# no vhost, no reverse proxy in front) # 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).
# 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
# redirect_uri_mismatch at the IdP, before any credential.
# Set it empty to register the LAN origin alone.
# AUTHENTIK_HOST ssh target (dankclaw) # AUTHENTIK_HOST ssh target (dankclaw)
set -euo pipefail set -euo pipefail
@@ -88,6 +100,7 @@ SSO_USER="${ABS_OIDC_USERNAME:-akadmin}"
MEMBERS="${ABS_OIDC_USERS:-}" MEMBERS="${ABS_OIDC_USERS:-}"
E2E_ADMIN="${ABS_E2E_ADMIN:-qa-e2e-admin}" E2E_ADMIN="${ABS_E2E_ADMIN:-qa-e2e-admin}"
BASE="${ABS_BASE:-http://192.168.0.235:8000/audiobookshelf}" BASE="${ABS_BASE:-http://192.168.0.235:8000/audiobookshelf}"
EXTRA_BASES="${ABS_EXTRA_BASES:-https://abs.franksdank.cloud/audiobookshelf}"
HOST="${AUTHENTIK_HOST:-dankclaw}" HOST="${AUTHENTIK_HOST:-dankclaw}"
# The claim expression: translate group membership into ABS role names. It is stored # The claim expression: translate group membership into ABS role names. It is stored
@@ -110,6 +123,7 @@ from authentik.providers.oauth2.models import (
ADMIN_GROUP, USER_GROUP = "$ADMIN_GROUP", "$USER_GROUP" ADMIN_GROUP, USER_GROUP = "$ADMIN_GROUP", "$USER_GROUP"
CLAIM, CLIENT_ID, SECRET, BASE = "$CLAIM", "$CLIENT_ID", "$SECRET", "$BASE" CLAIM, CLIENT_ID, SECRET, BASE = "$CLAIM", "$CLIENT_ID", "$SECRET", "$BASE"
BASES = [BASE] + """$EXTRA_BASES""".split()
SSO_USER, E2E_ADMIN = "$SSO_USER", "$E2E_ADMIN" SSO_USER, E2E_ADMIN = "$SSO_USER", "$E2E_ADMIN"
MEMBERS = """$MEMBERS""".split() MEMBERS = """$MEMBERS""".split()
EXPR = '''$EXPR''' EXPR = '''$EXPR'''
@@ -143,10 +157,12 @@ _ns = {}
exec(_src, _ns) exec(_src, _ns)
# --- provider -------------------------------------------------------------------- # --- provider --------------------------------------------------------------------
# One browser callback per origin (LAN address and the guarded vhost), because ABS
# derives redirect_uri from the request origin; the mobile URI stays on the LAN origin.
uris = [RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, uris = [RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT,
url="%s/auth/openid/callback" % BASE), url="%s/auth/openid/callback" % b) for b in BASES]
RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, uris.append(RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT,
url="%s/auth/openid/mobile-redirect" % BASE)] url="%s/auth/openid/mobile-redirect" % BASE))
authz_flow = Flow.objects.get(slug="default-provider-authorization-implicit-consent") authz_flow = Flow.objects.get(slug="default-provider-authorization-implicit-consent")
# Three things beyond the name, each one a silent dead login: the grant-type list (2026.8 # Three things beyond the name, each one a silent dead login: the grant-type list (2026.8