Is it possible to pass a parameter to a nestjs guard? - nestjs

I'm trying to come up with a somewhat reusable guard and it looks like I need to pass a string param to a guard. Is it achievable in nestjs?

It sounds like you are looking to use a mixin, a function that returns a class. I'm not sure what kind of parameter you're passing, but the idea is
export const RoleGuard = (role: string) => {
class RoleGuardMixin implements CanActivate {
canActivate(context: ExecutionContext) {
// do something with context and role
return true;
}
}
const guard = mixin(RoleGuardMixin);
return guard;
}
mixin as a function is imported from #nestjs/common and is a wrapper function that applies the #Injectable() decorator to the class
Now to use the guard, you need to do something like #UseGuards(RoleGuard('admin'))

It seems impossible to use mixin in Guard in NestJs. It will throw Exported variable 'RoleGuard' has or is using private name 'RoleGuardMixin'.
Actually, you may use setMetadata to pass in the parameters one by one, then get it from the Guard using reflector from from '#nestjs/core'.
#Injectable()
export class RoleGuard implements CanActivate {
constructor(
private reflector: Reflector,
) {}
canActivate(context: ExecutionContext) {
const roleName = this.reflector.get<string>('roleName', context.getHandler());
return true;
}
}
#Get()
#SetMetadata('roleName', 'developer')
async testRoleGuard() {
return true;
}
Or you may define a decorator to pass the parameter in.
export const RoleName = (roleName: string) => SetMetadata('roleName', roleName);
#Get()
#RoleName('developer')
async testRoleGuard() {
return true;
}

Usage:
#UseGuards(new AuthorizeGuard('read', 'users'))
Guard:
#Injectable()
export class AuthorizeGuard implements CanActivate {
constructor(private action, private subject) {}
canActivate(context: ExecutionContext): boolean {
//you can use this.action and this.subject
}
}

If anyone is struggling with testing the mixin approach #JayMcDoniel brilliantly pointed out, here's a sample setup from a guard I implemented using the mixin.
describe('PaymentGuard', () => {
let guard: CanActivate;
beforeEach(async () => {
const guardProvider = PaymentGuard(PaymentGuardHandlerToken.EXPECT_PLATFORM_ENABLED);
const module = await Test.createTestingModule({
providers: [
guardProvider,
{
provide: PaymentService,
useValue: MOCK_PAYMENT_SERVICE,
},
{
provide: EntityManager,
useValue: MOCK_ENTITY_MANAGER,
}
],
}).compile();
guard = module.get(guardProvider);
});
});
And then you just call the guard.canActivate within your test.

Related

Is it possible to intercept calls from one controller to another one in Nestjs?

and I'm working in a backend application and I want to reuse some api writed before in other controller but its necesary that when I call a method for example from controller A to Controller B it must be intecepted by a Guard, middleware, etc. I'm using a global guard, that intercept any request call. And I tried something like the example below but just intercept the first call triggered on controller A but when call to controller B it dosent trigger
#Controller('controller-a')
export class ControllerA {
#Get()
methodA(){
const respFromB = await ControllerB.prototype.methodB({ ..some data.. });
enter code here
return '...'
}
}
#Controller('controller-b')
export class ControllerB {
#Post()
methodB(
#Body() data: any
) {
... some other code...
return 'books';
}
}
// main.ts
const reflector = app.get(Reflector);
const authService = app.get(AuthService);
const prismaClient = app.get(PrismaClient);
app.useGlobalGuards(new MyGlobalGuard(reflector, authService, prismaClient));
// MyGlobalGuard.ts
#Injectable()
export class MyGlobalGuard implements CanActivate {
public constructor(
private readonly reflector: Reflector,
private readonly authService: AuthService,
private readonly prisma: PrismaClient,
) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
....
return true;
}
}
As you're the one calling from one controller to another, no it's not possible. You'd need to make an HTTP request from your server to your server to trigger the guards and interceptors again. It's Nest's internal route handler that's in charge of calling these enhancers, so you can't get to them from directly calling the class

NestJs - Validate request body using class-validator having 2 options for body class

