Exception Interceptor should modify res - node.js

I implemented an exception interceptor to intercept my HttpExceptions and return a http response depending on which HttpException is thrown by my application.
In my case I am throwing a 403 http exception in another interceptor which used by a route and declare the exception interceptor as a global one.
My interceptor looks like:
#Injectable()
export class ExceptionInterceptor implements NestInterceptor<Response> {
constructor(
private readonly logger: LoggerService
) {
this.logger.setContext(this.constructor.name);
}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const url = getOriginalUrl(request);
return next.handle().pipe(
catchError(
exception => {
switch (exception.status) {
case HttpStatus.FORBIDDEN: {
const forbiddenResBody = getForbiddenResBody(exception.response, url);
response.status(HttpStatus.FORBIDDEN);
response.header('Content-type', 'application/problem+json');
this.logger.error(
'Unauthorized',
exception
)
return of(forbiddenResBody)
}
default: {
const invalidServerResBody = getInternalServerResBody(url);
console.log('it is default')
this.logger.error(
'Unexpected exception',
exception
)
return of(invalidServerResBody);
}
}
})
);
}
}
However, the response is not modified is there anything that I am missing ?
Any help would be really appreciated !

Related

In nestjs, how can we change default error messages from typeORM globally?

I have this code to change the default message from typeorm when a value in a unique column already exists. It just creates a custom message when we get an error 23505.
if (error.code === '23505') {
// message = This COLUMN VALUE already exists.
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
throw new BadRequestException(message);
}
throw new InternalServerErrorException();
I will have to use it in other services, so I would like to abstract that code.
I think I could just create a helper and then I import and call it wherever I need it. But I don’t know if there is a better solution to use it globally with a filter or an interceptor, so I don’t have to even import and call it in different services.
Is this possible? how can that be done?
If it is not possible, what do you think the best solution would be?
Here all the service code:
#Injectable()
export class MerchantsService {
constructor(
#InjectRepository(Merchant)
private merchantRepository: Repository<Merchant>,
) {}
public async create(createMerchantDto: CreateMerchantDto) {
try {
const user = this.merchantRepository.create({
...createMerchantDto,
documentType: DocumentType.NIT,
isActive: false,
});
await this.merchantRepository.save(user);
const { password, ...merchantData } = createMerchantDto;
return {
...merchantData,
};
} catch (error) {
if (error.code === '23505') {
// message = This COLUMN VALUE already exists.
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
throw new BadRequestException(message);
}
throw new InternalServerErrorException();
}
}
public async findOneByEmail(email: string): Promise<Merchant | null> {
return this.merchantRepository.findOneBy({ email });
}
}
I created an exception filter for typeORM errors.
This was the result:
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpStatus,
InternalServerErrorException,
} from '#nestjs/common';
import { Response } from 'express';
import { QueryFailedError, TypeORMError } from 'typeorm';
type ExceptionResponse = {
statusCode: number;
message: string;
};
#Catch(TypeORMError, QueryFailedError)
export class TypeORMExceptionFilter implements ExceptionFilter {
private defaultExceptionResponse: ExceptionResponse =
new InternalServerErrorException().getResponse() as ExceptionResponse;
private exceptionResponse: ExceptionResponse = this.defaultExceptionResponse;
catch(exception: TypeORMError | QueryFailedError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
exception instanceof QueryFailedError &&
this.setQueryFailedErrorResponse(exception);
response
.status(this.exceptionResponse.statusCode)
.json(this.exceptionResponse);
}
private setQueryFailedErrorResponse(exception: QueryFailedError): void {
const error = exception.driverError;
if (error.code === '23505') {
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
this.exceptionResponse = {
statusCode: HttpStatus.BAD_REQUEST,
message,
};
}
// Other error codes can be handled here
}
// Add more methods here to set a different response for any other typeORM error, if needed.
// All typeORM erros: https://github.com/typeorm/typeorm/tree/master/src/error
}
I set it globally:
import { TypeORMExceptionFilter } from './common';
async function bootstrap() {
//...Other code
app.useGlobalFilters(new TypeORMExceptionFilter());
//...Other code
await app.listen(3000);
}
bootstrap();
And now I don't have to add any code when doing changes in the database:
#Injectable()
export class MerchantsService {
constructor(
#InjectRepository(Merchant)
private merchantRepository: Repository<Merchant>,
) {}
public async create(createMerchantDto: CreateMerchantDto) {
const user = this.merchantRepository.create({
...createMerchantDto,
documentType: DocumentType.NIT,
isActive: false,
});
await this.merchantRepository.save(user);
const { password, ...merchantData } = createMerchantDto;
return {
...merchantData,
};
}
}
Notice that now I don't use try catch because nest is handling the exceptions. When the repository save() method returns an error (actually it is a rejected promise), it is caught in the filter.

