towerops/lib/towerops_web/live/helpers/access_control.ex
Graham McIntire 3a408a8dc1 Security hardening + performance fixes across codebase
CRITICAL:
- Membership: remove :role/:org_id/:user_id from mass-assignment cast; use explicit create_changeset/4 and role_update_changeset/2
- GraphQL member resolver: add authorize_invite/3 checking admin/owner role and role hierarchy
- REST invitations controller: add auth check for invite creation

HIGH:
- ApiToken: remove :organization_id/:user_id from cast; use explicit create_changeset/4

MEDIUM:
- Move 8 LiveView Ecto queries into context modules (Admin, Alerts, Coverages, OnCall, Snmp)
- Replace Process.put/Process.get with socket assigns for unresolved_alert_count (user_auth + layouts + 50 templates)
- Add batch get_utilization_for_interfaces/1 to eliminate N+1 capacity queries in device show
- Replace Process.sleep with Process.monitor/assert_receive or Process.send_after in 5 test files

LOW:
- Add handle_params/3 to UserResetPasswordLive, UserRegistrationLive, StatusPageLive
- Remove redundant Repo.preload calls; add preloads to list_site_devices/1
- Fix @impl annotations and credo nesting warnings
2026-06-21 17:40:50 -05:00

56 lines
1.9 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.Devices.Device
alias Towerops.Sites
alias Towerops.Sites.Site
@doc """
Verifies that a device belongs to the specified organization.
Returns `{:ok, device}` if access is granted, or an error tuple.
"""
@spec verify_device_access(binary(), binary()) ::
{:ok, Device.t()} | {:error, :not_found | :unauthorized}
def verify_device_access(device_id, organization_id) do
case Devices.get_device(device_id) do
%Device{organization_id: ^organization_id} = device -> {:ok, device}
nil -> {:error, :not_found}
%Device{} -> {:error, :unauthorized}
end
end
@doc """
Verifies that a site belongs to the specified organization.
Returns `{:ok, site}` if access is granted, or an error tuple.
"""
@spec verify_site_access(binary(), binary()) ::
{:ok, Site.t()} | {:error, :not_found | :unauthorized}
def verify_site_access(site_id, organization_id) do
case Sites.get_site(site_id) do
%Site{organization_id: ^organization_id} = site -> {:ok, site}
nil -> {:error, :not_found}
%Site{} -> {:error, :unauthorized}
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.
"""
@spec verify_alert_access(binary(), binary()) ::
{:ok, Alert.t()} | {:error, :not_found | :unauthorized}
def verify_alert_access(alert_id, organization_id) do
Alerts.get_verified_alert(alert_id, organization_id)
end
end