defmodule Mv.OidcRoleSync do @moduledoc """ Syncs user role from OIDC user_info (e.g. groups claim → Admin role). Used after OIDC registration (register_with_rauthy) and on sign-in so that users in the configured admin group get the Admin role; others get Mitglied. Configure via OIDC_ADMIN_GROUP_NAME and OIDC_GROUPS_CLAIM (see OidcRoleSyncConfig). """ alias Mv.Accounts.User alias Mv.Authorization.Role alias Mv.OidcRoleSyncConfig @doc """ Applies Admin or Mitglied role to the user based on OIDC user_info (groups claim). - If OIDC_ADMIN_GROUP_NAME is not configured: no-op, returns :ok without changing the user. - If user_info contains the configured admin group (under OIDC_GROUPS_CLAIM): assigns Admin role. - Otherwise: assigns Mitglied role (downgrade if user was Admin). user_info is a map (e.g. from JWT claims) and may use string keys. Groups can be a list of strings or a single string. ## Examples user_info = %{"groups" => ["mila-admin"]} OidcRoleSync.apply_admin_role_from_user_info(user, user_info) user_info = %{"ak_groups" => ["other"]} # with OIDC_GROUPS_CLAIM=ak_groups OidcRoleSync.apply_admin_role_from_user_info(user, user_info) """ @spec apply_admin_role_from_user_info(User.t(), map()) :: :ok def apply_admin_role_from_user_info(user, user_info) when is_map(user_info) do admin_group = OidcRoleSyncConfig.oidc_admin_group_name() if is_nil(admin_group) or admin_group == "" do :ok else claim = OidcRoleSyncConfig.oidc_groups_claim() groups = groups_from_user_info(user_info, claim) target_role = if admin_group in groups, do: :admin, else: :mitglied set_user_role(user, target_role) end end defp groups_from_user_info(user_info, claim) do case user_info[claim] do nil -> [] list when is_list(list) -> Enum.map(list, &to_string/1) single when is_binary(single) -> [single] _ -> [] end end defp set_user_role(user, :admin) do case Role.get_admin_role() do {:ok, %Role{} = role} -> do_set_role(user, role) _ -> :ok end end defp set_user_role(user, :mitglied) do case Role.get_mitglied_role() do {:ok, %Role{} = role} -> do_set_role(user, role) _ -> :ok end end defp do_set_role(user, role) do user |> Ash.Changeset.for_update(:set_role_from_oidc_sync, %{role_id: role.id}) |> Ash.Changeset.set_context(%{private: %{oidc_role_sync: true}}) |> Ash.update!(domain: Mv.Accounts, context: %{private: %{oidc_role_sync: true}}) :ok end end