I have a rest call, which might receive body of type classA or classB.
I need to keep it as 2 different classes.
Example -
// classes -
class ClassA {
#IsString()
#Length(1, 128)
public readonly name: string;
#IsString()
#Length(1, 128)
public readonly address: string;
}
class ClassB {
#IsString()
#Length(1, 10)
public readonly id: string;
}
// my request controller -
#Post('/somecall')
public async doSomething(
#Body(new ValidationPipe({transform: true})) bodyDto: (ClassA | ClassB) // < not validating any of them..
): Promise<any> {
// do something
}
The issue is, that when having more than one class, body is not validated.
How can I use 2 or more classes and validate them using class-validator?
I don't want to use same class..
Thank you all :)
I don't want to use same class..
Then it won't be possible, at least not with Nest's built-in ValidationPipe. Typescript doesn't reflect unions, intersections, or other kinds of generic types, so there's no returned metadata for this parameter, and if there's no metadata that's actionable Nest will end up skipping the pipe.
You could probably create a custom pipe to do the validation for you, and if you have two types you're probably going to have to. You can still call the appropriate class-transformer and class-validator methods inside of the class too.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { of } from 'rxjs';
#Injectable()
export class CheckTypeInterceptor implements NestInterceptor {
constructor() {}
async intercept(context: ExecutionContext, next: CallHandler) /*: Observable<any>*/ {
const httpContext = context.switchToHttp();
const req = httpContext.getRequest();
const bodyDto = req.body.bodyDto;
// Need Update below logic
if (bodyDto instanceof ClassA || bodyDto instanceof ClassB) {
return next.handle();
}
// Return empty set
return of([]);
}
}
#UseInterceptors(CheckTypeInterceptor)
export class ApiController {
...
}
Encountered a similar situation where I had to validate some union type request. The solution I ended up with was a custom pipe as Jay McDoniel suggested here. The logic would vary depending on the request body you are dealing with, but per the question in case the following may work
Custom pipe:
import { ArgumentMetadata, BadRequestException, Inject, Scope } from "#nestjs/common";
import { PipeTransform } from "#nestjs/common";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
import { ClassADto } from '../repository/data-objects/class-a.dto';
import { ClassBDto } from '../repository/data-objects/class-b.dto';
export class CustomPipeName implements PipeTransform<any> {
async transform(value: any, { metatype, type }: ArgumentMetadata): Promise<any> {
if (type === 'body') {
const classA = plainToInstance(ClassADto, value);
const classB = plainToInstance(ClassBDto, value);
const classAValidationErrors = await validate(classA);
const classBValidationErrors = await validate(classB);
if (classAValidationErrors.length > 0 && classBValidationErrors.length > 0) {
throw new BadRequestException('some fancy info text');
}
}
return value;
}
}
Controller usage:
#Post('/somecall')
public async doSomething(
#Body(new CustomePipeName()) bodyDto: (ClassA | ClassB)
): Promise<any> {
// do something
}

How can I add an argument in an injectable constructor but still let Nest manage dependency injection?

I have created a custom guard which looks like this:
#Injectable()
export class MyCustomGuard implements CanActivate {
constructor(
private reflector: Reflector,
private myService: MyService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const idParam = request.params.id;
...
}
}
I use it like this in my controller:
#UseGuards(MyCustomGuard)
#Controller('my-controller')
export class MyController {
...
}
It works fine, but I would like to set the id param key as a parameter when instantiating the guard, like this:
#Injectable()
export class MyCustomGuard implements CanActivate {
constructor(
private idKey: string,
private reflector: Reflector,
private myService: MyService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const idParam = request.params[idKey];
...
}
}
Then I will have to instantiate the guard manually in my controller:
#UseGuards(new MyCustomGuard('id', ...?))
#Controller('my-controller')
export class MyController {
...
}
My problem is that if I do that, then I will have to manually instantiate the other constructor arguments of my guard: the reflector and the service. That is possible but it is something I would rather let Nest manage, for simplicity and performance reasons. Then my question is: is there any way to instantiate the guard manually (so that I can give it the idKey argument) but still let Nest manage dependency injection for the other arguments?
It seems that using a ModuleRef would work here, but I don't really know if it's the correct way to do it.
Instead of passing the idKey as a constructor parameter, I would suggest reflecting it as metadata of the class. This way, you could do something like
#IdKey('id')
#UseGuards(MyCustomGuard)
#Controller('my-controller')
export class MyController {
...
}
and in your guard you do something like
#Injectable()
export class MyCustomGuard implements CanActivate {
constructor(
private reflector: Reflector,
private myService: MyService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const idParam = request.params.id;
const idKey = this.getIdKey(context);
...
}
getIdKey(context: ExecutionContext) {
return this.reflector.get('id-key', context.getClass());
}
}
This is assuming that your #IdKey() looks something like
export const IdKey = (key: string) => SetMetadata(key);
where SetMetadata comes from #nestjs/common
You can read more about #SetMetadata() and metadata rerflection here

