Add authorize?: false to SystemActor bootstrap operations

- Role lookup and creation (find_admin_role, create_admin_role)
- System user creation and role assignment
- Role loading during initialization
This commit is contained in:
Moritz 2026-01-23 20:03:44 +01:00
parent 15d1a75571
commit 4c846f8bba
Signed by: moritz
GPG key ID: 1020A035E5DD0824

View file

@ -271,11 +271,12 @@ defmodule Mv.Helpers.SystemActor do
end end
# Finds admin role in existing roles # Finds admin role in existing roles
# SECURITY: Uses authorize?: false for bootstrap role lookup.
@spec find_admin_role() :: {:ok, Mv.Authorization.Role.t()} | {:error, :not_found} @spec find_admin_role() :: {:ok, Mv.Authorization.Role.t()} | {:error, :not_found}
defp find_admin_role do defp find_admin_role do
alias Mv.Authorization alias Mv.Authorization
case Authorization.list_roles() do case Authorization.list_roles(authorize?: false) do
{:ok, roles} -> {:ok, roles} ->
case Enum.find(roles, &(&1.permission_set_name == "admin")) do case Enum.find(roles, &(&1.permission_set_name == "admin")) do
nil -> {:error, :not_found} nil -> {:error, :not_found}
@ -305,16 +306,20 @@ defmodule Mv.Helpers.SystemActor do
end end
# Attempts to create admin role # Attempts to create admin role
# SECURITY: Uses authorize?: false for bootstrap role creation.
@spec create_admin_role() :: @spec create_admin_role() ::
{:ok, Mv.Authorization.Role.t()} | {:error, :already_exists | term()} {:ok, Mv.Authorization.Role.t()} | {:error, :already_exists | term()}
defp create_admin_role do defp create_admin_role do
alias Mv.Authorization alias Mv.Authorization
case Authorization.create_role(%{ case Authorization.create_role(
name: "Admin", %{
description: "Administrator with full access", name: "Admin",
permission_set_name: "admin" description: "Administrator with full access",
}) do permission_set_name: "admin"
},
authorize?: false
) do
{:ok, role} -> {:ok, role} ->
{:ok, role} {:ok, role}
@ -327,11 +332,12 @@ defmodule Mv.Helpers.SystemActor do
end end
# Finds existing admin role after creation attempt failed due to race condition # Finds existing admin role after creation attempt failed due to race condition
# SECURITY: Uses authorize?: false for bootstrap role lookup.
@spec find_existing_admin_role() :: Mv.Authorization.Role.t() | no_return() @spec find_existing_admin_role() :: Mv.Authorization.Role.t() | no_return()
defp find_existing_admin_role do defp find_existing_admin_role do
alias Mv.Authorization alias Mv.Authorization
case Authorization.list_roles() do case Authorization.list_roles(authorize?: false) do
{:ok, roles} -> {:ok, roles} ->
Enum.find(roles, &(&1.permission_set_name == "admin")) || Enum.find(roles, &(&1.permission_set_name == "admin")) ||
raise "Admin role should exist but was not found" raise "Admin role should exist but was not found"
@ -350,14 +356,22 @@ defmodule Mv.Helpers.SystemActor do
defp create_system_user_with_role(admin_role) do defp create_system_user_with_role(admin_role) do
alias Mv.Accounts alias Mv.Accounts
# SECURITY: Uses authorize?: false for bootstrap user creation.
# This is necessary because we're creating the system actor itself,
# which would otherwise be needed for authorization (chicken-and-egg).
# This is safe because:
# 1. Only creates system user with known email
# 2. Only called during system actor initialization (bootstrap)
# 3. Once created, all subsequent operations use proper authorization
Accounts.create_user!(%{email: system_user_email_config()}, Accounts.create_user!(%{email: system_user_email_config()},
upsert?: true, upsert?: true,
upsert_identity: :unique_email upsert_identity: :unique_email,
authorize?: false
) )
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!() |> Ash.update!(authorize?: false)
|> Ash.load!(:role, domain: Mv.Accounts) |> Ash.load!(:role, domain: Mv.Accounts, authorize?: false)
end end
# Finds a user by email address # Finds a user by email address
@ -376,9 +390,12 @@ defmodule Mv.Helpers.SystemActor do
end end
# Loads a user with their role preloaded (required for authorization) # Loads a user with their role preloaded (required for authorization)
# SECURITY: Uses authorize?: false for bootstrap role loading.
# This is necessary because loading the role is part of system actor initialization,
# which would otherwise require an actor (chicken-and-egg).
@spec load_user_with_role(Mv.Accounts.User.t()) :: Mv.Accounts.User.t() | no_return() @spec load_user_with_role(Mv.Accounts.User.t()) :: Mv.Accounts.User.t() | no_return()
defp load_user_with_role(user) do defp load_user_with_role(user) do
case Ash.load(user, :role, domain: Mv.Accounts) do case Ash.load(user, :role, domain: Mv.Accounts, authorize?: false) do
{:ok, user_with_role} -> {:ok, user_with_role} ->
validate_admin_role(user_with_role) validate_admin_role(user_with_role)