towerops/lib/towerops_web/live/admin/security_live/index.ex

129 lines
3.7 KiB
Elixir

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, "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, "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, "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, "Removed from allowlist")
|> load_data()}
{:error, _} ->
{:noreply, put_flash(socket, :error, "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, "IP #{ip_address} unblocked")
|> load_data()}
{:error, _} ->
{:noreply, put_flash(socket, :error, "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