Per-host group gating in Caddy from X-Authentik-Groups; Discord roles -> Authentik groups policy; domain-level forward auth documented
BOT_REQUIRE_GROUP / LIVE_REQUIRE_GROUP in .env drive a header_regexp matcher after forward_auth (403 before the app). authentik/discord-roles-to-groups.py syncs Discord roles into groups at every login so access follows Discord roles without manual edits. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
54
authentik/discord-roles-to-groups.py
Normal file
54
authentik/discord-roles-to-groups.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Authentik expression policy: sync a member's Discord ROLES into Authentik GROUPS at every login.
|
||||
# Paste into: Customization -> Policies -> Create -> Expression Policy (name: discord-roles-to-groups)
|
||||
# Bind to BOTH flows (after discord-guild-member): default-source-enrollment and default-source-authentication.
|
||||
# Requires the "guilds.members.read" scope on the Discord source (source -> Protocol settings -> Scopes:
|
||||
# guilds guilds.members.read ). Existing users re-consent once at Discord.
|
||||
# The Authentik groups must already exist (Directory -> Groups). Role IDs: Discord Developer Mode ->
|
||||
# Server Settings -> Roles -> right-click a role -> Copy Role ID.
|
||||
|
||||
GUILD_ID = "000000000000000000" # <- your Discord server ID
|
||||
ROLE_TO_GROUP = { # <- Discord role id : Authentik group name
|
||||
"111111111111111111": "bot-users",
|
||||
"222222222222222222": "streamers",
|
||||
"333333333333333333": "editors",
|
||||
"444444444444444444": "admins",
|
||||
}
|
||||
|
||||
from authentik.core.models import Group
|
||||
|
||||
source = context.get("source")
|
||||
if not source or getattr(source, "provider_type", "") != "discord":
|
||||
return True # not a Discord login (e.g. akadmin): nothing to sync
|
||||
|
||||
connection = context.get("goauthentik.io/sources/connection")
|
||||
if not connection:
|
||||
return True
|
||||
|
||||
resp = requests.get(
|
||||
f"https://discord.com/api/users/@me/guilds/{GUILD_ID}/member",
|
||||
headers={"Authorization": f"Bearer {connection.access_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
ak_message(f"Could not read your Discord roles ({resp.status_code}); groups unchanged.")
|
||||
return True # don't block login on a Discord hiccup
|
||||
|
||||
roles = set(resp.json().get("roles", []))
|
||||
wanted = {g for r, g in ROLE_TO_GROUP.items() if r in roles}
|
||||
managed = set(ROLE_TO_GROUP.values())
|
||||
|
||||
# Authentication flow: the user exists -> edit memberships directly.
|
||||
user = context.get("pending_user") or (request.user if request.user.is_authenticated else None)
|
||||
if user and user.pk:
|
||||
for group in Group.objects.filter(name__in=managed):
|
||||
if group.name in wanted:
|
||||
group.users.add(user)
|
||||
else:
|
||||
group.users.remove(user)
|
||||
return True
|
||||
|
||||
# Enrollment flow: the user is created later by the user-write stage; hand it the groups to attach.
|
||||
plan = context.get("flow_plan")
|
||||
if plan is not None:
|
||||
plan.context["groups"] = list(Group.objects.filter(name__in=wanted))
|
||||
return True
|
||||
Reference in New Issue
Block a user