feat(system_actor): add system_user?/1 and normalize email

Case-insensitive email comparison for system-actor detection.
This commit is contained in:
Moritz 2026-01-27 17:37:28 +01:00 committed by moritz
parent 41bc031cc6
commit 7d33acde9f
2 changed files with 35 additions and 5 deletions

View file

@ -172,6 +172,31 @@ defmodule Mv.Helpers.SystemActor do
end end
end end
@doc """
Returns whether the given user is the system actor user (case-insensitive email match).
Use this instead of ad-hoc `to_string(user.email) == system_user_email()` so
comparisons are consistent and case-insensitive everywhere.
## Returns
- `boolean()` - true if user's email matches system user email (case-insensitive)
## Examples
iex> Mv.Helpers.SystemActor.system_user?(user_with_system_email)
true
iex> Mv.Helpers.SystemActor.system_user?(other_user)
false
"""
@spec system_user?(Mv.Accounts.User.t() | map() | nil) :: boolean()
def system_user?(%{email: email}) when not is_nil(email) do
normalized_email(to_string(email)) == normalized_system_user_email()
end
def system_user?(_), do: false
@doc """ @doc """
Returns the email address of the system user. Returns the email address of the system user.
@ -191,6 +216,11 @@ defmodule Mv.Helpers.SystemActor do
@spec system_user_email() :: String.t() @spec system_user_email() :: String.t()
def system_user_email, do: system_user_email_config() def system_user_email, do: system_user_email_config()
# Case-insensitive normalized form for comparisons
defp normalized_system_user_email, do: normalized_email(system_user_email_config())
defp normalized_email(email) when is_binary(email), do: String.downcase(email)
# Returns the system user email from environment variable or default # Returns the system user email from environment variable or default
# This allows configuration via SYSTEM_ACTOR_EMAIL env var # This allows configuration via SYSTEM_ACTOR_EMAIL env var
@spec system_user_email_config() :: String.t() @spec system_user_email_config() :: String.t()
@ -368,7 +398,7 @@ defmodule Mv.Helpers.SystemActor do
upsert_identity: :unique_email, upsert_identity: :unique_email,
authorize?: false authorize?: false
) )
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.for_update(:update_internal, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!(authorize?: false) |> Ash.update!(authorize?: false)
|> Ash.load!(:role, domain: Mv.Accounts, authorize?: false) |> Ash.load!(:role, domain: Mv.Accounts, authorize?: false)

View file

@ -57,7 +57,7 @@ defmodule Mv.Helpers.SystemActorTest do
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do |> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
{:ok, user} when not is_nil(user) -> {:ok, user} when not is_nil(user) ->
user user
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.for_update(:update_internal, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!(authorize?: false) |> Ash.update!(authorize?: false)
|> Ash.load!(:role, domain: Mv.Accounts, authorize?: false) |> Ash.load!(:role, domain: Mv.Accounts, authorize?: false)
@ -68,7 +68,7 @@ defmodule Mv.Helpers.SystemActorTest do
upsert_identity: :unique_email, upsert_identity: :unique_email,
authorize?: false authorize?: false
) )
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.for_update(:update_internal, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!(authorize?: false) |> Ash.update!(authorize?: false)
|> Ash.load!(:role, domain: Mv.Accounts, authorize?: false) |> Ash.load!(:role, domain: Mv.Accounts, authorize?: false)
@ -373,9 +373,9 @@ defmodule Mv.Helpers.SystemActorTest do
system_actor = SystemActor.get_system_actor() system_actor = SystemActor.get_system_actor()
# Assign wrong role to system user # Assign wrong role to system user (use :update_internal so bootstrap-style update is allowed)
system_user system_user
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.for_update(:update_internal, %{})
|> Ash.Changeset.manage_relationship(:role, read_only_role, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:role, read_only_role, type: :append_and_remove)
|> Ash.update!(actor: system_actor) |> Ash.update!(actor: system_actor)