@nextnm/nestjs-next-guard
TypeScript icon, indicating that this package has built-in type declarations

2.2.5 • Public • Published

NestJS NextGuard

NPM Version Package License NPM Downloads

Introduction

This is simple guard which can protect your routes by Role (RBAC) and also has the ability to check the ownership chain of the requested entity.

Example

As an example consider that you are a user belonging to an organization which has some books associated. Now, if you request a book by id this guard will check if the book that you are trying to fetch belongs to the organization that you are associated and so on. You only need to pass the models chain as well as the property chain.

Installation

npm i @nextnm/nestjs-next-guard

Usage

Caveats

  1. We only support applications using mongodb.
  2. You must be sure that in every request that the guard is used there is a user property in the request with an array of roles.
  3. It will only contemplates situations where you a have an id as request param (GET ("/:id")) or in the body (PUT updating object {_id:"...", "property1":....})

Interface Decorator

export interface ICheckOwnerShip {
  requestParam: string; // name of param that has the id mentioned in caveat 3
  modelChain: string[]; // the chain of ownership between models
  propertyChain: string[]; // array of the properties that link the models
  godRole?: string; // the role that will overcome the verification
}

Importing module

import * as mongoose from 'mongoose';
import { NextGuardModule } from '@nextnm/nestjs-next-guard';

...
@Module({
  imports: [
    DbModule,
    NextGuardModule.forRoot(),
  ],
  controllers: [],
  providers: [],
  exports: [NestjsNextGuardModule],
})
export class YOURModule {}

Importing module (Redis integration)

We support Redis cache to improve performance when we have multiple chain nodes to verify ownership

import * as mongoose from 'mongoose';
import { NextGuardModule } from '@nextnm/nestjs-next-guard';

...
@Module({
  imports: [
    DbModule,
    NextGuardModule.forRoot(
      {
        redisConfiguration: {
          url: 'redis://localhost:6379'
          retry_strategy: () => 1000,
          mongooseInstance: mongoose,
        },
      }
    ),
  ],
  controllers: [],
  providers: [],
  exports: [NestjsNextGuardModule],
})
export class YOURModule {}

Using decorators

Be aware that both decorators (Roles and CheckOwnerShip) are optional so use them as you want.

1. Use Case

User:
{
  _id:ObjectId
}

Site:
{
  _id:ObjectId,
  user:ObjectId
}

Page:
{
  _id:ObjectId,
  site:ObjectId
}
Description (A user belongs to an Site)
  1. The guard will take a look if you have role based permission to use this route
  2. The guard will look for an "Page" (modelChain[0]) by id equals to the request param;
  3. From the found Page it will try to grab the property 'site' (propertyChain[0]) and find a Site (modelChain[1]) by id equal to that property (propertyChain[0]).
  4. From the Site found it will check if the property "user" matches the id of the user making the request.
  import { CheckOwnerShip, Roles } from '@nextnm/nestjs-next-guard';

  ...

  @CheckOwnerShip({
    requestParam: 'modelId',
    propertyChain: ['site', 'user'], // The last property will be compared with the Id of the user making the request
    modelChain: ['Page','Site'],
    godRole: ExistingRoles.SYS_ADMIN, // If the user has this role not check will be done by the guard
  })
  @Roles(ExistingRoles.USER, ExistingRoles.ADMIN) // Provide the roles that you allow to execute this method,example: 'USER', 'ADMIN'
  @UseGuards(NextGuard)
  @Get(':modelId')
  async findPageById(@Param('id') id: string) {
    //...
  }

2. Use case

User:
{
_id:ObjectId,
organization:ObjectId
}


Organization:
{
  _id:ObjectId
}
Description (A user belongs to an organization)
  1. The guard will take a look if you have role based permission to use this route
  2. The guard will look for an Organization (modelChain[0]) by id equals to the request param;
  3. From the found Organization it will try to grab the property '_id' (propertyChain[0]) and find a User (modelChain[1]) by id equal to the that property (propertyChain[0]).
  4. Since there isn't any, it will try to find a User with a property "organization" (propertyChain[1]) equals to the organization "_id" property(propertyChain[0])
  5. From the User found it will check if the property "_id" matches the id of the user making the request.
  import { CheckOwnerShip, Roles } from '@nextnm/nestjs-next-guard';

    ...

    @CheckOwnerShip({
    requestParam: 'id',
    propertyChain: ['_id','organization','_id'], // The last property will be compared with the Id of the user making the request
    modelChain: ['Organization','User'],
    godRole: ExistingRoles.SYS_ADMIN, // If the user has this role not check will be done by the guard
  })
  @Roles(ExistingRoles.USER, ExistingRoles.ADMIN) // Provide the roles that you allow to execute this method,example: 'USER', 'ADMIN'
  @UseGuards(AuthGuard('jwt'),NextGuard)
  @Get('/:id')
  findOrganizationById(@Param() params): Promise<ReadOrganizationDto> {
    //...
  }

Contributing

Contributions are welcome! See Contributing.

Next steps

  1. Improve documentation
  2. Add some tests using Jest and supertest
  3. Add full support do many to many relationships between models (it doesn't alow having array of ids as relationships)
  4. Build Policy Based Guard

Author

Nuno Carvalhão (nextnm/nextNC) Site

License

Licensed under the MIT License - see the LICENSE file for details.

Package Sidebar

Install

npm i @nextnm/nestjs-next-guard

Weekly Downloads

4

Version

2.2.5

License

MIT

Unpacked Size

266 kB

Total Files

36

Last publish

Collaborators

  • nextnm