@rbac/rbac
Version:
Blazing Fast, Zero dependency, Hierarchical Role-Based Access Control for Node.js
331 lines (234 loc) • 12.9 kB
Markdown
<p align="center">
<img alt="RBAC" width="556px" src="img/logo.png" />
</p>
<h1 align="center">
Hierarchical Role-Based Access Control for Node.js
</h1>
[](https://circleci.com/gh/phellipeandrade/rbac/tree/master)
[](https://badge.fury.io/js/@rbac%2Frbac)
[](https://twitter.com/intent/tweet?text=checkout%20RBAC%20project%20on%20Github!&url=https://github.com/phellipeandrade/rbac&hashtags=rbac,authorization,privacy,security,permission)
* ⏱ Lightweight
* ⚡️Fastest RBAC (check benchmarks)
* ️🍃low dependency
## Features
* Focused on operations
* Scalable
* Each role is given specific access rights for every operation
* High granularity in assigning rights
* Wildcard and regex support for operations
* Optional database adapters (MongoDB, MySQL, PostgreSQL)
* Express, NestJS and Fastify middlewares
* Roles can be updated at runtime
## Thanks
This project now uses **Vite** to generate the bundled output
Thanks to Karl Düüna ([DeadAlready](https://github.com/DeadAlready)) and his awesome [post on medium](https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1)
## Getting Started
#### Install
`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.
#### Import
The package supports both ES modules and CommonJS.
**ES Modules (recommended)**
```ts
// Default import
import RBAC from '@rbac/rbac';
// Named imports
import { createTenantRBAC, MongoRoleAdapter } from '@rbac/rbac';
// Combined
import RBAC, { createTenantRBAC } from '@rbac/rbac';
```
**CommonJS**
```js
// Default import
const RBAC = require('@rbac/rbac');
// Named imports
const { createTenantRBAC, MongoRoleAdapter } = require('@rbac/rbac');
// Combined
const RBAC = require('@rbac/rbac');
const { createTenantRBAC } = require('@rbac/rbac');
```
#### Usage
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:
#### Setup RBAC config

| Property | Type | Params | Default | Description |
|-------------- |--------------- |------------------------------------------------------------- |--------------- |----------------------------------------- |
| logger | **Function** | role: **String**<br/>operation: **String**<br/>result: **Boolean**<br/>colorsEnabled: **Boolean** (optional) | defaultLogger | Function that logs operations to console |
| enableLogger | **Boolean** | | true | Enable or disable logger |
| colors | **Boolean** | | auto-detect | Enable, disable, or auto-detect color support in logger output |
#### Creating some roles

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 |
###### IMPORTANT! **"when"** property can be a Callback function that receives params and done, an async function that returns a boolean or Promise, or a Promise that resolves in [Truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) or [Falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) values. Example:
```ts
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;
} }]
}
};
```
#### Check if user can do some operation

| 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 |
### Update roles at runtime
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.
```ts
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
```
### Color Configuration
The default logger automatically detects color support in your terminal and applies ANSI color codes accordingly. You can also manually control color output:
```ts
import RBAC from '@rbac/rbac'
// Auto-detect color support (default behavior)
const rbacAuto = RBAC({ enableLogger: true })({
user: { can: ['products:find'] }
})
// Force colors enabled
const rbacWithColors = RBAC({ enableLogger: true, colors: true })({
user: { can: ['products:find'] }
})
// Force colors disabled (plain text output)
const rbacNoColors = RBAC({ enableLogger: true, colors: false })({
user: { can: ['products:find'] }
})
```
**Color Detection Logic:**
1. If `colors: true` is set, colors are always enabled
2. If `colors: false` is set, colors are always disabled
3. If `colors` is not specified (default), the logger automatically detects:
- `FORCE_COLOR` environment variable (enables colors)
- `NO_COLOR` environment variable (disables colors)
- TTY detection (`process.stdout.isTTY`)
- CI environment detection (GitHub Actions, GitLab CI, CircleCI)
This ensures readable logs across all environments including CI systems, Windows terminals, and redirected output.
### Database adapters
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.
```ts
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:
```ts
const adapter = new MySQLRoleAdapter({
table: 'roles',
columns: { name: 'rname', role: 'rdef', tenantId: 'tid' }
})
```
### Multi-tenant RBAC
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:
```ts
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](examples/) folder.
### Middlewares
RBAC also provides helper middlewares for **Express**, **NestJS** and **Fastify**.
They make it easy to guard routes using existing role definitions.
```ts
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.
## Roadmap
- [X] Wildcard support
- [X] Regex support
- [X] Update roles in runtime
- [X] Async `when` callbacks
- [X] Database adapters (MongoDB, MySQL, PostgreSQL)
- [X] Middlewares for Express, NestJS and Fastify
## v2.0.0
- Rewritten in TypeScript
- Internal refactor focused on readability and performance
- Added support to update roles at runtime
- Database adapters
- Middlewares for Express, NestJS and Fastify
## Benchmarks
Run `npm run bench` to execute the performance suite. The script runs two end-to-end scenarios:
- **Baseline comparison** – compares `@rbac/rbac` with AccessControl, RBAC, Easy RBAC and Fast RBAC using the default dataset.
- **Large dataset comparison** – stresses the libraries with hundreds of resources, deep inheritance chains and three `when` flavours (callback, async function and promise).
For each scenario the suite generates detailed reports (JSON/CSV/HTML chart) under `benchmarks/results/` and prints a human-readable summary (ops/sec, margins, standard deviation, samples, etc.).
```
$ npm run bench
RBAC Performance Comparison ops/sec: 6859456, 6193737, 4427263, ...
RBAC Performance Comparison - Large Dataset ops/sec: 3277352, 3396327, 3424089, ...
```
The baseline run shows @rbac/rbac leading all categories; the large dataset confirms the same behaviour when conditional checks and large permission sets come into play.
## More Information
- [Migration guide from v1 to v2](docs/migrating-v1-to-v2.md)
- [Changelog](CHANGELOG.md)
## Contributing
#### Contributions are welcome!
1. Build RBAC
* Run `npm install` (or `yarn install`) to get RBAC's dependencies
* Run `npm run build` to compile the library and produce the minified bundle using Vite
2. Development mode
* Having all the dependencies installed run `yarn 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.
3. Running the tests
* Run `yarn test`
4. Scripts
* `npm run build` - produces production version of your library under the `lib` folder and generates `lib/@rbac/rbac.min.js` via Vite
* `npm run dev` - produces development version of your library and runs a watcher
* `npm test` - well ... it runs the tests :)
* `npm run test:watch` - same as above but in a watch mode
* `npm run bench` - run the benchmark suite
## License
This project is under MIT License [https://opensource.org/licenses/MIT]