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: 'rakib@example.com'
};β 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 permissionsTypes 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