Files
Behavision/server/internal/api/api.go
Suriyakumarvijayanayagam 830c1c1573 Five live endpoints answered 500 to a platform admin
/api/visits, /api/cameras, /api/sites, /api/visitors and
/api/reports/footfall, all in production, all before today's work. A
platform admin is defined by having NO client, and every tenant query
scopes on client_id = $1::uuid - so the empty string reaches Postgres as
''::uuid, which is a cast ERROR rather than an empty result. Found by
calling them while verifying the new routes, which have the same shape
and were failing the same way.

tenantOnly is the guard, beside adminOnly and for the opposite audience.
Per-query casts would have been the wrong fix twice over: it is a fix the
next query forgets, and the next query would then 500 in production
exactly as these did.

403, not adminOnly's 404, because the two hide opposite things. A tenant
must not learn a platform surface exists. A platform admin already knows
the tenant surface does - they are reading its data through /api/admin -
so nothing is concealed by pretending otherwise, and the refusal names
the route to use instead. "Forbidden" alone sends somebody hunting a
permissions problem that does not exist.

/api/auth/* stays on plain authed: a session is not a company's data, and
signing out or revoking a lost device must keep working for an account
with no tenant.

The fake could not have caught this either - it compares client ids as
strings and is perfectly content with "". The test asserts the contract
(403 and a message naming /api/admin) and a third case that matters more
than either: an ordinary tenant user still reaches all of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
2026-09-28 19:18:41 +05:30

30 KiB