From 135bca4a8325cef072ce2ff958930f3c700a0c9e Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Sat, 24 Oct 2026 13:37:12 +0200 Subject: [PATCH] authentik: log in to immich with Scouts Online Adds Scouts Online 3 (login.scouting.nl) as an OIDC source. Its userinfo has the member's roles within the group; a property mapping puts every member in "MBG leden" and those with an active Kaderlid role also in "MBG kader", on every login. Members become external users. Family members can share an email address and immich needs a unique one, so those get a plus address with their member number. The immich provider now authenticates through a flow whose only stage is an identification stage with this one source and no fields, which sends people straight on to Scouts Online. The source's own flows only run through the source. --- apps/templates/authentik-helm.yaml | 16 +++ authentik/blueprint-scouting.yaml | 185 ++++++++++++++++++++++++++++- authentik/external-secret.yaml | 24 ++++ 3 files changed, 224 insertions(+), 1 deletion(-) diff --git a/apps/templates/authentik-helm.yaml b/apps/templates/authentik-helm.yaml index 1602511..685666d 100644 --- a/apps/templates/authentik-helm.yaml +++ b/apps/templates/authentik-helm.yaml @@ -142,6 +142,14 @@ spec: items: - key: client_secret path: immich-oidc-client-secret + - secret: + name: scouts-online-client + optional: true + items: + - key: client_id + path: scouts-online-client-id + - key: client_secret + path: scouts-online-client-secret volumeMounts: - name: blueprint-vault mountPath: /blueprints/custom/vault-oidc.yaml @@ -219,6 +227,14 @@ spec: items: - key: client_secret path: immich-oidc-client-secret + - secret: + name: scouts-online-client + optional: true + items: + - key: client_id + path: scouts-online-client-id + - key: client_secret + path: scouts-online-client-secret volumeMounts: - name: blueprint-vault mountPath: /blueprints/custom/vault-oidc.yaml diff --git a/authentik/blueprint-scouting.yaml b/authentik/blueprint-scouting.yaml index d83bfc3..e10b40a 100644 --- a/authentik/blueprint-scouting.yaml +++ b/authentik/blueprint-scouting.yaml @@ -12,6 +12,12 @@ data: metadata: name: immich OIDC entries: + # The source and the login flow below come from scouts-online.yaml. + - model: authentik_blueprints.metaapplyblueprint + attrs: + identifiers: + path: custom/scouting/scouts-online.yaml + required: true # Filled by the Scouts Online source on every login: every member, and # the members with an active Kaderlid role. - model: authentik_core.group @@ -50,7 +56,9 @@ data: attrs: name: immich authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + # Straight on to Scouts Online. Admins with a password log in at + # authentik first. + authentication_flow: !Find [authentik_flows.flow, [slug, scouts-online-login]] invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] client_type: confidential client_id: immich @@ -93,3 +101,178 @@ data: target: !KeyOf immich-app attrs: group: !Find [authentik_core.group, [name, "authentik Admins"]] + scouts-online.yaml: | + version: 1 + metadata: + name: Scouts Online + entries: + - model: authentik_sources_oauth.oauthsourcepropertymapping + id: user-mapping + state: present + identifiers: + name: "Scouts Online: user" + attrs: + name: "Scouts Online: user" + expression: | + # info is the userinfo of Scouts Online (https://login.scouting.nl/docs/api). + # It only holds the roles within the organisations of this connection, + # which is just the group. + from datetime import date + + today = date.today().isoformat() + roles = {role.get("id"): role for role in (info.get("roles") or {}).values()} + kader = False + for assignment in info.get("role_assignments") or []: + start = (assignment.get("start_date") or "")[:10] + end = (assignment.get("end_date") or "")[:10] + if (start and start > today) or (end and end < today): + continue + role = roles.get(assignment.get("role_id")) or {} + if "Kaderlid" in (role.get("member_type") or []): + kader = True + + member_id = info.get("member_id") + email = info.get("email") or "" + # Family members can share an email address; immich needs one per account. + if email and info.get("multiple_accounts"): + local, _, domain = email.partition("@") + email = f"{local}+{member_id}@{domain}" + + return { + "username": f"sol-{member_id}", + "email": email, + "name": info.get("name") or "", + "groups": ["MBG leden", "MBG kader"] if kader else ["MBG leden"], + "attributes": {"sol_member_id": member_id}, + } + # The source's flows can only be reached through the source. + - model: authentik_policies_expression.expressionpolicy + id: sso-only + state: present + identifiers: + name: scouts-online-sso-only + attrs: + expression: return ak_is_sso_flow + # New members become external users: they can use the apps they are + # let into, not authentik's own interface. + - model: authentik_stages_user_write.userwritestage + id: user-write + state: present + identifiers: + name: scouts-online-user-write + attrs: + user_creation_mode: always_create + user_type: external + - model: authentik_stages_user_login.userloginstage + id: user-login + state: present + identifiers: + name: scouts-online-user-login + - model: authentik_flows.flow + id: enrollment-flow + state: present + identifiers: + slug: scouts-online-enrollment + attrs: + name: Scouts Online enrollment + title: Scouting MBG + designation: enrollment + authentication: none + - model: authentik_flows.flowstagebinding + state: present + identifiers: + order: 0 + target: !KeyOf enrollment-flow + stage: !KeyOf user-write + - model: authentik_flows.flowstagebinding + state: present + identifiers: + order: 1 + target: !KeyOf enrollment-flow + stage: !KeyOf user-login + - model: authentik_policies.policybinding + state: present + identifiers: + order: 0 + target: !KeyOf enrollment-flow + policy: !KeyOf sso-only + - model: authentik_flows.flow + id: authentication-flow + state: present + identifiers: + slug: scouts-online-authentication + attrs: + name: Scouts Online authentication + title: Scouting MBG + designation: authentication + authentication: none + - model: authentik_flows.flowstagebinding + state: present + identifiers: + order: 0 + target: !KeyOf authentication-flow + stage: !KeyOf user-login + - model: authentik_policies.policybinding + state: present + identifiers: + order: 0 + target: !KeyOf authentication-flow + policy: !KeyOf sso-only + # SOL only lets in members of the organisations picked in its + # connection, and only sends roles within them. The callback URL is + # https://authentik.rubenhensen.nl/source/oauth/callback/scouts-online/ + - model: authentik_sources_oauth.oauthsource + id: source + state: present + identifiers: + slug: scouts-online + attrs: + name: Scouts Online + slug: scouts-online + enabled: true + provider_type: openidconnect + consumer_key: !File /secrets/scouting/scouts-online-client-id + consumer_secret: !File /secrets/scouting/scouts-online-client-secret + authorization_url: https://login.scouting.nl/authorize + access_token_url: https://login.scouting.nl/token + profile_url: https://login.scouting.nl/oidc/userinfo + oidc_well_known_url: https://login.scouting.nl/.well-known/openid-configuration + oidc_jwks_url: https://login.scouting.nl/.well-known/openid-jwks.json + additional_scopes: membership roles + pkce: S256 + authorization_code_auth_method: basic_auth + user_matching_mode: identifier + # Links to the groups that immich-oidc.yaml creates + group_matching_mode: name_link + user_path_template: goauthentik.io/sources/scouts-online + enrollment_flow: !KeyOf enrollment-flow + authentication_flow: !KeyOf authentication-flow + user_property_mappings: + - !KeyOf user-mapping + # Where immich sends people who aren't logged in. An identification + # stage without fields and with one source goes straight to it. + - model: authentik_stages_identification.identificationstage + id: identification + state: present + identifiers: + name: scouts-online-identification + attrs: + user_fields: [] + sources: + - !KeyOf source + show_source_labels: true + - model: authentik_flows.flow + id: login-flow + state: present + identifiers: + slug: scouts-online-login + attrs: + name: Scouts Online login + title: Scouting MBG + designation: authentication + - model: authentik_flows.flowstagebinding + state: present + identifiers: + order: 0 + target: !KeyOf login-flow + stage: !KeyOf identification diff --git a/authentik/external-secret.yaml b/authentik/external-secret.yaml index d70dd54..e035121 100644 --- a/authentik/external-secret.yaml +++ b/authentik/external-secret.yaml @@ -172,3 +172,27 @@ spec: remoteRef: key: kv/immich-oidc property: client_secret +--- +# The Scouts Online connection (login.scouting.nl, "Beheerde websites"), +# mounted as files for blueprint-scouting.yaml. +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: scouts-online-client +spec: + secretStoreRef: + name: vault-backend + kind: ClusterSecretStore + refreshInterval: 15m + target: + name: scouts-online-client + creationPolicy: Owner + data: + - secretKey: client_id + remoteRef: + key: kv/scouts-online + property: client_id + - secretKey: client_secret + remoteRef: + key: kv/scouts-online + property: client_secret