authentik/vanguard-profile-scope-mapping.py (new, and the first time that mapping is version-controlled rather than pasted from the docs) emits the Discord snowflake from the account's source connection alongside the inherited groups, so the bot never has to match a name. docker-compose.yml: discord-bot moves to its own network. It trusts the identity headers Caddy injects, and it shared the default bridge with jellyfin, copyparty, syncthing, gitea, act_runner and cadvisor, any of which could reach :8000 directly and forge them. It needs no sibling container: Caddy uses the published loopback port and MariaDB is reached through host.docker.internal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
39 lines
2.1 KiB
Python
39 lines
2.1 KiB
Python
# Authentik SCOPE MAPPING (not a policy): adds two things the apps behind the proxy need and the built-in
|
|
# mappings do not provide.
|
|
#
|
|
# Paste into: Customization -> Property Mappings -> Create -> Scope Mapping
|
|
# Name: Vanguard profile (nested groups + discord id)
|
|
# Scope name: profile
|
|
# Then tick it, NEXT TO the managed "authentik default OAuth Mapping: OpenID 'profile'", on every provider the
|
|
# community uses: the "Vanguard sites" proxy provider, Jellyfin and Gitea (Advanced protocol settings -> Scopes).
|
|
# Claims from several mappings are merged, so both run and neither needs removing. Proxy providers re-add the
|
|
# managed one on every save, which is why this adds to it rather than replacing it.
|
|
#
|
|
# 1. groups: the managed mapping emits ak_groups, i.e. DIRECT memberships only, so a group inherited through a
|
|
# parent never reaches the apps. This emits only the inherited ones; merged with the managed mapping's direct
|
|
# ones the apps see the full set, with no duplicates.
|
|
#
|
|
# 2. discord_id: the Discord snowflake, taken from the source connection Authentik already stores when it links
|
|
# an account. It reaches the apps in X-Authentik-Jwt. The Discord bot uses it to identify the caller exactly,
|
|
# instead of matching an Authentik username against Discord names — names are mutable, reusable, and server
|
|
# nicknames are not unique, so name matching can both lock the right person out and let the wrong one in.
|
|
#
|
|
# Takes effect on the next login.
|
|
|
|
DISCORD_SOURCE_SLUG = "discord"
|
|
|
|
direct = set(request.user.ak_groups.all())
|
|
inherited = sorted(group.name for group in request.user.all_groups() if group not in direct)
|
|
|
|
discord_id = None
|
|
for attribute in ("usersourceconnection_set", "source_connections"): # the related name differs by version
|
|
related = getattr(request.user, attribute, None)
|
|
if related is None:
|
|
continue
|
|
connection = (related.filter(source__slug=DISCORD_SOURCE_SLUG).first() or related.first())
|
|
if connection is not None:
|
|
discord_id = getattr(connection, "identifier", None) or None
|
|
break
|
|
|
|
return {"groups": inherited, "discord_id": discord_id}
|