DEV Community

Sergey Telpuk
Sergey Telpuk

Posted on • Edited on

13

Write RBAC for NestJS

Hello there!

Recently, I came across with the difficulty of finding a good solution to Role-based access control (RBAC) for NestJS so I decided to make my own:).
The done repository is here

Quick Start

For using RBAC there is need to implement IStorageRbac

export interface IStorageRbac {
  roles: string[];
  permissions: object;
  grants: object;
  filters: { [key: string]: any | IFilterPermission };
}
Enter fullscreen mode Exit fullscreen mode

For instance:

export const RBACstorage: IStorageRbac = {
  roles: ['admin', 'user'],
  permissions: {
    permission1: ['create', 'update', 'delete'],
    permission2: ['create', 'update', 'delete'],
    permission3: ['filter1', 'filter2', RBAC_REQUEST_FILTER],
    permission4: ['create', 'update', 'delete'],
  },
  grants: {
    admin: [
      '&user',
      'permission1',
      'permission3',
    ],
    user: ['permission2', 'permission1@create', 'permission3@filter1'],
  },
  filters: {
    filter1: TestFilterOne,
    filter2: TestFilterTwo,
    [RBAC_REQUEST_FILTER]: RequestFilter,
  },
};
Enter fullscreen mode Exit fullscreen mode

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 customs roles

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

import { Module } from '@nestjs/common';
import { RBAcModule } from 'nestjs-rbac';

@Module({
  imports: [
    RBAcModule.forRoot(RBAC),
  ],
  controllers: []
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Using RBAC like a dynamic storage

There is enough to implement IDynamicStorageRbac interface.

import { Module } from '@nestjs/common';
import { RBAcModule } from 'nestjs-rbac';

@Module({
  imports: [
    RBAcModule.forDynamic(AsyncService),
  ],
  controllers: []
})
export class AppModule {}
// implement dynamic storage
import { IDynamicStorageRbac, IStorageRbac } from 'nestjs-rbac';
@Injectable()
export class AsyncService implements IDynamicStorageRbac {
  constructor(
    private readonly repository: AnyRepository
  ) {

  }
  async getRbac(): Promise<IStorageRbac> {
      return  await this.repository.getRbac(); //use any persistence storage for getting RBAC
  }
}
Enter fullscreen mode Exit fullscreen mode

Using for routers

import { RBAcPermissions, RBAcGuard } from 'nestjs-rbac';

@Controller()
export class RbacTestController {

  @RBAcPermissions('permission', 'permission@create')
  @UseGuards(
    AuthGuard, // need for using user into the request
    RBAcGuard,
  )
  @Get('/')
  async test1(): Promise<boolean> {
    return true;
  }
}
Enter fullscreen mode Exit fullscreen mode

Using like service

import { RbacService} from 'nestjs-rbac';

@Controller()
export class RbacTestController {

  constructor(
    private readonly rbac: RbacService
  ){}

  @Get('/')
  async test1(): Promise<boolean> {
    await this.rbac.getRole(role).can('permission', 'permission@create');
    return true;
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

export const RBAC: IStorageRbac = {
  roles: ['role'],
  permissions: {
    permission1: ['filter1'],
  },
  grants: {
    role: [
      `permission1@filter1`
    ],
  },
  filters: {
    filter1: TestFilter,
  },
};  
//===================== implementing filter
import { IFilterPermission} from 'nestjs-rbac';

export class TestFilter implements IFilterPermission {

  can(params?: any[]): boolean {
    return params[0];
  }

}
Enter fullscreen mode Exit fullscreen mode

ParamsFilter services for passing arguments into particular filter:

const filter = new ParamsFilter();
filter.setParam('filter1', some payload);

const res = await rbacService.getRole('admin', filter).can(
  'permission1@filter1',
);
Enter fullscreen mode Exit fullscreen mode

Also RBAC has a default filter RBAC_REQUEST_FILTER which has request object as argument:

Example:
//===================== 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<boolean> {
    return true;
  }
Enter fullscreen mode Exit fullscreen mode

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (9)

Collapse
 
dkrest1 profile image
Oluwatosin Akande • Edited

Please I am finding hard to apply this package to my project, please can someone put me thorugh

Collapse
 
sergey_telpuk profile image
Sergey Telpuk

Hello, I can do it. Ping me.

Collapse
 
dkrest1 profile image
Oluwatosin Akande

thanks much, I got to use the in built one with nestjs

Collapse
 
dkrest1 profile image
Oluwatosin Akande

Please can I have your linkedin link for connection, thank you

Thread Thread
 
sergey_telpuk profile image
Sergey Telpuk
Thread Thread
 
dkrest1 profile image
Oluwatosin Akande

thanks Sergey, sent a connection request already

Collapse
 
samrith profile image
Samrith Shankar

Hey, is this still package still actively maintained?

Collapse
 
sergey_telpuk profile image
Sergey Telpuk

Hi, sure

Collapse
 
aqlx86 profile image
Arnel Labarda

encountered some errors, I posted in the GitHub issue.