3

I have a global guard that is registered in the application in common.module.ts which is a global module.

    const HeaderGuardGlobal = {
        provide: APP_GUARD,
        useClass: HeaderGuard
    };

    @Global()
    @Module({
        imports: [ LoggerModule ],
        providers: [ HeaderGuardGlobal ],
        exports: []
    })

header.guard.ts:

    async canActivate(context: ExecutionContext): Promise<boolean> {
        const request = context.switchToHttp().getRequest();
        const userName = request.headers[HEADERS.USER_NAME];

        if(!userName) {
            throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
        }

I have a control-scoped interceptor authenticate-header.interceptor.ts

    @Injectable()
    export class setAuthenticateHeaderInterceptor<T> implements NestInterceptor<T, Response<T>> {
        public constructor() {}

        intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
            const req = context.switchToHttp().getRequest();
            const res = context.switchToHttp().getResponse();
            
            return next
            .handle()
            .pipe(
                catchError(err => {
                    console.log('ERROR: ', err);
                        res.setHeader('sampleKey', 'sampleValue');
                        return throwError(err);
                    })
                )
        }
    }

user.controller.ts:

    @Controller('user')
    @UseInterceptors(setAuthenticateHeaderInterceptor)
    export class ClientController {

What I'm trying to achieve is that when header.guard.ts throws Forbidden exception, the authenticate-header.interceptor.ts would catch the exception and will propagate it to the global http-filter, but before doing that I want to add a header to the response object.

The issue I'm facing is when the exception is being thrown by the guard, the interceptor is unable to catch it. However, when the same error is thrown either from the route handler or from service, the interceptor is able to catch it.

I went through the request-lifecycle to understand the execution context, and found the below statement in interceptors section.

any errors thrown by pipes, controllers, or services can be read in the catchError operator of an interceptor.

The statement doesn't say anything about guards, so I'm assuming what I'm trying to achieve is not possible.

I'm unable to figure out why an error thrown inside a guard is not being caught by the interceptor. The above code snippets only include the parts which I thought were necessary for the question, if anybody feels like more info is needed. then I'll provide it.

yogibear
  • 14,487
  • 9
  • 32
  • 31
Kartik Chauhan
  • 2,779
  • 5
  • 28
  • 39

1 Answers1

7

The docs correctly state, as you mentioned,

any errors thrown by pipes, controllers, or services can be read in the catchError operator of an interceptor.

Guards, as noted below in the summary section are executed before interceptors are, and as such, their errors are not able to be caught in the interceptor's catchError method. Your best bet would be to make a filter that extends your GlobalFilter, add in your logic there, then call super.catch(exception) to call the rest of the logic.

Jay McDoniel
  • 57,339
  • 7
  • 135
  • 147
  • Thanks for the response Jay. But I still don't get why the interceptor is unable to catch the error. – Kartik Chauhan Apr 07 '20 at 19:44
  • 1
    The interceptor and it's context are created _after_ the guard has already run its `canActivate` method. I spent a lot of time trying to figure out when methods were called from and where before putting those docs together. It's just the way that the request lifecycle works. In the interceptor, Nest injects a `point-cut` to allow for moving from the interceptor to the controller and then to manage the post request logic, but all of this happens after middleware and guards have already been executed. In short, it's a limitation of the framework – Jay McDoniel Apr 07 '20 at 19:46
  • [Better wikipedia explains it than me](https://en.wikipedia.org/wiki/Pointcut), but TL;DR: It's a function that allows for the interjection of other code depending on certain circumstances. In this case, the circumstance is which class and which method are being activated here and now (i.e. which controller and which route handler). – Jay McDoniel Apr 07 '20 at 20:06