Use current_actor/1 helper in all LiveViews

Replace inconsistent actor access patterns with current_actor/1 helper
and ensure actor is passed to all Ash operations for proper authorization.
This commit is contained in:
Moritz 2026-01-09 05:26:06 +01:00
parent 74fe60f768
commit cd7e6b0843
Signed by: moritz
GPG key ID: 1020A035E5DD0824
9 changed files with 268 additions and 57 deletions

View file

@ -33,6 +33,9 @@ defmodule MvWeb.UserLive.Form do
"""
use MvWeb, :live_view
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
import MvWeb.LiveHelpers, only: [current_actor: 1]
@impl true
def render(assigns) do
~H"""
@ -258,10 +261,12 @@ defmodule MvWeb.UserLive.Form do
@impl true
def mount(params, _session, socket) do
actor = current_actor(socket)
user =
case params["id"] do
nil -> nil
id -> Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member])
id -> Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member], actor: actor)
end
action = if is_nil(user), do: gettext("New"), else: gettext("Edit")
@ -307,7 +312,7 @@ defmodule MvWeb.UserLive.Form do
socket =
if Map.has_key?(user_params, "email") do
user_email = user_params["email"]
members = load_members_for_linking(user_email, socket.assigns.member_search_query)
members = load_members_for_linking(user_email, socket.assigns.member_search_query, socket)
assign(socket, form: validated_form, available_members: members)
else
@ -318,19 +323,27 @@ defmodule MvWeb.UserLive.Form do
end
def handle_event("save", %{"user" => user_params}, socket) do
actor = current_actor(socket)
# First save the user without member changes
case AshPhoenix.Form.submit(socket.assigns.form, params: user_params) do
case AshPhoenix.Form.submit(socket.assigns.form,
params: user_params,
action_opts: [actor: actor]
) do
{:ok, user} ->
# Then handle member linking/unlinking as a separate step
actor = current_actor(socket)
result =
cond do
# Selected member ID takes precedence (new link)
socket.assigns.selected_member_id ->
Mv.Accounts.update_user(user, %{member: %{id: socket.assigns.selected_member_id}})
Mv.Accounts.update_user(user, %{member: %{id: socket.assigns.selected_member_id}},
actor: actor
)
# Unlink flag is set
socket.assigns[:unlink_member] ->
Mv.Accounts.update_user(user, %{member: nil})
Mv.Accounts.update_user(user, %{member: nil}, actor: actor)
# No changes to member relationship
true ->
@ -497,18 +510,21 @@ defmodule MvWeb.UserLive.Form do
@spec assign_form(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
defp assign_form(%{assigns: %{user: user, show_password_fields: show_password_fields}} = socket) do
actor = current_actor(socket)
form =
if user do
# For existing users, use admin password action if password fields are shown
action = if show_password_fields, do: :admin_set_password, else: :update_user
AshPhoenix.Form.for_update(user, action, domain: Mv.Accounts, as: "user")
AshPhoenix.Form.for_update(user, action, domain: Mv.Accounts, as: "user", actor: actor)
else
# For new users, use password registration if password fields are shown
action = if show_password_fields, do: :register_with_password, else: :create_user
AshPhoenix.Form.for_create(Mv.Accounts.User, action,
domain: Mv.Accounts,
as: "user"
as: "user",
actor: actor
)
end
@ -524,7 +540,7 @@ defmodule MvWeb.UserLive.Form do
user = socket.assigns.user
user_email = if user, do: user.email, else: nil
members = load_members_for_linking(user_email, "")
members = load_members_for_linking(user_email, "", socket)
# Dropdown should ALWAYS be hidden initially
# It will only show when user focuses the input field (show_member_dropdown event)
@ -539,12 +555,15 @@ defmodule MvWeb.UserLive.Form do
user = socket.assigns.user
user_email = if user, do: user.email, else: nil
members = load_members_for_linking(user_email, query)
members = load_members_for_linking(user_email, query, socket)
assign(socket, available_members: members)
end
@spec load_members_for_linking(String.t() | nil, String.t() | nil) :: [Mv.Membership.Member.t()]
defp load_members_for_linking(user_email, search_query) do
@spec load_members_for_linking(String.t() | nil, String.t() | nil, Phoenix.LiveView.Socket.t()) ::
[
Mv.Membership.Member.t()
]
defp load_members_for_linking(user_email, search_query, socket) do
user_email_str = if user_email, do: to_string(user_email), else: nil
search_query_str = if search_query && search_query != "", do: search_query, else: nil
@ -555,7 +574,9 @@ defmodule MvWeb.UserLive.Form do
search_query: search_query_str
})
case Ash.read(query, domain: Mv.Membership) do
actor = current_actor(socket)
case Ash.read(query, domain: Mv.Membership, actor: actor) do
{:ok, members} ->
# Apply email match filter if user_email is provided
if user_email_str do

View file

@ -23,9 +23,13 @@ defmodule MvWeb.UserLive.Index do
use MvWeb, :live_view
import MvWeb.TableComponents
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
import MvWeb.LiveHelpers, only: [current_actor: 1]
@impl true
def mount(_params, _session, socket) do
users = Ash.read!(Mv.Accounts.User, domain: Mv.Accounts, load: [:member])
actor = current_actor(socket)
users = Ash.read!(Mv.Accounts.User, domain: Mv.Accounts, load: [:member], actor: actor)
sorted = Enum.sort_by(users, & &1.email)
{:ok,

View file

@ -26,6 +26,9 @@ defmodule MvWeb.UserLive.Show do
"""
use MvWeb, :live_view
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
import MvWeb.LiveHelpers, only: [current_actor: 1]
@impl true
def render(assigns) do
~H"""
@ -70,7 +73,8 @@ defmodule MvWeb.UserLive.Show do
@impl true
def mount(%{"id" => id}, _session, socket) do
user = Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member])
actor = current_actor(socket)
user = Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member], actor: actor)
{:ok,
socket