Hierarchical Role-Based Access Control for Node.js
This project now uses Vite to generate the bundled output
Thanks to Karl Düüna (DeadAlready) and his awesome post on medium
yarn add @rbac/rbac
or npm install @rbac/rbac
This library is written in TypeScript and the published package ships with
its declaration files for a great developer experience.
RBAC is a curried function thats initially takes an object with configurations,
then returns another function that takes an object with roles,
finally returns an object that holds “can” property that is a function.
You can use it in many ways, below is one of them:
Property | Type | Params | Default | Description |
---|---|---|---|---|
logger | Function | role: String operation: String result: Boolean |
defaultLogger | Function that logs operations to console |
enableLogger | Boolean | true | Enable or disable logger |
RBAC expects an object with roles as property names.
Property | Type | Example | Description |
---|---|---|---|
can | Array | ['products:*'] |
Array of strings, list of operations that user can do, it also supports glob patterns |
when | Function, Async Function or Promise | (params , done ) => done (null , true ) |
Optional Promise that should resolve in Truthy or Falsy, an async function that returns a boolean or Promise, or a Callback function that receives params and done as properties, should return done passing errors and result |
inherits | Array | ['user'] |
Optional Array of strings, list of roles inherited by this role |
import type { Roles } from '@rbac/rbac';
interface Params {
registered: boolean;
}
const roles: Roles<Params> = {
supervisor: {
can: [{ name: 'products:find', when: (params, done) => {
// done receives error as first argument and Truthy or Falsy value as second argument
done(null, params.registered);
}}]
},
admin: {
can: [{ name: 'products:*', when: async (params) => {
return params.registered;
} }]
}
};
Param | Type | Example | Description |
---|---|---|---|
First | String | 'admin' |
Array of strings, list of operations that user can do |
Second | String, Glob (Wildcard), Regex | 'products:find' |
Operation to validate |
Third | Any | {registered: true} |
Optional Params that will flow to “when” callback Function |
RBAC exposes two helpers to modify the role definition at runtime. addRole
adds a new role and updateRoles
merges new definitions with the existing ones.
import RBAC from '@rbac/rbac'
const base = RBAC({ enableLogger: false })({
user: { can: ['products:find'] }
})
base.addRole('editor', { can: ['products:update'], inherits: ['user'] })
await base.can('editor', 'products:update') // true
base.updateRoles({
user: { can: ['products:find', 'products:create'] }
})
await base.can('user', 'products:create') // true
RBAC exposes optional adapters to load and persist role definitions using
MongoDB, MySQL or PostgreSQL. Each adapter implements the RoleAdapter
interface with getRoles
, addRole
and updateRoles
methods.
import RBAC from '@rbac/rbac'
import { MongoRoleAdapter } from '@rbac/rbac/adapters'
const adapter = new MongoRoleAdapter({
uri: 'mongodb://localhost:27017',
dbName: 'mydb',
collection: 'roles'
})
const roles = await adapter.getRoles()
const rbac = RBAC()(roles)
Adapters available:
MongoRoleAdapter
MySQLRoleAdapter
PostgresRoleAdapter
Adapters also allow customizing the underlying table or collection column names
through a columns
option when creating a new instance:
const adapter = new MySQLRoleAdapter({
table: 'roles',
columns: { name: 'rname', role: 'rdef', tenantId: 'tid' }
})
Adapters can optionally receive a tenantId
parameter to store and retrieve
roles for different tenants. When omitted, the adapter falls back to a default
tenant so existing single-tenant usage keeps working. Use createTenantRBAC
to
instantiate an RBAC instance scoped to a tenant:
import { MongoRoleAdapter, createTenantRBAC } from '@rbac/rbac';
const adapter = new MongoRoleAdapter({ uri: 'mongodb://localhost:27017', dbName: 'mydb', collection: 'roles' });
await adapter.addRole('user', { can: ['products:find'] }, 'tenant-a');
const rbacTenantA = await createTenantRBAC(adapter, 'tenant-a');
await rbacTenantA.can('user', 'products:find'); // true
Want more? Check out the examples folder.
RBAC also provides helper middlewares for Express, NestJS and Fastify.
They make it easy to guard routes using existing role definitions.
import RBAC, { createExpressMiddleware } from '@rbac/rbac';
const rbac = RBAC({ enableLogger: false })({
user: { can: ['products:find'] }
});
const canFindProducts = createExpressMiddleware(rbac)('products:find');
app.get('/products', canFindProducts, handler);
For NestJS and Fastify you can use createNestMiddleware
and createFastifyMiddleware
respectively with a similar API.
when
callbacksRun npm run bench
to execute performance tests.
$ npm run bench
Benchmark ops/sec: 457270 (direct), 435470 (inherited), 45681 (glob)
npm install
(or yarn install
) to get RBAC’s dependenciesnpm run build
to compile the library and produce the minified bundle using Viteyarn dev
. This command will generate a non-minified version of your library and will run a watcher so you get the compilation on file change.yarn test
npm run build
- produces production version of your library under the lib
folder and generates lib/@rbac/rbac.min.js
via Vitenpm run dev
- produces development version of your library and runs a watchernpm test
- well … it runs the tests 😃npm run test:watch
- same as above but in a watch modenpm run bench
- run the benchmark suiteThis project is under MIT License [https://opensource.org/licenses/MIT]