@tezx/rbac
v1.0.2
Published
π Flexible Role-Based Access Control (RBAC) middleware for the TezX framework with support for dynamic role IDs.
Maintainers
Readme
π @tezx/rbac
A powerful, fully type-safe Role-Based Access Control (RBAC) plugin for TezX, designed to help you control access to routes, APIs, and resources using simple, template-based permission keys with full IntelliSense support.
π Highlights
- π― Type-safe permission system (
T extends string[]) - π§ IntelliSense-based permission enforcement
- π Multi-role support (
ctx.user.rolecan bestring | string[]) - βοΈ Middleware-driven, plug-and-play
- β Built-in denial handling + custom
onDeny()support - π§© Easy integration with auth middlewares (like
authChecker) - π§ͺ Battle-tested in production apps
- π Use role IDs(Dynamically generated, flexible)
- π Clean merge of all permissions (No manual logic needed)
- π·οΈ Static roles still supported (Easy for default usage)
π¦ Installation
npm install @tezx/rbacπ§ How It Works
[Your Middleware]
β¬οΈ sets ctx.user.role
[RBAC Plugin]
β¬οΈ loads permission map
[Route Guard]
β¬οΈ checks permission key
[β ALLOW] or [β DENY]β οΈ Required: ctx.user.role
To work correctly, you must set ctx.user.role before using RBAC.
β Example:
ctx.user = {
id: 'user_001',
role: 'admin', // β
Required
email: '[email protected]'
};β If roles can be multiple:
ctx.user = {
role: ['editor', 'viewer']
};π‘ Use
authChecker()middleware to assignctx.userfrom token/session.
π§βπ» Usage Example
import RBAC from '@tezx/rbac';
type Permissions = ['user:create', 'user:delete', 'order:read', 'property:approve'];
const rbac = new RBAC<Permissions>();
app.use(authChecker()); // β
Assigns ctx.user + ctx.user.role
app.use(rbac.plugin({
loadPermissions: async () => ({
admin: ['user:create', 'user:delete', 'order:read', 'property:approve'],
editor: ['order:read'],
guest: []
})
}));
app.get('/admin/users', rbac.authorize('user:create'), async (ctx) => {
return ctx.text('You can create users.');
});
π RBAC Lifecycle
| Step | Action |
| ---- | ----------------------------------------------------------------- |
| 1οΈβ£ | ctx.user.role assigned by auth middleware |
| 2οΈβ£ | rbac.plugin() loads RoleβPermission map |
| 3οΈβ£ | rbac.authorize('permission:key') checks merged role permissions |
| 4οΈβ£ | If not allowed β return 403 (with onDeny if provided) |
π Replace role with Unique Role IDs (Advanced)
RBAC system supports mapping dynamic role identifiers (like database IDs or UUIDs) instead of hardcoded role names.
This is helpful when:
- β Roles are created dynamically from a dashboard or DB
- β
You want to map user roles like
"role_8FaHq1"instead of just"admin" - β Permission sets are assigned to these dynamic IDs
π§ͺ Example
ctx.user = {
id: 'user_xyz',
role: 'role_8FaHq1' // β
Your actual role ID from database
};// Load role-permission map based on DB role IDs
loadPermissions: async () => ({
role_8FaHq1: ['user:create', 'order:read'],
role_7NbQt55: ['user:delete']
})β Internally,
RBACmerges all permissions based on the providedctx.user.role, whether it'sstringorstring[].
β οΈ Important
Make sure the role ID you assign in ctx.user.role exactly matches the keys in your permission map.
Bonus: Hybrid Role Support
You can even mix static roles with dynamic IDs if needed:
ctx.user = {
role: ['admin', 'role_7bXy91']
};
loadPermissions: async () => ({
admin: ['dashboard:access'],
role_7bXy91: ['product:create']
});π§© Plugin API
rbac.plugin(config)
Initializes the permission map.
Config options:
| Field | Type | Required | Description |
| ----------------- | ---------------------------- | -------- | --------------------- |
| loadPermissions | (ctx) => RolePermissionMap | β
| Role β permission map |
| isAuthorized | (roles, permissions, ctx) | β | Custom check hook |
| onDeny | (error, ctx) | β | Custom deny response |
rbac.authorize('permission:key')
Middleware to protect routes.
app.post('/orders', rbac.authorize('order:read'), handler);π‘ IntelliSense with Template Types
type Permissions = ['user:create', 'order:read', 'admin:panel'];
const rbac = new RBAC<Permissions>();β
Now rbac.authorize(...) will auto-suggest only those permission keys.
β Custom Deny Example
rbac.plugin({
loadPermissions: ...,
onDeny: (error, ctx) => {
return ctx.json({
success: false,
reason: error.message,
permission: error.permission
});
}
});π Real-World Structure
const permissionMap = {
admin: ['user:create', 'user:delete'],
editor: ['order:read'],
viewer: [],
};User may have:
ctx.user = {
id: 'u-001',
role: ['editor', 'viewer']
};RBAC will combine permissions from both roles.
π₯ Debug Tip
To check permissions being applied at runtime:
console.log(ctx.user.permissions); // all merged permissionsπ Types Summary
type RolePermissionMap<T extends string[]> = Record<string, T[number][]>;
type DenyError<T extends string[]> = {
error: string;
message: string;
permission: T[number];
};π¦ Exported API
import RBAC, { plugin, authorize } from '@tezx/rbac';π§ͺ Test Route Example
app.get('/secure', rbac.authorize('admin:panel'), async (ctx) => {
ctx.body = { status: 'Access granted.' };
});β Best Practices
- π Always assign
ctx.user.roleinauthChecker - π§ Define permissions centrally as union literal type
- π Protect all critical routes using
rbac.authorize() - π§ͺ Add logging inside
onDenyfor better traceability
