# Billing-read authorization — findings, options, recommendation **Status: PLAN. No code changed.** Goal: a least-privilege way to read `GET /api/billing/usage` that does NOT require platform-admin, while platform-admin can still read it. --- ## Phase 0 — how authz actually works here **1. Roles = a fixed, hardcoded enum on `users.role`.** There is NO role→permission mapping. Authz is `Array.includes(role)` against hardcoded sets in `middleware/auth.js`: `PLATFORM_ROLES = ['superadmin','platform_admin']`, `ELEVATED_ROLES = ['admin','superadmin', 'platform_admin']`, `PLATFORM_STAFF = [...,'platform_operator']`. Guards are hardcoded functions: `requireAuth`, `requireAdmin`, `requireSuperAdmin` (`requirePlatformAdmin` is an alias). The enum is threaded through **~20 server files** plus the frontend role dropdown (`PLATFORM_ROLE_OPTIONS` in `frontend/js/views/admin.js`) and the #14 role-normalization migration. Adding a role is a wide change. **2. The authz seam is per-route middleware, not centralized.** Billing today: `server.js:582 → app.use('/api/billing', requireAuth, require('./routes/billing'))`, and `routes/billing.js` gates the handler with `requirePlatformAdmin`. Other endpoints declare their guard at mount or per-handler. **Note:** this billing mount is *bespoke* — it is NOT in `config/api-surface.js` (the partition source of truth) and is therefore **not covered by the firewall test** (`test/api.test.js`). Fixing that is a side-benefit of Option C. **3. Identity: JWT sessions AND scoped API tokens.** `middleware/apiToken.js` implements a `Bearer st_…` token front door (`api_tokens` table, SHA-256 hash, `scope` column). Its security model is the important part: - A token authenticates **as its owner but with `role` forced to `'user'`** (line 63) — every `PLATFORM_ROLES`/`ELEVATED_ROLES` check downstream is false. So a token can never pass `requirePlatformAdmin`; **billing is unreachable by any token today.** - Routers are partitioned in `config/api-surface.js`: `PUBLIC_ROUTERS` (token + JWT, gated by `tokenScopeGate` read