Can handle multiple errors using Nestjs' Exception Filter?

Spring boot was able to handle various errors using #RestControllerAdvice. The code created by spring boot is as follows.
#RestControllerAdvice
public class ControllerExceptionHandler {
// #valid에서 바인딩 에러가 발생
#ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException ==> " + e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.WRONG_INPUT_VALUE, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
// 사용자 생성시 이메일이 중복된 경우
#ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<ErrorResponse> handleDuplicateEmailException(DuplicateEmailException e) {
log.error("handleDuplicateEmailException ==> " + e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.DUPLICATE_EMAIL_VALUE);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
...
}
Currently, I was looking for features similar to #RestControllerAdvice while using NestJs to handle errors, and I found out about ExceptionFilter. I wrote the code, but I had a question after writing it. If we process the error like this, we can only process errors related to HttPexception, right?
I want to handle other errors globally besides HttPexception. However, unlike Spring Boot's #RestControllerAdvice, ExceptionFilter does not seem to be able to handle many errors in a single class. Am I using it wrong?
#Catch()
export class ExceptionHandler implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
message: exception.message,
path: request.url
});
}
}
You have to pass exception class to #Catch() decorator.
If you want to catch all http exceptions you can do
import { ExceptionFilter, Catch } from '#nestjs/common';
#Catch(HttpException)
export class ExceptionHandler implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
message: exception.message,
path: request.url
});
}
}
This will catch all errors thrown when executing any route.
You can also pass a few exception classes to catch only particular exceptions.
#Catch(BadRequestException, UnauthorizedException)
You can read more at https://docs.nestjs.com/exception-filters

Nestjs Interceptor how to catch http 401 error and resubmit original request

