diff --git a/scripts/audiobookshelf-init.sh b/scripts/audiobookshelf-init.sh index f4ac4c1..5d81f78 100755 --- a/scripts/audiobookshelf-init.sh +++ b/scripts/audiobookshelf-init.sh @@ -268,8 +268,10 @@ else: # not auto-launch: the login page keeps offering the local form, which is the # break-glass path when the IdP is unreachable. "authOpenIDAutoLaunch": False, - # a household user's first SSO sign-in creates their account; the group claim - # decides the role, and a user in neither group is denied (fail closed). + # a household user's first SSO sign-in creates their account. The role comes from + # the abs_groups claim, which grants the listener role to every signer; only the + # authentik admin group escalates. Revoking a listener therefore happens HERE in + # ABS (disable or delete the user), never by removing an IdP group membership. "authOpenIDAutoRegister": True, "authOpenIDGroupClaim": "abs_groups", # The app's own scheme lives HERE, never in the IdP: ABS swaps it for diff --git a/scripts/authentik-audiobookshelf-sso.sh b/scripts/authentik-audiobookshelf-sso.sh index d7b73de..e163b50 100755 --- a/scripts/authentik-audiobookshelf-sso.sh +++ b/scripts/authentik-audiobookshelf-sso.sh @@ -70,9 +70,12 @@ # ABS_OIDC_CLIENT_ID provider/client id (audiobookshelf) # ABS_OIDC_GROUPS_CLAIM claim ABS reads for roles (abs_groups) # ABS_OIDC_USERNAME authentik user made admin (akadmin) -# ABS_OIDC_USERS space-separated authentik usernames given the USER group -# (default: nobody — an empty list is fail-closed, and the -# household decides who listens to audiobooks) +# There is deliberately NO user list. The claim grants the LISTENER role to every +# authentik account that signs in (2026-11-03), so onboarding a listener is exactly +# "create the authentik account". Before this, a household account authenticated at +# the IdP and was then refused by ABS for being in no group — a policy decision that +# read as a broken login. Revocation lives on the ABS side (disable or delete the +# user there): a group can no longer withhold a role the claim always sends. # ABS_E2E_ADMIN authentik user kept in the ADMIN group for the Playwright gate # (qa-e2e-admin — tests/e2e/specs/23-audiobookshelf-sso.spec.ts # signs in as this identity, so a re-run restores the fixture) @@ -97,53 +100,67 @@ SECRET="${ABS_OIDC_CLIENT_SECRET:?set ABS_OIDC_CLIENT_SECRET (see the header)}" CLIENT_ID="${ABS_OIDC_CLIENT_ID:-audiobookshelf}" CLAIM="${ABS_OIDC_GROUPS_CLAIM:-abs_groups}" ADMIN_GROUP="audiobookshelf_admins" -USER_GROUP="audiobookshelf_users" 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://audiobooks.franksdank.cloud/audiobookshelf}" HOST="${AUTHENTIK_HOST:-dankclaw}" -# The claim expression: translate group membership into ABS role names. It is stored -# verbatim so a later read of the provider shows exactly this policy. +# The claim expression: every signer is a listener; only the admin group escalates. +# Stored verbatim so a later read of the provider shows exactly this policy. The default +# lives HERE and nowhere else — ABS reads this list off the token, so an account in no +# Audiobookshelf group still arrives with ["user"] and is auto-registered as a listener +# instead of being refused after a successful authentication. read -r -d '' EXPR < %s" % ( + u.username, sorted(g.name for g in u.groups.all()), + _ns["_probe"](user=u, request=None))) # --- verification: the claim the callback will actually receive --------------------- -for probe in [root] + ([e2e] if e2e else []) + [User.objects.get(username=n) for n in MEMBERS]: +for probe in [root] + ([e2e] if e2e else []): print("claim for %s -> %s" % (probe.username, _ns["_probe"](user=probe, request=None))) p.refresh_from_db() print("provider %s | client_id %s | flow %s | grants %s | uris %s" % (