security fixes

This commit is contained in:
Graham McIntire 2026-01-28 16:55:41 -06:00
parent fd4f8b2342
commit 58b1aa50c2
6 changed files with 124 additions and 40 deletions

View file

@ -147,6 +147,16 @@ defmodule Towerops.Alerts do
|> Repo.preload([:device, :acknowledged_by])
end
@doc """
Gets a single alert. Returns nil if not found.
"""
def get_alert(id) do
case Repo.get(Alert, id) do
nil -> nil
alert -> Repo.preload(alert, [:device, :acknowledged_by])
end
end
@doc """
Acknowledges an alert.
"""

View file

@ -70,6 +70,16 @@ defmodule Towerops.Sites do
|> Repo.preload([:parent_site, :child_sites, :device])
end
@doc """
Gets a single site. Returns nil if not found.
"""
def get_site(id) do
case Repo.get(Site, id) do
nil -> nil
site -> Repo.preload(site, [:parent_site, :child_sites, :device])
end
end
@doc """
Gets a single site belonging to an organization.
"""

View file

@ -3,6 +3,7 @@ defmodule ToweropsWeb.AlertLive.Index do
use ToweropsWeb, :live_view
alias Towerops.Alerts
alias Towerops.Repo
@impl true
def mount(_params, _session, socket) do
@ -34,18 +35,31 @@ defmodule ToweropsWeb.AlertLive.Index do
@impl true
def handle_event("acknowledge", %{"id" => id}, socket) do
alert = Alerts.get_alert!(id)
organization = socket.assigns.current_organization
user_id = socket.assigns.current_scope.user.id
case Alerts.acknowledge_alert(alert, user_id) do
{:ok, _alert} ->
{:noreply,
socket
|> put_flash(:info, "Alert acknowledged")
|> load_alerts(socket.assigns.current_organization.id)}
case Alerts.get_alert(id) do
nil ->
{:noreply, put_flash(socket, :error, "Alert not found")}
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Unable to acknowledge alert")}
alert ->
# Preload device and site to check organization access
alert = Repo.preload(alert, device: [site: :organization])
if alert.device.site.organization_id == organization.id do
case Alerts.acknowledge_alert(alert, user_id) do
{:ok, _alert} ->
{:noreply,
socket
|> put_flash(:info, "Alert acknowledged")
|> load_alerts(organization.id)}
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Unable to acknowledge alert")}
end
else
{:noreply, put_flash(socket, :error, "You don't have access to this alert")}
end
end
end

View file

@ -101,11 +101,30 @@ defmodule ToweropsWeb.DeviceLive.Form do
end
defp apply_action(socket, :edit, %{"id" => id}) do
device = Devices.get_device!(id)
organization = socket.assigns.current_organization
# Preload necessary associations for agent resolution
device = Towerops.Repo.preload(device, site: [organization: :default_agent_token])
case Devices.get_device(id) do
nil ->
socket
|> put_flash(:error, "Device not found")
|> push_navigate(to: ~p"/devices")
device ->
# Preload necessary associations for agent resolution
device = Towerops.Repo.preload(device, site: [organization: :default_agent_token])
# Verify device belongs to current organization
if device.site.organization_id == organization.id do
apply_edit_action(socket, device)
else
socket
|> put_flash(:error, "You don't have access to this device")
|> push_navigate(to: ~p"/devices")
end
end
end
defp apply_edit_action(socket, device) do
# Get current agent assignment if any
current_assignment = Agents.get_device_assignment(device.id)

View file

@ -4,6 +4,7 @@ defmodule ToweropsWeb.DeviceLive.Index do
alias Towerops.Devices
alias Towerops.Organizations.SubscriptionLimits
alias Towerops.Repo
alias Towerops.Sites
alias Towerops.Snmp
alias Towerops.Workers.DiscoveryWorker
@ -72,45 +73,74 @@ defmodule ToweropsWeb.DeviceLive.Index do
@impl true
def handle_event("reorder_site", %{"site_id" => site_id, "new_position" => new_position}, socket) do
# Handle both integer and string input
position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position)
organization = socket.assigns.current_organization
case Sites.reorder_site(site_id, position) do
{:ok, _site} ->
# Reload devices with updated order
devices = Devices.list_organization_devices(socket.assigns.current_organization.id)
grouped_devices = group_devices_by_site(devices)
# Verify site belongs to current organization
case Sites.get_site(site_id) do
nil ->
{:noreply, put_flash(socket, :error, "Site not found")}
{:noreply,
socket
|> assign(:device, devices)
|> assign(:grouped_devices, grouped_devices)
|> put_flash(:info, "Site order updated")}
site ->
if site.organization_id == organization.id do
# Handle both integer and string input
position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position)
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Failed to reorder site")}
case Sites.reorder_site(site_id, position) do
{:ok, _site} ->
# Reload devices with updated order
devices = Devices.list_organization_devices(organization.id)
grouped_devices = group_devices_by_site(devices)
{:noreply,
socket
|> assign(:device, devices)
|> assign(:grouped_devices, grouped_devices)
|> put_flash(:info, "Site order updated")}
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Failed to reorder site")}
end
else
{:noreply, put_flash(socket, :error, "You don't have access to this site")}
end
end
end
@impl true
def handle_event("reorder_device", %{"device_id" => device_id, "new_position" => new_position}, socket) do
# Handle both integer and string input
position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position)
organization = socket.assigns.current_organization
case Devices.reorder_device(device_id, position) do
{:ok, _device} ->
# Reload devices with updated order
devices = Devices.list_organization_devices(socket.assigns.current_organization.id)
grouped_devices = group_devices_by_site(devices)
# Verify device belongs to current organization
case Devices.get_device(device_id) do
nil ->
{:noreply, put_flash(socket, :error, "Device not found")}
{:noreply,
socket
|> assign(:device, devices)
|> assign(:grouped_devices, grouped_devices)
|> put_flash(:info, "Device order updated")}
device ->
# Preload site to check organization access
device = Repo.preload(device, site: :organization)
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Failed to reorder device")}
if device.site.organization_id == organization.id do
# Handle both integer and string input
position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position)
case Devices.reorder_device(device_id, position) do
{:ok, _device} ->
# Reload devices with updated order
devices = Devices.list_organization_devices(organization.id)
grouped_devices = group_devices_by_site(devices)
{:noreply,
socket
|> assign(:device, devices)
|> assign(:grouped_devices, grouped_devices)
|> put_flash(:info, "Device order updated")}
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Failed to reorder device")}
end
else
{:noreply, put_flash(socket, :error, "You don't have access to this device")}
end
end
end

View file

@ -10,6 +10,7 @@ Devices Working
* GDPR: My Data links in user settings and navigation
* Bug fix: SNMP Community when using remote agent
* Feature: Add mandatory TOTP MFA
* Security improvements
2025-01-27
* Infrastructure improvements