Add admin authorization check for regenerate cycles button

Restrict UI access to cycle regeneration to administrators only
to prevent policy bypass via user interface
This commit is contained in:
Moritz 2026-01-21 08:02:38 +01:00 committed by Simon
parent 15bf76ab25
commit 214b84b9b3
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2

View file

@ -554,45 +554,55 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
end end
def handle_event("regenerate_cycles", _params, socket) do def handle_event("regenerate_cycles", _params, socket) do
socket = assign(socket, :regenerating, true) actor = current_actor(socket)
member = socket.assigns.member
case CycleGenerator.generate_cycles_for_member(member.id) do # SECURITY: Only admins can manually regenerate cycles via UI
{:ok, _new_cycles, _notifications} -> # Cycle generation itself uses system actor, but UI access should be restricted
# Reload member with cycles if actor.role && actor.role.permission_set_name == "admin" do
actor = current_actor(socket) socket = assign(socket, :regenerating, true)
member = socket.assigns.member
updated_member = case CycleGenerator.generate_cycles_for_member(member.id) do
member {:ok, _new_cycles, _notifications} ->
|> Ash.load!( # Reload member with cycles
[ actor = current_actor(socket)
:membership_fee_type,
membership_fee_cycles: [:membership_fee_type]
],
actor: actor
)
cycles = updated_member =
Enum.sort_by( member
updated_member.membership_fee_cycles || [], |> Ash.load!(
& &1.cycle_start, [
{:desc, Date} :membership_fee_type,
) membership_fee_cycles: [:membership_fee_type]
],
actor: actor
)
send(self(), {:member_updated, updated_member}) cycles =
Enum.sort_by(
updated_member.membership_fee_cycles || [],
& &1.cycle_start,
{:desc, Date}
)
{:noreply, send(self(), {:member_updated, updated_member})
socket
|> assign(:member, updated_member)
|> assign(:cycles, cycles)
|> assign(:regenerating, false)
|> put_flash(:info, gettext("Cycles regenerated successfully"))}
{:error, error} -> {:noreply,
{:noreply, socket
socket |> assign(:member, updated_member)
|> assign(:regenerating, false) |> assign(:cycles, cycles)
|> put_flash(:error, format_error(error))} |> assign(:regenerating, false)
|> put_flash(:info, gettext("Cycles regenerated successfully"))}
{:error, error} ->
{:noreply,
socket
|> assign(:regenerating, false)
|> put_flash(:error, format_error(error))}
end
else
{:noreply,
socket
|> put_flash(:error, gettext("Only administrators can regenerate cycles"))}
end end
end end