Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/sergey-telpuk/nestjs-rbac
Awesome RBAC for NestJs
https://github.com/sergey-telpuk/nestjs-rbac
javascript nestjs nodejs npm rbac
Last synced: 1 day ago
JSON representation
Awesome RBAC for NestJs
- Host: GitHub
- URL: https://github.com/sergey-telpuk/nestjs-rbac
- Owner: sergey-telpuk
- License: other
- Created: 2019-09-10T10:51:32.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2023-07-11T08:15:43.000Z (over 1 year ago)
- Last Synced: 2025-01-13T03:04:22.243Z (8 days ago)
- Topics: javascript, nestjs, nodejs, npm, rbac
- Language: TypeScript
- Homepage:
- Size: 203 KB
- Stars: 438
- Watchers: 5
- Forks: 37
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
[![npm version](https://badge.fury.io/js/nestjs-rbac.svg)](https://badge.fury.io/js/nestjs-rbac)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/sergey-telpuk/nestjs-rbac/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/sergey-telpuk/nestjs-rbac/)
[![codecov](https://codecov.io/gh/sergey-telpuk/nestjs-rbac/branch/master/graph/badge.svg)](https://codecov.io/gh/sergey-telpuk/nestjs-rbac)
[![npm](https://img.shields.io/npm/dw/nestjs-rbac)](https://www.npmjs.com/package/nestjs-rbac)
![RBAC CI](https://github.com/sergey-telpuk/nestjs-rbac/workflows/RBAC%20CI/badge.svg)
![RBAC CD](https://github.com/sergey-telpuk/nestjs-rbac/workflows/RBAC%20CD/badge.svg)# Join our discord server: [Link](https://discord.gg/Gu3KxPJNg3)
## Description
The **rbac** module for [Nest](https://github.com/nestjs/nest). Support NestJS ^8.0.0 || ^9.0.0 || ^10.0.0## Installation
npm i --save nestjs-rbac## Quick Start
For using `RBAC` there is need to implement `IStorageRbac`
```typescript
export interface IStorageRbac {
roles: string[];
permissions: object;
grants: object;
filters: { [key: string]: any | IFilterPermission };
}
```
### For instance:
```typescript
export const RBAC: IStorageRbac = {
roles: ['admin', 'user'],
permissions: {
permission1: ['create', 'update', 'delete'],
permission2: ['create', 'update', 'delete'],
permission3: ['filter1', 'filter2', RBAC_REQUEST_FILTER],
permission4: ['create', 'update', 'delete'],
permission5: ['ASYNC_filter1', 'ASYNC_filter2', ASYNC_RBAC_REQUEST_FILTER],
},
grants: {
admin: [
'&user',
'permission1',
'permission3',
'permission5',
],
user: ['&userRoot', 'permission2', 'permission1@create', 'permission3@filter1', 'permission5@ASYNC_filter1'],
userRoot: ['permission4'],},
filters: {
filter1: TestFilterOne,
filter2: TestFilterTwo,
ASYNC_filter1: TestAsyncFilterOne,
ASYNC_filter2: TestAsyncFilterTwo,
[RBAC_REQUEST_FILTER]: RequestFilter,
[ASYNC_RBAC_REQUEST_FILTER]: RequestAsyncFilter,
},
};
```### Storage consists of the following keys:
`roles`: array of roles
`permissions`: objects of permissions which content actions
`grants`: objects of assigned permission to roles
`filters`: objects of customized behavior
prefix `ASYNC_` use for async operations
### Grant symbols
`&`: extends grant by another grant, for instance `admin` extends `user` _(only support one level inheritance)_`@`: a particular action from permission, for instance `permission1@update`
### Using RBAC like an unchangeable storage
```typescript
import { Module } from '@nestjs/common';
import { RBAcModule } from 'nestjs-rbac';@Module({
imports: [
RBAcModule.forRoot(IStorageRbac),
],
controllers: []
})
export class AppModule {}
```
### Using RBAC like a dynamic storage
_There is enough to implement IDynamicStorageRbac interface._
```typescript
import { Module } from '@nestjs/common';
import { RBAcModule } from 'nestjs-rbac';@Module({
imports: [
RBAcModule.forDynamic(DynamicStorageService),
],
controllers: []
})
export class AppModule {}
// implement dynamic storage
import { IDynamicStorageRbac, IStorageRbac } from 'nestjs-rbac';
@Injectable()
export class DynamicStorageService implements IDynamicStorageRbac {
constructor(
private readonly repository: AnyRepository
) {}
async getRbac(): Promise {
//use any persistence storage for getting `RBAC`
return await this.repository.getRbac();
}
}
```
#### Using for routers RBAcPermissions```typescript
import {RBAcPermissions, RBAcGuard} from 'nestjs-rbac';
import {RBAcAsyncPermissions} from "./rbac.permissions.decorator";@Controller()
export class RbacTestController {@RBAcPermissions('permission', 'permission@create')
@UseGuards(
// Any Guard for getting & adding user to request which implements `IRole` interface from `nestjs-rbac`:
//*NOTE:
// const request = context.switchToHttp().getRequest();
// const user: IRole = request.user;
GuardIsForAddingUserToRequestGuard,
RBAcGuard,
)
@Get('/')
async test1(): Promise {
return true;
}
}// example Async
@Controller()
export class RbacAsyncTestController {@RBAcAsyncPermissions('permission1')
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/admin-permission1')
async test1(): Promise {
return true;
}@RBAcAsyncPermissions('permission2', 'permission1')
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/admin-permission1-and-permission2')
async test2(): Promise {
return true;
}@RBAcAsyncPermissions('permission4')
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/admin-permission4')
async test3(): Promise {
return true;
}@RBAcAsyncPermissions(`permission5@${ASYNC_RBAC_REQUEST_FILTER}`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/admin-request-filter')
async test4(): Promise {
return true;
}@RBAcAsyncPermissions(`permission4`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/user-extends-userRoot')
async test5(): Promise {
return true;
}@RBAcAsyncPermissions(`permission1@create`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/user-permission1@create')
async test7(): Promise {
return true;
}@RBAcAsyncPermissions(`permission1@delete`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/user-permission1@delete')
async test8(): Promise {
return true;
}@RBAcAnyAsyncPermissions(
[`permission1@delete`],
[`permission1@create`]
)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/user-permission1@deleteOrCreate')
async test9(): Promise {
return true;
}
}// example
export class RbacTestController {@RBAcPermissions('permission1')
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/admin-permission1')
async test1(): Promise {
return true;
}@RBAcPermissions('permission2', 'permission1')
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/admin-permission1-and-permission2')
async test2(): Promise {
return true;
}@RBAcPermissions('permission4')
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/admin-permission4')
async test3(): Promise {
return true;
}@RBAcPermissions(`permission3@${RBAC_REQUEST_FILTER}`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/admin-request-filter')
async test4(): Promise {
return true;
}@RBAcPermissions(`permission4`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/user-extends-userRoot')
async test5(): Promise {
return true;
}@RBAcPermissions(`permission1@create`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/user-permission1@create')
async test7(): Promise {
return true;
}@RBAcPermissions(`permission1@delete`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/user-permission1@delete')
async test8(): Promise {
return true;
}@RBAcAnyPermissions(
[`permission1@delete`],
[`permission1@create`]
)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/user-permission1@deleteOrCreate')
async test9(): Promise {
return true;
}```
### Variety of the decorators`@RBAcPermissions`: obtain 'permission', 'permission@create'
`@RBAcAnyPermissions`: obtain ['permission'], ['permission@create']
`@RBAcAsyncPermissions`: obtain ['permission'], ['permission@create']
`@RBAcAnyAsyncPermissions` obtain ['permission'], ['permission@create'] and async filter
#### Async filter
For using async filter add `ASYNC_`#### Using for a whole controller
It's applicable with the crud library, for example [nestjsx/crud](https://github.com/nestjsx/crud)
```typescript
import { RBAcPermissions, RBAcGuard } from 'nestjs-rbac';@Crud({
model: {
type: Company,
},
})
@RBAcPermissions('permission2')
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Controller('companies')
export class CompaniesController implements CrudController {
constructor(public service: CompaniesService) {}
}
```
### one more example
```typescript
@Crud({
model: {
type: Company,
},
routes: {
getManyBase: {
interceptors : [],
decorators: [RBAcPermissions('permission1')],
},
createOneBase: {
interceptors : [],
decorators: [RBAcPermissions('permission2')],
},
},
})
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Controller('companies')
export class CompaniesController implements CrudController {
constructor(public service: CompaniesService) {
}
}
```
#### Using like service
```typescript
import { RbacService } from 'nestjs-rbac';@Controller()
export class RbacTestController {constructor(
private readonly rbac: RbacService
){}@Get('/')
async test1(): Promise {
return await (await this.rbac.getRole(role)).can('permission', 'permission@create');
return true;
}
}
```
#### Using the custom filters
`filter` is a great opportunity of customising behaviour RBAC.
For creating `filter`, there is need to implement `IFilterPermission` interface, which requires for implementing `can` method, and bind a key filter with filter implementation, like below:
```typescript
export const RBAC: IStorageRbac = {
roles: ['role'],
permissions: {
permission1: ['filter1', 'filter2'],
},
grants: {
role: [
`permission1@filter1`
`permission1@filter2`
],
},
filters: {
filter1: TestFilter,
filter2: TestAsyncFilter,
},
};
//===================== implementing filter
import { IFilterPermission } from 'nestjs-rbac';export class TestFilter implements IFilterPermission {
can(params?: any[]): boolean {
return params[0];
}}
//===================== implementing async filter
import { IFilterPermission } from 'nestjs-rbac';@Injectable()
export class TestAsyncFilter implements IFilterPermission {
constructor(private readonly myService: MyService) {}async canAsync(params?: any[]): Promise {
const myResult = await this.myService.someAsyncOperation()
// Do something with myResult
return myResult;
}
}
```
:warning: - A single filter can implement both `can` and `canAsync`. If you use the RBAcGuard, they will be evaluated with an **AND** condition.`ParamsFilter` services for passing arguments into particular filter:
```typescript
const filter = new ParamsFilter();
filter.setParam('filter1', some payload);const res = await (await rbacService.getRole('admin', filter)).can(
'permission1@filter1',
);
```
Also RBAC has a default filter `RBAC_REQUEST_FILTER` which has `request` object as argument:
##### Example:
```typescript
//===================== filter
export class RequestFilter implements IFilterPermission {can(params?: any[]): boolean {
return params[0].headers['test-header'] === 'test';
}
}
//===================== storage
export const RBAC: IStorageRbac = {
roles: ['role'],
permissions: {
permission1: ['filter1', 'filter2', RBAC_REQUEST_FILTER],
},
grants: {
role: [
`permission1@${RBAC_REQUEST_FILTER}`
],
},
filters: {
[RBAC_REQUEST_FILTER]: RequestFilter,
},
};
//===================== using for routes
@RBAcPermissions(`permission1@${RBAC_REQUEST_FILTER}`)
@UseGuards(
AuthGuard,
RBAcGuard,
)
@Get('/')
async test4(): Promise {
return true;
}
```
### Performance
By default, RBAC storage always parses grants for each request, in some cases, it can be a very expensive operation.
The bigger RBAC storage, the more taking time for parsing. For saving performance RBAC has built-in a cache, based on [node-cache](https://github.com/node-cache/node-cache)
#### Using cache
```typescript
import { RbacCache } from 'nestjs-rbac';@Module({
imports: [
RBAcModule.useCache(RbacCache, {KEY: 'RBAC', TTL: 400}).forDynamic(AsyncService),
],
})
```
if you need to change a cache storage, there is enough to implement `ICacheRBAC`
#### ICacheRBAC
```typescript
export interface ICacheRBAC {
KEY: string;
TTL: number;get(): object | null;
/**
*
* @param value
*/
set(value: object): void;del(): void;
}
```
#### Inject `ICacheRBAC`
```typescript
import { ICacheRBAC } from 'nestjs-rbac';
...
@Inject('ICacheRBAC') cache: ICacheRBAC
```