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
|
require Jason
|
||||||
|
|
||||||
import MvWeb.LiveHelpers, only: [current_actor: 1, submit_form: 3]
|
import MvWeb.LiveHelpers, only: [current_actor: 1, submit_form: 3]
|
||||||
|
import MvWeb.Authorization, only: [can?: 3]
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
|
|
@ -125,7 +126,8 @@ defmodule MvWeb.UserLive.Form do
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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">
|
<div class="mt-6">
|
||||||
<h2 class="mb-3 text-base font-semibold">{gettext("Linked Member")}</h2>
|
<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>
|
<p class="text-sm text-base-content/70">{member.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -248,6 +252,7 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
||||||
|
|
@ -289,14 +294,19 @@ defmodule MvWeb.UserLive.Form do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp mount_continue(user, params, socket) do
|
defp mount_continue(user, params, socket) do
|
||||||
|
actor = current_actor(socket)
|
||||||
action = if is_nil(user), do: gettext("New"), else: gettext("Edit")
|
action = if is_nil(user), do: gettext("New"), else: gettext("Edit")
|
||||||
page_title = action <> " " <> gettext("User")
|
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,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:return_to, return_to(params["return_to"]))
|
|> assign(:return_to, return_to(params["return_to"]))
|
||||||
|> assign(user: user)
|
|> assign(user: user)
|
||||||
|> assign(:page_title, page_title)
|
|> assign(:page_title, page_title)
|
||||||
|
|> assign(:can_manage_member_linking, can_manage_member_linking)
|
||||||
|> assign(:show_password_fields, false)
|
|> assign(:show_password_fields, false)
|
||||||
|> assign(:member_search_query, "")
|
|> assign(:member_search_query, "")
|
||||||
|> assign(:available_members, [])
|
|> assign(:available_members, [])
|
||||||
|
|
@ -329,9 +339,9 @@ defmodule MvWeb.UserLive.Form do
|
||||||
def handle_event("validate", %{"user" => user_params}, socket) do
|
def handle_event("validate", %{"user" => user_params}, socket) do
|
||||||
validated_form = AshPhoenix.Form.validate(socket.assigns.form, user_params)
|
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 =
|
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"]
|
user_email = user_params["email"]
|
||||||
members = load_members_for_linking(user_email, socket.assigns.member_search_query, socket)
|
members = load_members_for_linking(user_email, socket.assigns.member_search_query, socket)
|
||||||
|
|
||||||
|
|
@ -480,6 +490,8 @@ defmodule MvWeb.UserLive.Form do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp perform_member_link_action(socket, user, actor) do
|
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
|
cond do
|
||||||
# Selected member ID takes precedence (new link)
|
# Selected member ID takes precedence (new link)
|
||||||
socket.assigns.selected_member_id ->
|
socket.assigns.selected_member_id ->
|
||||||
|
|
@ -495,6 +507,9 @@ defmodule MvWeb.UserLive.Form do
|
||||||
true ->
|
true ->
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_save_success(socket, updated_user) do
|
defp handle_save_success(socket, updated_user) do
|
||||||
|
|
@ -552,13 +567,28 @@ defmodule MvWeb.UserLive.Form do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec assign_form(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
|
@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)
|
actor = current_actor(socket)
|
||||||
|
|
||||||
form =
|
form =
|
||||||
if user do
|
if user do
|
||||||
# For existing users, use admin password action if password fields are shown
|
# For existing users: admin uses update_user (email + member); non-admin uses update (email only).
|
||||||
action = if show_password_fields, do: :admin_set_password, else: :update_user
|
# 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)
|
AshPhoenix.Form.for_update(user, action, domain: Mv.Accounts, as: "user", actor: actor)
|
||||||
else
|
else
|
||||||
# For new users, use password registration if password fields are shown
|
# For new users, use password registration if password fields are shown
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue