UserLive.Form: gate Member-Linking to admin, use :update for non-admin
- Show Member-Linking UI only when can_manage_member_linking (admin) - perform_member_link_action runs only for admin - assign_form: non-admin uses :update (email), admin uses :update_user - Load members for linking only when can_manage_member_linking
This commit is contained in:
parent
14fa873640
commit
06d6531569
1 changed files with 160 additions and 130 deletions
|
|
@ -36,6 +36,7 @@ defmodule MvWeb.UserLive.Form do
|
|||
require Jason
|
||||
|
||||
import MvWeb.LiveHelpers, only: [current_actor: 1, submit_form: 3]
|
||||
import MvWeb.Authorization, only: [can?: 3]
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
|
|
@ -125,7 +126,8 @@ defmodule MvWeb.UserLive.Form do
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Member Linking Section -->
|
||||
<!-- Member Linking Section (admin only: only admins can link/unlink users to members) -->
|
||||
<%= if @can_manage_member_linking do %>
|
||||
<div class="mt-6">
|
||||
<h2 class="mb-3 text-base font-semibold">{gettext("Linked Member")}</h2>
|
||||
|
||||
|
|
@ -214,7 +216,9 @@ defmodule MvWeb.UserLive.Form do
|
|||
)
|
||||
]}
|
||||
>
|
||||
<p class="font-medium">{MvWeb.Helpers.MemberHelpers.display_name(member)}</p>
|
||||
<p class="font-medium">
|
||||
{MvWeb.Helpers.MemberHelpers.display_name(member)}
|
||||
</p>
|
||||
<p class="text-sm text-base-content/70">{member.email}</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -248,6 +252,7 @@ defmodule MvWeb.UserLive.Form do
|
|||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-4">
|
||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
||||
|
|
@ -289,14 +294,19 @@ defmodule MvWeb.UserLive.Form do
|
|||
end
|
||||
|
||||
defp mount_continue(user, params, socket) do
|
||||
actor = current_actor(socket)
|
||||
action = if is_nil(user), do: gettext("New"), else: gettext("Edit")
|
||||
page_title = action <> " " <> gettext("User")
|
||||
|
||||
# Only admins can link/unlink users to members (permission docs; prevents privilege escalation).
|
||||
can_manage_member_linking = can?(actor, :destroy, Mv.Accounts.User)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:return_to, return_to(params["return_to"]))
|
||||
|> assign(user: user)
|
||||
|> assign(:page_title, page_title)
|
||||
|> assign(:can_manage_member_linking, can_manage_member_linking)
|
||||
|> assign(:show_password_fields, false)
|
||||
|> assign(:member_search_query, "")
|
||||
|> assign(:available_members, [])
|
||||
|
|
@ -329,9 +339,9 @@ defmodule MvWeb.UserLive.Form do
|
|||
def handle_event("validate", %{"user" => user_params}, socket) do
|
||||
validated_form = AshPhoenix.Form.validate(socket.assigns.form, user_params)
|
||||
|
||||
# Reload members if email changed (for email-match priority)
|
||||
# Reload members if email changed (for email-match priority; only when member linking UI is shown)
|
||||
socket =
|
||||
if Map.has_key?(user_params, "email") do
|
||||
if Map.has_key?(user_params, "email") and socket.assigns[:can_manage_member_linking] do
|
||||
user_email = user_params["email"]
|
||||
members = load_members_for_linking(user_email, socket.assigns.member_search_query, socket)
|
||||
|
||||
|
|
@ -480,6 +490,8 @@ defmodule MvWeb.UserLive.Form do
|
|||
end
|
||||
|
||||
defp perform_member_link_action(socket, user, actor) do
|
||||
# Only admins may link/unlink (backend policy also restricts update_user; UI must not call it).
|
||||
if can?(actor, :destroy, Mv.Accounts.User) do
|
||||
cond do
|
||||
# Selected member ID takes precedence (new link)
|
||||
socket.assigns.selected_member_id ->
|
||||
|
|
@ -495,6 +507,9 @@ defmodule MvWeb.UserLive.Form do
|
|||
true ->
|
||||
{:ok, user}
|
||||
end
|
||||
else
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_save_success(socket, updated_user) do
|
||||
|
|
@ -552,13 +567,28 @@ defmodule MvWeb.UserLive.Form do
|
|||
end
|
||||
|
||||
@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
|
||||
defp assign_form(
|
||||
%{
|
||||
assigns: %{
|
||||
user: user,
|
||||
show_password_fields: show_password_fields,
|
||||
can_manage_member_linking: can_manage_member_linking
|
||||
}
|
||||
} = 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
|
||||
# For existing users: admin uses update_user (email + member); non-admin uses update (email only).
|
||||
# Password change uses admin_set_password for both.
|
||||
action =
|
||||
cond do
|
||||
show_password_fields -> :admin_set_password
|
||||
can_manage_member_linking -> :update_user
|
||||
true -> :update
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue