Fixed 5 critical issues from code review: 1. **GlobalSearchTrigger hook listener leak** - Added destroyed() callback to remove click listener - Prevents memory leak on LiveView unmount 2. **NetworkMap/WeathermapViewer hook listener leaks** - Store zoom/fit button handlers as properties - Remove DOM event listeners in destroyed() callback - Fixes leak on every LiveView remount 3. **SitesMap uses undocumented window.liveSocket.execJS** - Changed to proper LiveView pattern using pushEvent - Attach listener via Leaflet's popupopen event 4. **handle_info(:refresh_data) double DB fetch** - Removed redundant Devices.get_device call - Reuse device from assign_base_data 5. **Redundant DB queries in DeviceLive.Show overview** - Changed load_sensor_chart_data to accept snmp_device - Changed load_overall_traffic_chart_data to accept snmp_device - Eliminated 4 redundant Snmp.get_device_with_associations calls Also removed unused functions: - load_equipment_data/2 - reload_active_tab_data/1 - assign_subscriber_impact/1 Note: DeviceLive.Show module size (2252 lines) is acknowledged but deferred as it requires larger architectural refactoring. Reviewed-on: graham/towerops-web#192
112 lines
3 KiB
Elixir
112 lines
3 KiB
Elixir
defmodule ToweropsWeb.Live.Helpers.AccessControl do
|
|
@moduledoc """
|
|
Centralized organization-based access control for LiveViews.
|
|
|
|
Provides functions to verify that users have access to resources
|
|
(devices, sites, alerts) within their organization scope.
|
|
"""
|
|
|
|
alias Towerops.Alerts.Alert
|
|
alias Towerops.Devices
|
|
alias Towerops.Repo
|
|
alias Towerops.Sites
|
|
|
|
@doc """
|
|
Verifies that a device belongs to the specified organization.
|
|
|
|
Returns `{:ok, device}` if access is granted, or an error tuple.
|
|
|
|
## Examples
|
|
|
|
iex> verify_device_access(device_id, org_id)
|
|
{:ok, %Device{}}
|
|
|
|
iex> verify_device_access(device_id, wrong_org_id)
|
|
{:error, :unauthorized}
|
|
|
|
iex> verify_device_access("nonexistent", org_id)
|
|
{:error, :not_found}
|
|
"""
|
|
@spec verify_device_access(binary(), binary()) ::
|
|
{:ok, Devices.Device.t()} | {:error, :not_found | :unauthorized}
|
|
def verify_device_access(device_id, organization_id) do
|
|
case Devices.get_device(device_id) do
|
|
nil ->
|
|
{:error, :not_found}
|
|
|
|
device ->
|
|
if device.organization_id == organization_id do
|
|
{:ok, device}
|
|
else
|
|
{:error, :unauthorized}
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Verifies that a site belongs to the specified organization.
|
|
|
|
Returns `{:ok, site}` if access is granted, or an error tuple.
|
|
|
|
## Examples
|
|
|
|
iex> verify_site_access(site_id, org_id)
|
|
{:ok, %Site{}}
|
|
|
|
iex> verify_site_access(site_id, wrong_org_id)
|
|
{:error, :unauthorized}
|
|
|
|
iex> verify_site_access("nonexistent", org_id)
|
|
{:error, :not_found}
|
|
"""
|
|
@spec verify_site_access(binary(), binary()) ::
|
|
{:ok, Sites.Site.t()} | {:error, :not_found | :unauthorized}
|
|
def verify_site_access(site_id, organization_id) do
|
|
case Sites.get_site(site_id) do
|
|
nil ->
|
|
{:error, :not_found}
|
|
|
|
site ->
|
|
if site.organization_id == organization_id do
|
|
{:ok, site}
|
|
else
|
|
{:error, :unauthorized}
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Verifies that an alert's device belongs to the specified organization.
|
|
|
|
Returns `{:ok, alert}` if access is granted, or an error tuple.
|
|
The alert is preloaded with `device: [site: :organization]` for access checking.
|
|
|
|
## Examples
|
|
|
|
iex> verify_alert_access(alert_id, org_id)
|
|
{:ok, %Alert{device: %Device{site: %Site{organization: %Organization{}}}}}
|
|
|
|
iex> verify_alert_access(alert_id, wrong_org_id)
|
|
{:error, :unauthorized}
|
|
|
|
iex> verify_alert_access("nonexistent", org_id)
|
|
{:error, :not_found}
|
|
"""
|
|
@spec verify_alert_access(binary(), binary()) ::
|
|
{:ok, Alert.t()} | {:error, :not_found | :unauthorized}
|
|
def verify_alert_access(alert_id, organization_id) do
|
|
case Repo.get(Alert, alert_id) do
|
|
nil ->
|
|
{:error, :not_found}
|
|
|
|
alert ->
|
|
alert = Repo.preload(alert, [:acknowledged_by, device: [site: :organization]])
|
|
|
|
if alert.device.site.organization_id == organization_id do
|
|
{:ok, alert}
|
|
else
|
|
{:error, :unauthorized}
|
|
end
|
|
end
|
|
end
|
|
end
|