# engineer — RBAC + Token page + Filtered Excel export + full-width + donut fix **Date:** 2026-08-27 · From: [principal-20260827-175448](principal-20260827-175448.md) ## What changed ### 1. Roles / RBAC - `server/db.ts`: `app_users.role TEXT NOT NULL DEFAULT 'viewer'` (additive `ALTER … ADD COLUMN IF NOT EXISTS`). `seedUser` forces the bootstrap `AUTH_USER` to `admin` (and repairs pre-role rows). Added `Role`/`ROLES`/`isRole`, `listUsers`, `createUser` (23505 → 'exists'), `deleteUser`. - `server/auth.ts`: session `user = {username, role}`; `verifyLogin` returns the role; `requireRole(min)` — 401 unauth, **403** under-privileged. - `index.ts` route gates: - viewer: `/api/tickets`, `/api/tickets/:number`, `/api/stats`, `/api/config` - pm: `/api/analytics/*`, `/api/insights` - lead: `GET/POST /api/users`, `DELETE /api/users/:username` - admin: `GET/POST /api/tokens`, `POST /api/tokens/:id/revoke` - **Only admin may create an admin** (server 403 + client dropdown limits leadership to ≤lead). - Client: `roleAtLeast`/`ROLE_LABELS`; App route guards (reports→pm, /admin→lead); Sidebar shows Analytics only for pm+, Admin only for lead+; TopBar shows role. ### 2. API token page — `pages/Admin` (Tokens tab, admin only) List (label/id/last-used/expires/revoked) + create (raw `fg_…` shown once, copy) + revoke. Replaces the `x-admin-key` HTTP gate with the admin session role. `mint-token` CLI unchanged. ### 3. Filtered Excel export `utils/excel.utils.ts` (`write-excel-file` dep, as in Husky) → exports the board's **currently filtered** rows. Button in the board header. 17 columns incl. lifetime, days-in-state, cost, PO, link. ### 4. Also - Overall verifier REDO fixed: donut now fed `byRequesterShare` (full 966-ticket distribution), not the top-20 nested `byRequester` → center total 966, correct %. - All analytics pages `max-width: none` (charts span the full container width). ## Verification (live, role forge_app DB) - server tsc / client tsc clean; client vite build OK; `npm test` 9 pass. - Gating matrix reproduced end-to-end: viewer → tickets/config 200, analytics/insights/users/tokens **403**; pm → analytics/insights 200, users/tokens **403**; admin → all 200. viewer POST /api/users **403**. - Bootstrap `admin` row correctly migrated to role `admin` (not the 'viewer' default). ## Next security — audit the authz (public domain): privilege escalation, self-delete/last-admin, token exposure. verifier — gate the full change.