defmodule ToweropsWeb.Admin.SecurityLive.Index do @moduledoc """ Admin page for managing IP-based brute force protection. Provides two main functions: 1. Allowlist management - Add/remove IPs and CIDR ranges that bypass protection 2. Denylist management - View and manually unblock banned IPs """ use ToweropsWeb, :live_view alias Towerops.Security.BruteForce @impl true def mount(_params, _session, socket) do if connected?(socket) do Phoenix.PubSub.subscribe(Towerops.PubSub, "security:whitelist") Phoenix.PubSub.subscribe(Towerops.PubSub, "security:blocks") end {:ok, socket |> assign(:page_title, t("Security - IP Access Control")) |> assign(:current_tab, "whitelist")} end @impl true def handle_params(params, _url, socket) do tab = Map.get(params, "tab", "whitelist") filter = Map.get(params, "filter", "all") {:noreply, socket |> assign(:current_tab, tab) |> assign(:filter, filter) |> load_data()} end @impl true def handle_event("change_tab", %{"tab" => tab}, socket) do {:noreply, push_patch(socket, to: ~p"/admin/security?tab=#{tab}")} end def handle_event("change_filter", %{"filter" => filter}, socket) do {:noreply, push_patch(socket, to: ~p"/admin/security?tab=#{socket.assigns.current_tab}&filter=#{filter}")} end def handle_event("show_whitelist_form", _params, socket) do {:noreply, socket |> assign(:show_whitelist_form, true) |> assign(:whitelist_form, to_form(%{}))} end def handle_event("hide_whitelist_form", _params, socket) do {:noreply, assign(socket, :show_whitelist_form, false)} end def handle_event("add_whitelist", %{"whitelist" => params}, socket) do case BruteForce.add_to_whitelist( params["ip_or_cidr"], params["description"], socket.assigns.current_user ) do {:ok, _entry} -> {:noreply, socket |> put_flash(:info, t("IP/CIDR added to allowlist")) |> assign(:show_whitelist_form, false) |> load_data()} {:error, changeset} -> {:noreply, socket |> assign(:whitelist_form, to_form(Map.put(changeset, :action, :validate))) |> put_flash(:error, t("Failed to add to allowlist"))} end end def handle_event("remove_whitelist", %{"id" => id}, socket) do case BruteForce.remove_from_whitelist(id) do :ok -> {:noreply, socket |> put_flash(:info, t("Removed from allowlist")) |> load_data()} {:error, _} -> {:noreply, put_flash(socket, :error, t("Failed to remove from allowlist"))} end end def handle_event("unblock_ip", %{"ip" => ip_address}, socket) do case BruteForce.manually_unblock(ip_address) do {:ok, _} -> {:noreply, socket |> put_flash(:info, t("IP %{ip_address} unblocked", ip_address: ip_address)) |> load_data()} {:error, _} -> {:noreply, put_flash(socket, :error, t("Failed to unblock IP"))} end end @impl true def handle_info(:whitelist_updated, socket) do {:noreply, load_data(socket)} end def handle_info(:blocks_updated, socket) do {:noreply, load_data(socket)} end defp load_data(socket) do whitelist = BruteForce.list_whitelist() blocked_ips = case socket.assigns[:filter] || "all" do "permanent" -> BruteForce.list_blocked_ips(:permanent) "temporary" -> BruteForce.list_blocked_ips(:temporary) _ -> BruteForce.list_blocked_ips(:all) end socket |> assign(:whitelist, whitelist) |> assign(:blocked_ips, blocked_ips) |> assign(:show_whitelist_form, Map.get(socket.assigns, :show_whitelist_form, false)) end end