Options
All
  • Public
  • Public/Protected
  • All
Menu

Getting started

This module is compatible with nestjs@^8.0.0.

Setup & configuration

First, install the module:

npm install @knodes/nest-casl

Then, declare a provider that generate a CASL Ability from the current request.

From src/ability-factory.service.ts

@Injectable()
export class AbilityFactory implements CaslAbilityFactory {
// Here, \`request\` is the express or fastify request. You might get infos from it.
public createFromRequest( _request: unknown ): PureAbility {
const abilityBuilder = new AbilityBuilder( PureAbility );
abilityBuilder.can( 'feed', 'cat' );
abilityBuilder.can( 'hug', 'cat' );
abilityBuilder.cannot( 'rename', 'cat' );
return abilityBuilder.build();
}
}

Import the CaslModule in your AppModule, and configure it to use your ability factory.

From src/app.module.ts

@Module( {
imports: [
CaslModule.withConfig( ( { abilityFactory: AbilityFactory } ) ),
// ....
],
} )
export class AppModule {}

You can now start using policy decorators (Policy and PoliciesMask) in your controllers !

Basic usage

You can protect all methods of your controller using the Policy class decorator.

From src/cat-owner.controller.ts

@Controller( '/cat/owner' )
@Policy( { action: 'rename', subject: 'cat' } )
export class CatOwnerController {
// Given the ability builder above, this method will always reject.
@Post( 'rename' )
public rename( @Body() _name: string ){
// ...
}
}

This decorator can also be used to protect individual methods.

From src/cat-care.controller.ts

@Controller( '/cat/care' )
export class CatCareController {
// Okay, you can feed.
@Get( 'feed' )
@Policy( { action: 'feed', subject: 'cat' } )
public feed(){
// ...
}

// Well, I guess he won't bite.
@Get( 'hug' )
@Policy( { action: 'hug', subject: 'cat' } )
public hug(){
// ...
}
}

If you want to group various policies in the same decorator at the controller level, use the PoliciesMask decorator.

From src/cat.controller.ts

@Controller( '/cat' )
@PoliciesMask( {
feed: { action: 'feed', subject: 'cat' },
hug: { action: 'hug', subject: 'cat' },
rename: { action: 'rename', subject: 'cat' },
} )
export class CatController {
@Get( 'feed' )
public feed(){
// ...
}

@Get( 'hug' )
public hug(){
// ...
}

@Post( 'rename' )
public rename( @Body() _name: string ){
// ...
}
}

Check the tests !

From test/cats.e2e-spec.ts

describe( 'Basic usage', () => {
let app: INestApplication;

beforeAll( async () => {
const moduleRef = await Test.createTestingModule( {
imports: [ CaslModule.withConfig( { abilityFactory: AbilityFactory } ) ],
controllers: [
CatOwnerController, CatCareController, CatController,
],
} ).compile();

app = moduleRef.createNestApplication();
await app.init();
} );

describe( 'CatOwnerController', () => {
it( 'should not be able to rename a cat', () => request( app.getHttpServer() )
.post( '/cat/owner/rename' )
.expect( HttpStatus.FORBIDDEN ) );
} );
describe( 'CatCareController', () => {
it( 'should be able to feed the cat', () => request( app.getHttpServer() )
.get( '/cat/care/feed' )
.expect( HttpStatus.OK ) );
it( 'should be able to hug the cat', () => request( app.getHttpServer() )
.get( '/cat/care/hug' )
.expect( HttpStatus.OK ) );
} );
describe( 'PoliciesMask', () => {
it( 'should not be able to rename a cat', () => request( app.getHttpServer() )
.post( '/cat/rename' )
.expect( HttpStatus.FORBIDDEN ) );
it( 'should be able to feed the cat', () => request( app.getHttpServer() )
.get( '/cat/feed' )
.expect( HttpStatus.OK ) );
it( 'should be able to hug the cat', () => request( app.getHttpServer() )
.get( '/cat/hug' )
.expect( HttpStatus.OK ) );
} );
} );

What next ?

Use with guards

Generated using TypeDoc