This module is compatible with
nestjs@^8.0.0
.
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 !
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 ) );
} );
} );
Generated using TypeDoc