Extract duplicate access control logic from multiple LiveView files into a reusable AccessControl helper module. **Changes:** - NEW: lib/towerops_web/live/helpers/access_control.ex - NEW: test/towerops_web/live/helpers/access_control_test.exs - Refactor device_live/index.ex to use AccessControl.verify_site_access/2 and verify_device_access/2 - Refactor device_live/form.ex to use AccessControl.verify_device_access/2 - Refactor alert_live/index.ex to use AccessControl.verify_alert_access/2 - Refactor device_live/show.ex to use AccessControl.verify_device_access/2 - Remove unused Repo aliases from refactored files **Benefits:** - Reduces code duplication across 7+ locations - Centralizes security-critical access checks - Improves testability (helper module has >90% coverage) - Consistent error handling across all LiveViews 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
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
|
|
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, Alerts.Alert.t()} | {:error, :not_found | :unauthorized}
|
|
def verify_alert_access(alert_id, organization_id) do
|
|
case Alerts.get_alert(alert_id) do
|
|
nil ->
|
|
{:error, :not_found}
|
|
|
|
alert ->
|
|
alert = Repo.preload(alert, device: [site: :organization])
|
|
|
|
if alert.device.site.organization_id == organization_id do
|
|
{:ok, alert}
|
|
else
|
|
{:error, :unauthorized}
|
|
end
|
|
end
|
|
end
|
|
end
|