Files
arcadia-cloud/lib/arcadia_cloud_web/controllers/inventory_controller.ex
Giuliano Silvestro c10b847324 Fix operator role gate: platform-admin (hyphen), not platform_admin
arcadia-app issues the role slug "platform-admin" (hyphen) — confirmed
from a live arcadia-dev JWT (roles: ["admin","platform-admin"]). Every
authorization check here tested for "platform_admin" (underscore), so
real operator tokens got 403 on billing / dashboard / drift and an
empty tenant-scoped result on inventory.

The smoke tests missed it because Guardian.mint_dev_token hardcoded the
underscore form — fixed there too, so the dev helper now matches what
arcadia-app actually emits.

Replaced the string literal "platform_admin" -> "platform-admin" in all
six controllers + guardian.ex. The platform_admin?/1 function names keep
underscores (Elixir identifiers can't contain hyphens) — only the role
string changed.

Verified: with a platform-admin token, /inventory, /billing/balance,
/dashboard/margin and /drift all return 200.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:13 +10:00

63 lines
1.5 KiB
Elixir

defmodule ArcadiaCloudWeb.InventoryController do
@moduledoc """
Cloud resource inventory.
Scope rules (phase 1):
- platform_admin tenants see every non-deleted resource
- other tenants see only resources tagged to their tenant_id
Filters: `?kind=droplet`, `?deployment_id=...`
"""
use ArcadiaCloudWeb, :controller
alias ArcadiaCloud.Cloud
def index(conn, params) do
identity = conn.assigns.current_identity
base_opts =
[]
|> maybe_put(:kind, params["kind"])
|> maybe_put(:deployment_id, params["deployment_id"])
opts =
if platform_admin?(identity) do
base_opts
else
Keyword.put(base_opts, :tenant_id, identity.tenant_id)
end
resources =
Cloud.list_resources(opts)
|> Enum.map(&shape/1)
json(conn, %{resources: resources, count: length(resources)})
end
defp platform_admin?(%{roles: roles}) when is_list(roles), do: "platform-admin" in roles
defp platform_admin?(_), do: false
defp maybe_put(opts, _key, nil), do: opts
defp maybe_put(opts, _key, ""), do: opts
defp maybe_put(opts, key, value), do: Keyword.put(opts, key, value)
defp shape(r) do
%{
id: r.id,
provider: r.provider,
provider_id: r.provider_id,
kind: r.kind,
name: r.name,
region: r.region,
status: r.status,
size_slug: r.size_slug,
tenant_id: r.tenant_id,
deployment_id: r.deployment_id,
tags: r.tags,
first_seen_at: r.first_seen_at,
last_seen_at: r.last_seen_at
}
end
end