I need to write an http header interceptor to add Authorization header, if there is a 401 error, submit another request for a new token, then resubmit the original request with the new token.
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
return next.handle().pipe(
catchError(async error => {
if (error.response.status === 401) {
const originalRequest = error.config;
var authRes = await this.authenticationService.getAccessToken();
this.authenticationService.accessTokenSubject.next(authRes.access_token);
// I need to resubmit the original request with the new token from here
// but return next.handle(originalRequest) doesn't work
}
return throwError(error);
}),
);
}
But next.handle(originalRequest) doesn't work. How to resubmit the original request in the interceptor? Thank you very much in advance for your help.
I just encountered a similar problem, where I can catch the exception from exception filter but can't do so in interception layer.
So I looked up the manual and found it says:
Any exception thrown by a guard will be handled by the exceptions layer
(global exceptions filter and any exceptions filters that are applied to the current context).
So, if the exception is thrown from AuthGuard context(including the validate method in your AuthService), probably better to move the additional logic by extending the Authguard
like this:
export class CustomizedAuthGuard extends AuthGuard('strategy') {
handleRequest(err, user, info, context, status) {
if (err || !user) {
// your logic here
throw err || new UnauthorizedException();
}
return user;
}
}
or simply using customized exception filter.
It's been a while since the question but maybe it will help someone.
Ok, suppose that we need handle unauthorize exception out of route and guards, maybe service to service. So you can implement a interceptor like that and add some logic to get some data if needed, Ex: inject some Service in the interceptor.
So, throw an unauthorize exception and we are going to intercept it:
#Injectable()
export class UnauthorizedInterceptor implements NestInterceptor {
constructor(
private readonly authService: AuthService,
private readonly httpService: HttpService,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError((err) => {
const {
response: { status, config },
} = err;
// assuming we have a request body
const jsonData = JSON.parse(config.data);
if (status === HttpStatus.UNAUTHORIZED) {
// We can use some data in payload to find user data
// here for example the user email
if (jsonData?.email) {
return
from(this.authService.getByUserEmail(jsonData.email)).pipe(
switchMap((user: User) => {
if (user) {
// Ex: we can have stored token info in user entity.
// call function to refresh access token and update user data
// with new tokens
return from(this.authService.refreshToken(user)).pipe(
switchMap((updatedUser: User) => {
// now updatedUser have the new accessToken
const { accessToken } = updatedUser;
// set the new token to config (original request)
config.headers['Authorization'] = `Bearer ${accessToken}`;
// and use the underlying Axios instance created by #nestjs/axios
// to resubmit the original request
return of(this.httpService.axiosRef(config));
}),
);
}
}),
);
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
}),
);
}
}

how to modify Request and Response coming from PUT using interceptor in NestJs

I am using NestJs. I am using intercepter in my controller for PUT request.
I want to change the request body before the PUT request and I want to change response body that returns by PUT request. How to achieve that?
Using in PUT
#UseInterceptors(UpdateFlowInterceptor)
#Put('flows')
public updateFlow(#Body() flow: Flow): Observable<Flow> {
return this.apiFactory.getApiService().updateFlow(flow).pipe(catchError(error =>
of(new HttpException(error.message, 404))));
}
Interceptor
#Injectable()
export class UpdateFlowInterceptor implements NestInterceptor {
public intercept(_context: ExecutionContext, next: CallHandler): Observable<FlowUI> {
// how to change request also
return next.handle().pipe(
map(flow => {
flow.name = 'changeing response body';
return flow;
}),
);
}
}
I was able to do it by getting request from ExecutionContext
following is the code.
#Injectable()
export class UpdateFlowInterceptor implements NestInterceptor {
public intercept(
_context: ExecutionContext,
next: CallHandler
): Observable<FlowUI> {
// changing request
let request = _context.switchToHttp().getRequest();
if (request.body.name) {
request.body.name = 'modify request';
}
return next.handle().pipe(
map((flow) => {
flow.name = 'changeing response body';
return flow;
})
);
}
}

How to execute pipe(ValidateObjectId) before guard(ResourceOwnerGuard)?

Im playing around with nestjs and mongoose.
The code:
class BrevesController {
constructor(private readonly brevesService: BrevesService) { }
// Here is used BreveOwnerGuard(1)
#UseGuards(JwtAuthGuard, BreveOwnerGuard)
#Get(':breveId')
// Here is used ValidateObjectId(3)
async getById(#Param('breveId', ValidateObjectId) id: string) {
return await this.brevesService.getById(id)
}
}
class BreveOwnerGuard {
constructor(private readonly brevesService: BrevesService) { }
async canActivate(context: ExecutionContext) {
const req = context.switchToHttp().getRequest()
const {user, params} = req
const {breveId} = params
// This is executed before ValidateObjectId in getById
// route handler and unknown error is thrown but we
// have pipe for this.(2)
const breve = await this.brevesService.getById(breveId)
const breveCreatorId = breve.creatorId.toString()
const userId = user.id
return breveCreatorId === userId
}
}
So after request /breves/:breveId with invalid object id, the BreveOwnerGuard is executed before ValidateObjectId and unknown error is thrown.
Is there a way for this flow to validate the ObjectId before BreveOwnerGuard ?
Or what should I do in this case? What is expected ?
Guards are executed after each middleware, but before any interceptor or pipe.
Source: Guard Docs (emphasis by me)
Not much you can do other than change the ResourceOwnerGuard to a pipe or the ValidateObjectId into a Guard.

Resources