NestJS - Combine multiple Guards and activate if one returns true

Is it possible to use multiple auth guards on a route (in my case basic and ldap auth).
The route should be authenticated when one guard was successful.
Short answer: No, if you add more than one guard to a route, they all need to pass for the route to be able to activate.
Long answer: What you are trying to accomplish is possible however by making your LDAP guard extend the basic one. If the LDAP specific logic succeeds, return true, otherwise return the result of the call to super.canActivate(). Then, in your controller, add either the basic or LDAP guard to your routes, but not both.
basic.guard.ts
export BasicGuard implements CanActivate {
constructor(
protected readonly reflector: Reflector
) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if () {
// Do some logic and return true if access is granted
return true;
}
return false;
}
}
ldap.guard.ts
export LdapGuard extends BasicGuard implements CanActivate {
constructor(
protected readonly reflector: Reflector
) {
super(reflector);
}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if () {
// Do some logic and return true if access is granted
return true;
}
// Basically if this guard is false then try the super.canActivate. If its true then it would have returned already
return await super.canActivate(context);
}
}
For more information see this GitHub issue on the official NestJS repository.
According to AuthGuard it just works out of the box
AuthGuard definition
If you look at AuthGuard then you see the following definition:
(File is node_modules/#nestjs/passport/dist/auth.guard.d.ts)
export declare const AuthGuard: (type?: string | string[]) => Type<IAuthGuard>;
That means that AuthGuard can receive an array of strings.
Code
In my code I did the following:
#UseGuards(AuthGuard(["jwt", "api-key"]))
#Get()
getOrders() {
return this.orderService.getAllOrders();
}
Postman test
In Postman, the endpoint can have the api-key and the JWT.
Tested with JWT in Postman Authorization: It works
Tested with API-Key in Postman Authorization: It works
That implies there is an OR function between the 2 Guards.
You can create an abstract guard, and pass instances or references there, and return true from this guard if any of the passed guards returned true.
Let's imagine you have 2 guards: BasicGuard and LdapGuard. And you have a controller UserController with route #Get(), which should be protected by these guards.
So, we can create an abstract guard MultipleAuthorizeGuard with next code:
#Injectable()
export class MultipleAuthorizeGuard implements CanActivate {
constructor(private readonly reflector: Reflector, private readonly moduleRef: ModuleRef) {}
public canActivate(context: ExecutionContext): Observable<boolean> {
const allowedGuards = this.reflector.get<Type<CanActivate>[]>('multipleGuardsReferences', context.getHandler()) || [];
const guards = allowedGuards.map((guardReference) => this.moduleRef.get<CanActivate>(guardReference));
if (guards.length === 0) {
return of(true);
}
if (guards.length === 1) {
return guards[0].canActivate(context) as Observable<boolean>;
}
const checks$: Observable<boolean>[] = guards.map((guard) =>
(guard.canActivate(context) as Observable<boolean>).pipe(
catchError((err) => {
if (err instanceof UnauthorizedException) {
return of(false);
}
throw err;
}),
),
);
return forkJoin(checks$).pipe(map((results: boolean[]) => any(identity, results)));
}
}
As you can see, this guard doesn't contain any references to a particular guard, but only accept the list of references. In my example, all guards return Observable, so I use forkJoin to run multiple requests. But of course, it can be adopted to Promises as well.
To avoid initiating MultipleAuthorizeGuard in the controller, and pass necessary dependencies manually, I'm left this task to Nest.js and pass references via custom decorator MultipleGuardsReferences
export const MultipleGuardsReferences = (...guards: Type<CanActivate>[]) =>
SetMetadata('multipleGuardsReferences', guards);
So, in controller we can have next code:
#Get()
#MultipleGuardsReferences(BasicGuard, LdapGuard)
#UseGuards(MultipleAuthorizeGuard)
public getUser(): Observable<User> {
return this.userService.getUser();
}
You can use combo guard that injects all guards what you need and combines their logic.
There is closed github issue:
https://github.com/nestjs/nest/issues/873
There is also a npm package that address this scenario: https://www.npmjs.com/package/#nest-lab/or-guard.
Then you call a unique guard that references all the necessary guards as parameters:
guards([useGuard('basic') ,useGuard('ldap')])
Inspired from https://stackoverflow.com/a/69966319/16730890
This uses Promise instead of Observable.
import {
CanActivate,
ExecutionContext,
Injectable,
SetMetadata,
Type,
} from '#nestjs/common';
import { ModuleRef, Reflector } from '#nestjs/core';
#Injectable()
export class MultipleAuthorizeGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private readonly moduleRef: ModuleRef,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const allowedGuards =
this.reflector.get<Type<CanActivate>[]>(
'multipleGuardsReferences',
context.getHandler(),
) || [];
const guards = allowedGuards.map((guardReference) =>
this.moduleRef.get<CanActivate>(guardReference),
);
if (guards.length === 0) {
return Promise.resolve(true);
}
if (guards.length === 1) {
return guards[0].canActivate(context) as Promise<boolean>;
}
return Promise.any(
guards.map((guard) => {
return guard.canActivate(context) as Promise<boolean>;
}),
);
}
}
export const MultipleGuardsReferences = (...guards: Type<CanActivate>[]) =>
SetMetadata('multipleGuardsReferences', guards);
#Get()
#MultipleGuardsReferences(BasicGuard, LdapGuard)
#UseGuards(MultipleAuthorizeGuard)
public getUser(): Promise<User> {
return this.userService.getUser();
}

