From 4c846f8bbafcc31a740b2fa06dab2ffa58f584b8 Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 23 Jan 2026 20:03:44 +0100 Subject: [PATCH] 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 --- lib/mv/helpers/system_actor.ex | 39 ++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/mv/helpers/system_actor.ex b/lib/mv/helpers/system_actor.ex index 7a8ab8b..565c2ef 100644 --- a/lib/mv/helpers/system_actor.ex +++ b/lib/mv/helpers/system_actor.ex @@ -271,11 +271,12 @@ defmodule Mv.Helpers.SystemActor do end # 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} defp find_admin_role do alias Mv.Authorization - case Authorization.list_roles() do + case Authorization.list_roles(authorize?: false) do {:ok, roles} -> case Enum.find(roles, &(&1.permission_set_name == "admin")) do nil -> {:error, :not_found} @@ -305,16 +306,20 @@ defmodule Mv.Helpers.SystemActor do end # Attempts to create admin role + # SECURITY: Uses authorize?: false for bootstrap role creation. @spec create_admin_role() :: {:ok, Mv.Authorization.Role.t()} | {:error, :already_exists | term()} defp create_admin_role do alias Mv.Authorization - case Authorization.create_role(%{ - name: "Admin", - description: "Administrator with full access", - permission_set_name: "admin" - }) do + case Authorization.create_role( + %{ + name: "Admin", + description: "Administrator with full access", + permission_set_name: "admin" + }, + authorize?: false + ) do {:ok, role} -> {:ok, role} @@ -327,11 +332,12 @@ defmodule Mv.Helpers.SystemActor do end # 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() defp find_existing_admin_role do alias Mv.Authorization - case Authorization.list_roles() do + case Authorization.list_roles(authorize?: false) do {:ok, roles} -> Enum.find(roles, &(&1.permission_set_name == "admin")) || 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 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()}, upsert?: true, - upsert_identity: :unique_email + upsert_identity: :unique_email, + authorize?: false ) |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) - |> Ash.update!() - |> Ash.load!(:role, domain: Mv.Accounts) + |> Ash.update!(authorize?: false) + |> Ash.load!(:role, domain: Mv.Accounts, authorize?: false) end # Finds a user by email address @@ -376,9 +390,12 @@ defmodule Mv.Helpers.SystemActor do end # 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() 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} -> validate_admin_role(user_with_role)