# Role-Based UI Access Control

**Date:** 2026-04-06
**Status:** Approved

## Problem

The UI shows all features to all authenticated users regardless of role. VIEWER users see the Admin sidebar section, can navigate to admin pages, and encounter 403 errors from the backend. App Config is under the Admin section despite being accessible to VIEWER/OPERATOR.

## Design

### Role-Permission Matrix

| Feature | VIEWER | OPERATOR | ADMIN |
|---------|--------|----------|-------|
| **Main tabs** | | | |
| Exchanges (view) | Yes | Yes | Yes |
| Dashboard | Yes | Yes | Yes |
| Runtime | Yes | Yes | Yes |
| Logs | Yes | Yes | Yes |
| Config (new tab, per-app) | Read-only | Read + Edit | Read + Edit |
| **Operator actions** | | | |
| Route control (start/stop/suspend/resume) | Hidden | Yes | Yes |
| Replay exchange | Hidden | Yes | Yes |
| Diagram context menu | Hidden | Yes | Yes |
| **Admin section** (hidden entirely from non-admin) | | | |
| Users & Roles | Hidden | Hidden | Full CRUD |
| Audit Log | Hidden | Hidden | Read-only |
| OIDC Config | Hidden | Hidden | Full CRUD |
| Database Admin | Hidden | Hidden | Read + Kill query |
| ClickHouse Admin | Hidden | Hidden | Read-only |
| **Sidebar** | | | |
| Applications section | Yes | Yes | Yes |
| Starred section | Yes | Yes | Yes |
| Admin section | Hidden | Hidden | Yes |
| API Docs link | Yes | Yes | Yes |

### Changes

#### 1. Sidebar — hide Admin section for non-ADMIN

In `LayoutShell.tsx`, read `roles` from `useAuthStore` and conditionally render the Admin sidebar section only when the user has the ADMIN role.

#### 2. Router — add role guard for `/admin/*`

In `router.tsx`, wrap admin routes with a `RequireRole` guard component that checks for ADMIN role and redirects to `/` if not authorized. Reuse the existing `useAuthStore` roles.

#### 3. Move App Config to main application tabs

- Remove App Config from the Admin sidebar items
- Add a "Config" tab alongside Exchanges/Dashboard/Runtime/Logs in the main tab bar
- Route: `/config/:appId` (scoped per-app, like other tabs)
- The config page already exists (`AppConfigPage.tsx`, `AppConfigDetailPage.tsx`) — reuse it
- Hide edit/save controls for VIEWER (check `roles` for OPERATOR+)
- Backend ACL already correct: `GET /api/v1/config/*` is VIEWER+, `PUT /api/v1/config/*` is OPERATOR+

#### 4. Role helper

Add a small utility (e.g., `hasRole(role)` or `useHasRole(role)`) to reduce boilerplate. The existing pattern `roles.some(r => r === 'OPERATOR' || r === 'ADMIN')` is repeated — centralize it.

### Files to modify

| File | Change |
|------|--------|
| `ui/src/auth/auth-store.ts` | Add `hasRole(role)` / `isAdmin` / `isOperator` helpers |
| `ui/src/components/LayoutShell.tsx` | Hide Admin sidebar for non-ADMIN; remove App Config from admin items |
| `ui/src/router.tsx` | Add `RequireRole` guard on admin routes; add `/config/:appId` route |
| `ui/src/components/sidebar-utils.ts` | Remove App Config from `buildAdminTreeNodes()` |
| `ui/src/pages/Admin/AppConfigPage.tsx` | Adapt for per-app context (read appId from route params) |
| `ui/src/pages/Admin/AppConfigDetailPage.tsx` | Hide edit controls for VIEWER |
| `ui/src/pages/Exchanges/ExchangesPage.tsx` | Conditionally pass `onNodeAction` to diagram based on role |

#### 5. Diagram node toolbar — hide for VIEWER

The process diagram's `NodeToolbar` (hover actions: inspect, toggle-trace, configure-tap, copy-id) exposes operator-level actions. Hide it for VIEWER users by not passing the `onNodeAction` prop to `ProcessDiagram` / `ExecutionDiagram`. When `onNodeAction` is `undefined`, the toolbar is not rendered (already handled in `ProcessDiagram.tsx` line 394: `toolbar.hoveredNodeId && onNodeAction && ...`). The `inspect` action (node selection) still works via `onNodeSelect` which is separate.

### What stays the same

- Backend ACLs — already correct, no changes needed
- Route control bar — already gated on OPERATOR+ in `ExchangeHeader.tsx` and `ExchangesPage.tsx`
- All admin pages — no individual page changes needed (router guard handles access)

### Verification

- Log in as VIEWER: sidebar has no Admin section, `/admin/*` routes redirect to `/`, Config tab is read-only, diagram node toolbar hidden, route control bar hidden
- Log in as OPERATOR: same as VIEWER + route control buttons visible + Config tab is editable + diagram node toolbar visible
- Log in as ADMIN: full Admin sidebar visible, all admin pages accessible, all features enabled