How to create a NestJs Pipe with a config object and dependency?

I would Like to pass a configuration string to a Pipe but also want to inject a service. The NesJs docs describe how to do both of these independent of each other but not together. Take the following example:
pipe.ts
#Injectable()
export class FileExistsPipe implements PipeTransform {
constructor(private filePath: string, db: DatabaseService) { }
async transform(value: any, metadata: ArgumentMetadata) {
const path = value[this.filePath];
const doesExist = await this.db.file(path).exists()
if(!doesExist) throw new BadRequestException();
return value;
}
}
controller.ts
#Controller('transcode')
export class TranscodeController {
#Post()
async transcode (
#Body( new FileExistsPipe('input')) transcodeRequest: JobRequest) {
return await this.videoProducer.addJob(transcodeRequest);
}
Basically, I want to be able to pass a property name to my pipe (e.g.'input') and then have the pipe look up the value of the property in the request (e.g.const path = value[this.filePath]) and then look to see if the file exists or not in the database. If it doesn't, throw a Bad Request error, otherwise continue.
The issue I am facing is that I need NestJs to inject my DataBaseService. With the current example, It won't and my IDE gives me an error that new FileExistsPipe('input') only has one argument passed but was expecting two (e.g. DatabaseService).
Is there anyway to achieve this?
EDIT: I just checked your repo (sorry for missing it before). Your DatabaseService is undefined in the FIleExistPipe because you use the pipe in AppController. AppController will be resolved before the DatabaseModule gets resolved. You can use forwardRef() to inject the DatabaseService in your pipe if you are going to use the pipe in AppController. The good practice here is to have feature controllers provided in feature modules.
export const FileExistPipe: (filePath: string) => PipeTransform = memoize(
createFileExistPipe
);
function createFileExistPipe(filePath: string): Type<PipeTransform> {
class MixinFileExistPipe implements PipeTransform {
constructor(
// use forwardRef here
#Inject(forwardRef(() => DatabaseService)) private db: DatabaseService
) {
console.log(db);
}
async transform(value: ITranscodeRequest, metadata: ArgumentMetadata) {
console.log(filePath, this.db);
const doesExist = await this.db.checkFileExists(filePath);
if (!doesExist) throw new BadRequestException();
return value;
}
}
return mixin(MixinFileExistPipe);
}
You can achieve this with Mixin. Instead of exporting an injectable class, you'd export a factory function that would return such class.
export const FileExistPipe: (filePath: string) => PipeTransform = memoize(createFileExistPipe);
function createFileExistPipe(filePath: string) {
class MixinFileExistPipe implements PipeTransform {
constructor(private db: DatabaseService) {}
...
}
return mixin(MixinFileExistPipe);
}
memoize is just a simple function to cache the created mixin-pipe with the filePath. So for each filePath, you'd only have a single version of that pipe.
mixin is a helper function imported from nestjs/common which will wrap the MixinFileExistPipe class and make the DI container available (so DatabaseService can be injected).
Usage:
#Controller('transcode')
export class TranscodeController {
#Post()
async transcode (
// notice, there's no "new"
#Body(FileExistsPipe('input')) transcodeRequest: JobRequest) {
return await this.videoProducer.addJob(transcodeRequest);
}
a mixin guard injecting the MongoDB Connection
the console shows the connection being logged

Resources