@devcraft-ts/abac-admin-react
Headless React hooks for building custom ABAC admin interfaces. Built on @devcraft-ts/abac-admin-core which leverages abac-engine for policy evaluation.
Built on abac-engine
These hooks provide a React-friendly interface to manage ABAC policies powered by abac-engine. While the hooks handle UI state and data fetching, abac-engine provides the underlying policy evaluation logic. Learn more →
Headless
Bring your own UI - complete design freedom
Context-Based
Centralized state management with React Context
Type-Safe
Full TypeScript support with abac-engine types
Lightweight
~30KB minified - minimal overhead
Optimistic Updates
Built-in optimistic UI updates for smooth UX
Installation
npm install @devcraft-ts/abac-admin-react # Peer dependencies npm install react react-dom
Quick Start
1. Wrap Your App with ABACProvider
import { ABACProvider } from '@devcraft-ts/abac-admin-react';
function App() {
return (
<ABACProvider
config={{
baseURL: '/api/abac',
headers: {
'Authorization': `Bearer ${token}`
}
}}
>
<YourApp />
</ABACProvider>
);
}2. Use Hooks in Your Components
import { usePolicies } from '@devcraft-ts/abac-admin-react';
function PolicyList() {
const { policies, isLoading, error, refetch } = usePolicies();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{policies.map(policy => (
<div key={policy.id}>
<h3>{policy.description}</h3>
<span>{policy.effect}</span>
</div>
))}
</div>
);
}Policy Hooks
usePolicies
Hook for managing multiple policies with filtering and CRUD operations.
const {
policies, // Policy[]
isLoading, // boolean
error, // Error | null
refetch, // () => Promise<void>
createPolicy, // (policy: PolicyInput) => Promise<Policy>
updatePolicy, // (id: string, update: PolicyUpdate) => Promise<Policy>
deletePolicy, // (id: string) => Promise<void>
activatePolicy, // (id: string) => Promise<Policy>
deactivatePolicy // (id: string) => Promise<Policy>
} = usePolicies({
filters?: {
isActive?: boolean;
category?: string;
tags?: string[];
}
});
// Example usage
function PolicyManager() {
const {
policies,
isLoading,
error,
createPolicy,
deletePolicy
} = usePolicies({ filters: { isActive: true } });
const handleCreate = async () => {
await createPolicy({
policyId: 'new-policy',
version: '1.0.0',
effect: 'PERMIT',
description: 'New policy',
isActive: true
});
};
const handleDelete = async (id: string) => {
await deletePolicy(id);
};
return (
<div>
<button onClick={handleCreate}>Create Policy</button>
{policies.map(policy => (
<div key={policy.id}>
<span>{policy.description}</span>
<button onClick={() => handleDelete(policy.id)}>
Delete
</button>
</div>
))}
</div>
);
}usePolicy
Hook for managing a single policy.
const {
policy, // Policy | null
isLoading, // boolean
error, // Error | null
refetch, // () => Promise<void>
update, // (update: PolicyUpdate) => Promise<Policy>
delete: del // () => Promise<void>
} = usePolicy(policyId);
function PolicyDetails({ policyId }: { policyId: string }) {
const { policy, isLoading, update } = usePolicy(policyId);
if (isLoading) return <div>Loading...</div>;
if (!policy) return <div>Not found</div>;
const handleUpdate = async () => {
await update({
description: 'Updated description'
});
};
return (
<div>
<h2>{policy.description}</h2>
<button onClick={handleUpdate}>Update</button>
</div>
);
}usePolicyTest
Hook for testing policies with sample data.
const {
testPolicy, // (request: PolicyTestRequest) => Promise<PolicyTestResult>
result, // PolicyTestResult | null
isLoading, // boolean
error // Error | null
} = usePolicyTest();
function PolicyTester({ policyId }: { policyId: string }) {
const { testPolicy, result, isLoading } = usePolicyTest();
const handleTest = async () => {
await testPolicy({
policyId,
subject: { id: 'user-123', role: 'admin' },
resource: { id: 'doc-456' },
action: { id: 'read' },
environment: {}
});
};
return (
<div>
<button onClick={handleTest}>Test Policy</button>
{result && (
<div>
<p>Decision: {result.decision}</p>
<p>Matched: {result.matched ? 'Yes' : 'No'}</p>
</div>
)}
</div>
);
}Attribute Hooks
useAttributes
const {
attributes, // Record<string, any>
isLoading, // boolean
error, // Error | null
refetch, // () => Promise<void>
setAttribute, // (key: string, value: any) => Promise<void>
bulkSet, // (attrs: Record<string, any>) => Promise<void>
deleteAttribute // (key: string) => Promise<void>
} = useAttributes(resourceType, resourceId);
function UserAttributeManager({ userId }: { userId: string }) {
const {
attributes,
setAttribute,
deleteAttribute
} = useAttributes('user', userId);
const handleSetDepartment = async () => {
await setAttribute('department', 'engineering');
};
return (
<div>
{Object.entries(attributes).map(([key, value]) => (
<div key={key}>
<span>{key}: {value}</span>
<button onClick={() => deleteAttribute(key)}>
Delete
</button>
</div>
))}
<button onClick={handleSetDepartment}>
Set Department
</button>
</div>
);
}useAttributeHistory
const {
history, // AttributeValue[]
isLoading, // boolean
error // Error | null
} = useAttributeHistory(resourceType, resourceId, attributeKey?);
function AttributeHistory({ userId }: { userId: string }) {
const { history, isLoading } = useAttributeHistory('user', userId, 'role');
return (
<div>
<h3>Role History</h3>
{history.map((entry, i) => (
<div key={i}>
<span>{entry.value}</span>
<span>{new Date(entry.timestamp).toLocaleString()}</span>
</div>
))}
</div>
);
}Audit Hooks
useAuditLog
const {
entries, // AuditLogEntry[]
total, // number
hasMore, // boolean
isLoading, // boolean
error, // Error | null
refetch // () => Promise<void>
} = useAuditLog({
entityType?: 'policy' | 'attribute';
entityId?: string;
action?: AuditAction;
startDate?: string;
endDate?: string;
limit?: number;
offset?: number;
});
function AuditLog() {
const { entries, isLoading } = useAuditLog({
entityType: 'policy',
limit: 50
});
return (
<div>
{entries.map(entry => (
<div key={entry.id}>
<span>{entry.action}</span>
<span>{entry.entityId}</span>
<span>{new Date(entry.timestamp).toLocaleString()}</span>
</div>
))}
</div>
);
}Best Practices
Use Context Provider at App Root
Place the ABACProvider at the root of your app to ensure all components can access the hooks.
Handle Loading and Error States
Always handle loading and error states in your UI for better user experience.
Use Filters to Reduce Data
Apply filters to hooks to fetch only the data you need, improving performance.
Leverage Refetch for Updates
Use the refetch function to manually refresh data when needed.