@quanticjs/iam-audit-ui
v8.0.0
Published
IAM Audit Dashboard UI — users, groups, and permission matrix for Keycloak-backed apps
Readme
@quanticjs/iam-audit-ui
IAM Audit Dashboard UI — users, groups, and permission matrix for Keycloak-backed apps.
All pages are permission-gated via PermissionGuard from @quanticjs/react-core and emit structured access events, so the audit tool itself leaves an audit trail.
Requires @quanticjs/tailwind-preset >= 8 in the consuming app's CSS build — components render with v8 token utilities (shadow-* tiers, z-(--z-*), animate-*) that compile to nothing on older presets. See docs/MIGRATION-8.md.
Setup
import { IamAuditProvider } from '@quanticjs/iam-audit-ui';
<IamAuditProvider
config={{
onAccessEvent: (event) => {
// the framework does no batching or transport — POST to your audit backend
client.post('/audit/access-events', event);
},
}}
>
<Routes>…</Routes>
</IamAuditProvider>Config reference (IamAuditConfig)
| Option | Default | Description |
|---|---|---|
| apiBasePath | /iam | API base path for IAM endpoints |
| routeBasePath | /admin/iam | Route base path for navigation links |
| permissions.users | iam:users:read | Gates IamUsersPage and IamUserDetailPage |
| permissions.groups | iam:groups:read | Gates IamGroupsPage |
| permissions.matrix | iam:permissions:read | Gates IamPermissionMatrixPage |
| onAccessEvent | — | Receives an AccessEvent whenever a page or detail view renders with access granted |
| accessDeniedFallback | styled "Access denied" panel | Rendered inline on denial — pages are embedded, so denial never redirects |
Required permissions per page
| Page | Permission | Access events |
|---|---|---|
| IamUsersPage | permissions.users | iam_users_page_viewed |
| IamUserDetailPage | permissions.users | iam_user_detail_viewed (resourceId = userId) |
| IamGroupsPage | permissions.groups | iam_groups_page_viewed |
| IamPermissionMatrixPage | permissions.matrix | iam_permission_matrix_viewed |
Access events
interface AccessEvent {
type: IamAccessEventType; // e.g. 'iam_users_page_viewed'
resourceId?: string; // userId for detail views
path: string; // window.location.pathname at emission
timestamp: string; // ISO 8601
}Events fire once per mount (StrictMode-safe) and only after access is granted — a denied or still-loading session emits nothing. If your onAccessEvent callback throws, the error is swallowed: viewing must never break.
Disabling a guard (discouraged)
Setting a permission to null renders that page unguarded:
<IamAuditProvider config={{ permissions: { matrix: null } }}>This is an explicit opt-out for apps that gate access at a higher level (e.g. routing). Leaving IAM data unguarded defeats the package's least-privilege design — prefer granting the proper permission.
Role-based gating instead
If you gate by role rather than permission, opt the page out with null and wrap it yourself:
<PermissionGuard role="iam-auditor" fallback={<Denied />}>
<IamUsersPage />
</PermissionGuard>Internationalization
Every rendered string lives in a labels interface with an English default. Each page takes a labels prop, and all of them resolve app-wide from the TranslationProvider in @quanticjs/react-ui — this package registers the iamAuditUi namespace (IamAuditTranslations). Precedence per key: explicit labels prop > provider catalog > English default.
import { TranslationProvider } from '@quanticjs/react-ui';
<TranslationProvider
locale="de-DE"
translations={{
iamAuditUi: {
users: { title: 'IAM-Benutzer', total: (t) => `${t} Benutzer insgesamt` },
userDetail: { created: (date) => `Erstellt am ${date}` },
matrix: { hasPermission: (name, p) => `${name} hat ${p}` },
},
}}
>
<IamUsersPage />
</TranslationProvider>Namespaces: users (IamUsersPageLabels), userDetail (IamUserDetailLabels), groups (IamGroupsPageLabels), matrix (IamPermissionMatrixLabels). Function-valued labels (total(count), created(date), memberCount(count), hasPermission(name, permission), …) handle interpolation/pluralization. The user-detail creation date is formatted with the shared formatDate and follows the provider locale reactively.
Error states
Failed queries render the shared QueryErrorPanel from @quanticjs/react-ui: 403 shows an access message (no retry), 404 shows not-found, network/5xx errors show "Try again". 401 is handled by the @quanticjs/react-core client redirect before it reaches the UI.
Notes
- Permission revoked mid-session: the session is cached with
staleTime: Infinity(ADR-004), so revocation takes effect at next login/refresh, not mid-session.
RTL & reduced motion
The permission-matrix sticky column (start-0), table alignment (text-start), and the users-page search affordance (start-3, ps-9) use logical properties, so pages mirror under dir="rtl" with no configuration. Loading skeletons respect prefers-reduced-motion (via @quanticjs/react-ui primitives and the preset's global media block).
