frontend/src/lib/authz/components/README.md
Quick reference for permission-gating UI. All components use AND semantics: user needs ALL permissions in checks array.
Need to gate...
├── A button? → AuthZButton
├── Any element with tooltip on deny? → AuthZTooltip
├── A section inside a page? → withAuthZContent (preferred)
│ └── Need JSX wrapper? → AuthZGuardContent
├── An entire page/route? → withAuthZPage (preferred)
│ └── Need JSX wrapper? → AuthZGuardPage
├── Need full control over fallback? → withAuthZ / AuthZGuard
└── None of above fit?
├── Can create wrapper component? → Create it (like AuthZButton)
└── Last resort → useAuthZ hook directly
Use buildPermission, buildObjectString or pre-built constants. Never cast with as BrandedPermission.
import { buildPermission, buildObjectString } from 'lib/authz/hooks/useAuthZ/utils';
import {
RoleCreatePermission,
buildRoleReadPermission
} from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
// Static permission (pre-built)
const checks = [RoleCreatePermission];
// Dynamic permission (builder fn)
const checks = [buildRoleReadPermission(roleId)];
// Custom permission (buildPermission + buildObjectString)
const checks = [buildPermission('read', buildObjectString('dashboard', dashboardId))];
When adding authz to a new resource, create a permissions file under lib/authz/hooks/useAuthZ/permissions/.
// lib/authz/hooks/useAuthZ/permissions/dashboard.permissions.ts
import { buildPermission } from '../utils';
import type { BrandedPermission } from '../types';
// Collection-level — wildcard, no specific id needed
export const DashboardCreatePermission = buildPermission('create', 'dashboard:*');
export const DashboardListPermission = buildPermission('list', 'dashboard:*');
// Resource-level — require specific id
export const buildDashboardReadPermission = (id: string): BrandedPermission =>
buildPermission('read', `dashboard:${id}`);
export const buildDashboardUpdatePermission = (id: string): BrandedPermission =>
buildPermission('update', `dashboard:${id}`);
export const buildDashboardDeletePermission = (id: string): BrandedPermission =>
buildPermission('delete', `dashboard:${id}`);
Pattern:
<Resource><Action>Permission → collection-level const (wildcard *)build<Resource><Action>Permission(id) → resource-level fn (specific id)Button that disables + shows tooltip when denied.
import { SACreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
<AuthZButton checks={[SACreatePermission]} onClick={handleCreate}>
Create
</AuthZButton>
Wraps any element. Disables child + shows denial tooltip.
import { buildSADeletePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
<AuthZTooltip checks={[buildSADeletePermission(accountId)]}>
<IconButton icon={<Trash />} onClick={handleDelete} />
</AuthZTooltip>
HOC for route-level gating. Wrap at export. Shows PermissionDeniedFullPage + AppLoading.
import { RoleListPermission } from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
function RolesPage(): JSX.Element {
return <div>...</div>;
}
export default withAuthZPage(RolesPage, {
checks: [RoleListPermission],
});
HOC for inline sections. Shows PermissionDeniedCallout on deny.
import { buildRoleReadPermission } from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
function RoleEditor(): JSX.Element {
return <div>...</div>;
}
// Dynamic checks from route params
export default withAuthZContent(RoleEditor, {
checks: (_props, ctx) => [buildRoleReadPermission(ctx.params.roleId)],
});
HOC base. No default fallback. Use when you need custom fallback.
import { buildPermission } from 'lib/authz/hooks/useAuthZ/utils';
export default withAuthZ(SecretPanel, {
checks: [buildPermission('write', 'settings:org')],
fallback: <p>No access</p>,
});
JSX variant of withAuthZPage. Use when HOC not possible (conditional rendering).
import { RoleListPermission } from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
<AuthZGuardPage checks={[RoleListPermission]}>
<RolesPage />
</AuthZGuardPage>
JSX variant of withAuthZContent. Use when HOC not possible.
import { RoleCreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
<AuthZGuardContent checks={[RoleCreatePermission]}>
<RoleEditor />
</AuthZGuardContent>
JSX base guard. No default fallback. Use when you need custom fallback in JSX.
import { buildPermission } from 'lib/authz/hooks/useAuthZ/utils';
<AuthZGuard
checks={[buildPermission('write', 'settings:org')]}
fallback={<p>No access</p>}
fallbackOnLoading={<Spinner />}
>
<SecretContent />
</AuthZGuard>
Don't use these components directly, always prefer using via withAuthZ and their variants.
user/{id} is not authorized to perform {permissions}.