- Add organization membership check before API token creation - Fix admin security allowlist form reading current_user instead of current_scope.user - Use recursive File.ls instead of Path.wildcard to include hidden files in MIB validation - Add upload size check before ZIP extraction in MIB controller - Centralize CSP in SecurityHeaders plug with per-request nonces instead of 'unsafe-inline'
130 lines
3.7 KiB
Elixir
130 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, 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_scope.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
|