Files
arcadia-cloud/lib/arcadia_cloud_web/controllers/invoice_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

71 lines
1.9 KiB
Elixir

defmodule ArcadiaCloudWeb.InvoiceController do
@moduledoc """
Tenant invoices (revenue side). platform_admin sees all tenants;
others are scoped to their own tenant_id.
"""
use ArcadiaCloudWeb, :controller
alias ArcadiaCloud.Invoicing
def index(conn, params) do
identity = conn.assigns.current_identity
opts =
if platform_admin?(identity),
do: [tenant_id: params["tenant_id"], status: params["status"]],
else: [tenant_id: identity.tenant_id, status: params["status"]]
invoices = Invoicing.list_invoices(opts) |> Enum.map(&shape/1)
json(conn, %{invoices: invoices, count: length(invoices)})
end
def show(conn, %{"id" => id}) do
identity = conn.assigns.current_identity
case Invoicing.get_invoice(id) do
nil ->
conn |> put_status(:not_found) |> json(%{error: "not_found"})
invoice ->
if platform_admin?(identity) or invoice.tenant_id == identity.tenant_id do
json(conn, %{invoice: shape(invoice), lines: Enum.map(invoice.lines, &shape_line/1)})
else
conn |> put_status(:forbidden) |> json(%{error: "forbidden"})
end
end
end
defp platform_admin?(%{roles: roles}) when is_list(roles), do: "platform-admin" in roles
defp platform_admin?(_), do: false
defp shape(i) do
%{
id: i.id,
tenant_id: i.tenant_id,
period_start: i.period_start,
period_end: i.period_end,
currency: i.currency,
subtotal_cents: i.subtotal_cents,
tax_cents: i.tax_cents,
total_cents: i.total_cents,
status: i.status,
issued_at: i.issued_at,
pushed_to_finance_at: i.pushed_to_finance_at
}
end
defp shape_line(l) do
%{
kind: l.kind,
deployment_id: l.deployment_id,
resource_kind: l.resource_kind,
description: l.description,
qty: l.qty,
unit: l.unit,
unit_price_cents: l.unit_price_cents,
amount_cents: l.amount_cents
}
end
end