defmodule ToweropsWeb.Permissions do @moduledoc """ Authorization helpers for checking user permissions in LiveViews, controllers, and templates. Integrates with `Towerops.Organizations.Policy` to provide a simple, reusable API for permission checks based on organization membership roles. ## Usage in LiveViews import ToweropsWeb.Permissions def handle_event("delete_backup", _params, socket) do if can?(socket, :delete, :backup) do # Delete backup else {:noreply, put_flash(socket, :error, "You don't have permission to delete backups")} end end ## Usage in Templates <%= if can?(@current_scope, :delete, :backup) do %> <% end %> ## Usage in Controllers import ToweropsWeb.Permissions def delete(conn, _params) do if can?(conn.assigns.current_scope, :delete, :backup) do # Delete backup else conn |> put_flash(:error, "You don't have permission to delete backups") |> redirect(to: ~p"/") end end """ alias Phoenix.LiveView.Socket alias Towerops.Accounts.Scope alias Towerops.Organizations.Policy @doc """ Checks if the current user can perform an action on a resource. Accepts either a socket (LiveView) or a Scope struct directly. Returns `true` if: - User is a superuser (can do everything) - User's organization membership role allows the action per Policy ## Examples iex> can?(socket, :delete, :backup) true iex> can?(%Scope{}, :edit, :device) false """ def can?(%Socket{} = socket, action, resource) do can?(socket.assigns.current_scope, action, resource) end def can?(%Scope{} = scope, action, resource) do cond do # Superusers can do everything Scope.superuser?(scope) -> true # No organization context - cannot perform action is_nil(scope.organization) -> false # Check organization membership role permissions true -> membership = get_membership(scope) Policy.can?(membership, action, resource) end end def can?(nil, _action, _resource), do: false @doc """ Checks if the current user is an organization owner. Accepts either a socket (LiveView) or a Scope struct directly. ## Examples iex> owner?(socket) true iex> owner?(%Scope{}) false """ def owner?(%Socket{} = socket) do owner?(socket.assigns.current_scope) end def owner?(%Scope{} = scope) do cond do Scope.superuser?(scope) -> true is_nil(scope.organization) -> false true -> membership = get_membership(scope) membership && membership.role == :owner end end def owner?(nil), do: false @doc """ Checks if the current user is an organization owner or admin. Accepts either a socket (LiveView) or a Scope struct directly. ## Examples iex> admin?(socket) true iex> admin?(%Scope{}) false """ def admin?(%Socket{} = socket) do admin?(socket.assigns.current_scope) end def admin?(%Scope{} = scope) do cond do Scope.superuser?(scope) -> true is_nil(scope.organization) -> false true -> membership = get_membership(scope) membership && membership.role in [:owner, :admin] end end def admin?(nil), do: false # Private helper to extract membership from scope defp get_membership(%Scope{organization: org, user: user}) when not is_nil(org) and not is_nil(user) do # Organization should be preloaded with memberships if Ecto.assoc_loaded?(org.memberships) do Enum.find(org.memberships, &(&1.user_id == user.id)) # If memberships not preloaded, return nil (permission check will fail safely) end end defp get_membership(_scope), do: nil end