I am new to NestJS and have been looking at a couple of solutions but haven't found a way.
My API returns list of users, the users are either read from the cache if present or from the DB and based on this I need to set the headers.
I want the interceptor to handle this part.
My Controller
#UseInterceptors(CacheOrServiceHeaderInterceptor)
#Get('users')
public async users(
#Query('id') clinicianId: number,
#Query('name') specialtyName: string,
){
const setFlags: MetaFlags = {setCacheHeader: false, setServiceHeader: false};
const data = await this.service.getUsers(id, name, setFlags);
return data;
}
My service class
export interface MetaFlags {
setServiceHeader: boolean
setCacheHeader?: boolean,
}
:
:
public async getUsers(....){
// code that handles whether cache is used or a service is used to get users
and based on it, the metaFlags are set. setCacheHeader = true if read from cache or setServiceHeader = true if read from DB.
My interceptor looks like this
#Injectable()
export class CacheOrServiceHeaderInterceptor implements NestInterceptor {
public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(
map((data) => {
const req = context.switchToHttp().getRequest();
// this is static but I want a condition here if
if(MetaFlags.setCacheHeader)
// set xyz header
else set abc header
req.res.set('x-api-key', 'pretty secure');
return data;
}
)
);}}
Is there a way to pass these flags to the interceptor or make the interceptor read these flag values ?
I would suggest returning what header should be set as a part of the service of controller's return. If you end up using a global state, you could get multiple requests to the endpoint overwriting the global state before you read it in the interceptor and send back the wrong header. Something like
{
data: regularDataHere,
header: 'cache' || 'database'
}
Now in the map of your interceptor, you can read the data object and pull off the data property to return only it and not expose this header field to the client, and read the header field so you know which header to set on the response. Something like this:
#Injectable()
export class CacheOrServiceHeaderInterceptor implements NestInterceptor {
public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handler().pipe(
map((data: { data: DataType, header: 'cache' | 'database' }) => {
const res = context.switchToHttp().getResponse<ResponseType>();
if (data.header === 'database') {
res.setHeader('x-api-key', 'pretty secure');
} else {
res.setHeader(xyz, value);
}
return data.data;
})
);
}
}
Related
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
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
}
I'm doing a server-side application with NestJS and TypeScript in combination with the implementation of Passport JWT.
A little bit of context first:
My JwtStrategy (no issues here):
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private userService: UserService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'hi',
});
}
async validate(payload: IJwtClaims): Promise<UserEntity> {
const { sub: id } = payload;
// Find the user's database record by its "id" and return it.
const user = await this.userService.findById(id);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
According to the documentation about the validate() method:
Passport will build a user object based on the return value of our
validate() method, and attach it as a property on the Request object.
Thanks to this behavior, I can access the user object in my handler like this:
#Get('hi')
example(#Req() request: Request) {
const userId = (request.user as UserEntity).id;
}
Did you notice that I have used a Type Assertion (tells the compiler to consider the user object as UserEntity) ? Without it, I won't have auto-completion about my entity's properties.
As a quick solution, I have created a class that extends the Request interface and include my own property of type UserEntity.
import { Request } from 'express';
import { UserEntity } from 'entities/user.entity';
export class WithUserEntityRequestDto extends Request {
user: UserEntity;
}
Now, my handler will be:
#Get('hi')
example(#Req() request: WithUserEntityRequestDto) {
const userId = request.user.id; // Nicer
}
The real issue now:
I have (and will have more) a handler that will receive a payload, let's call it for this example PasswordResetRequestDto.
export class PasswordResetRequestDto {
currentPassword: string;
newPassword: string;
}
The handler will be:
#Get('password-reset')
resetPassword(#Body() request: PasswordResetRequestDto) {
}
Now, I don't have access to the user's object. I would like to access it to know who is the user that is making this request.
What I have tried:
Use TypeScript Generics and add a new property to my previous WithUserEntityRequestDto class like this:
export class WithUserEntityRequestDto<T> extends Request {
user: UserEntity;
newProp: T;
}
And the handler will be:
#Get('password-reset')
resetPassword(#Req() request: WithUserEntityRequestDto<PasswordResetRequestDto>) {
}
But now the PasswordResetRequestDto will be under newProp, making it not a scalable solution. Any type that I pass as the generic will be under newProp. Also, I cannot extends T because a class cannot extends two classes. I don't see myself doing classes like this all the time.
What I expect to accomplish:
Pass a type to my WithUserEntityRequestDto class to include the passed type properties and also the user object by default. A way that I can do for example:
request: WithUserEntityRequestDto<AwesomeRequestDto>
request: WithUserEntityRequestDto<BankRequestDto>
And the value will be something like:
{
user: UserEntity, // As default, always present
// all the properties of the passed type (T),
// all the properties of the Request interface
}
My goal is to find an easy and scalable way to extends the Request interface and include any type/class on it, while having the user object (UserEntity) always present.
Thanks for the time and any help/advice/approach will be appreciated.
Nestjs provides an elegant solution for your problem, which is Custom decoration
it's common practice to attach properties to the request object. Then you manually extract them in each route handler,
What you have to do is create a user decorator:
//user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
then you can simply use it in your controller like this:
#Get('hi')
example(#Req() request: Request,#User() user: UserEntity) {
const userId = user.id;
}
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();
}
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