Custom Policies
The AccessPolicy
decorator supports more complex policies custom made for your specific use cases.
A policy is any function with the following signature:
type PolicyMethod = (ctx: ExecutionContext, authData: any, moduleRef: ModuleRef) => void;
A short summary of what the parameters mean:
ctx: ExecutionContext
: useful when therequest
object is needed for your business logic. See NestJS execution context for more.authData: any
: the user's metadata generated by your authentication middleware. Commonly used for accessing user/session information. Retrieved using authDataKey.moduleRef: ModuleRef
: commonly used when accessing injected dependencies such asPrismaService
inside your policy functions. See NestJS module reference.
#
Recipestip
Examples below only seek to illustrate the custom policies capabilities.
The features themselves would likely be implemented differently in practice!!
#
JohnnyIsBanned PolicyThis is an example of a simple static policy that throws 403
errors for the user named Johnny.
#
Implementationimport { ExecutionContext, ForbiddenException } from '@nestjs/common';import { ModuleRef } from '@nestjs/core';
export const JohnnyIsBanned = (ctx: ExecutionContext, authData: any, moduleRef: ModuleRef) => { if (authData.user.name === 'Johnny') { throw new ForbiddenException('Bye Johnny!'); }};
#
Usage@Controller('party')export class PartyController { // ... @Get() @AccessPolicy('everyone', JohnnyIsBanned) async getParties(@Query('crudQuery') crudQuery: string) { const match = await this.partyService.findMany(crudQuery); return match; }}
#
BannedPeople PolicyThis takes the same idea of the previous example, but now we have a function that receives a list of names to ban, and returns a PolicyMethod.
#
Implementationimport { ExecutionContext, ForbiddenException } from '@nestjs/common';import { ModuleRef } from '@nestjs/core';
export const BannedPeople = (bannedNames: string[]) => ( ctx: ExecutionContext, authData: any, moduleRef: ModuleRef,) => { if (bannedNames.includes(authData.user.name)) { throw new ForbiddenException('You are not alone Johnny!'); }};
#
Usage@Controller('party')export class PartyController { // ... @Get() @AccessPolicy('everyone', BannedPeople(['Johnny', 'Danny'])) async getParties(@Query('crudQuery') crudQuery: string) { const match = await this.partyService.findMany(crudQuery); return match; }}
#
DbBannedPeople PolicyThis example takes the same idea of the previous, but queries the database instead of using a hard coded list.
#
Implementationimport { ExecutionContext, ForbiddenException } from '@nestjs/common';import { ModuleRef } from '@nestjs/core';
export const DbBannedPeople = (ctx: ExecutionContext, authData: any, moduleRef: ModuleRef) => { const prismaService = moduleRef.get(PrismaService, { strict: false }); const bannedPeople = prismaService.bannedPeople.findMany();
if (bannedPeople.includes(authData.user.name)) { throw new ForbiddenException('You have been banned!'); }};
#
Usage@Controller('party')export class PartyController { // ... @Get() @AccessPolicy('everyone', DbBannedPeople) async getParties(@Query('crudQuery') crudQuery: string) { const match = await this.partyService.findMany(crudQuery); return match; }}
crudQuery)#
HideDrafts Policy (extendingIn this section we are going to write a policy that modifies the final prisma query, by extending the client's provided crudQuery
and adding additional constraints to it.
This technique is extremely useful when a user must be granted access to only a subset of records from a given table. It ensures that we scope the client's access while not breaking the requested sorting/filtering/pagination.
Below is a policy that only retrieves non-draft parties
from the database. The highlighted additionalConstraints
can be any prisma filter object .
#
Implementationimport { ExecutionContext } from '@nestjs/common';import { ModuleRef } from '@nestjs/core';import { CrudQuery } from 'nestjs-prisma-crud';
export const HideDrafts = (ctx: ExecutionContext, _authData: any, _moduleRef: ModuleRef) => { // get crudQuery from the request's query params const request = ctx.switchToHttp().getRequest(); const query = request.query; const crudQuery: string = query.crudQuery;
// get client provided `.where`, if any const parsedCrudQuery: CrudQuery = crudQuery ? JSON.parse(crudQuery) : {}; const clientWhere = parsedCrudQuery.where || {};
// additionalConstraints is any valid prisma filter object const additionalConstraints = { state: { not: 'DRAFT' } };
// override client `.where`, adding the additional `AND` clause parsedCrudQuery.where = { AND: [additionalConstraints, clientWhere], };
request.query.crudQuery = JSON.stringify(parsedCrudQuery);};
#
Usage@Controller('party')export class PartyController { // ... @Get() @AccessPolicy('everyone', HideDrafts) async getParties(@Query('crudQuery') crudQuery: string) { const match = await this.partyService.findMany(crudQuery); return match; }}