'Guard factory in nestJS application

I'm trying to create a guard factory in order to create a guard based on given params and ran into an issue.

I built the following factory

@Injectable()
export class GateKeeperFactory {
  public static guards = []

  static forRoute(allow: boolean) {
    @Injectable()
    class GateKeeperCustomGuard implements CanActivate {
      canActivate(context: ExecutionContext): boolean {
        return allow
      }
    }
    const result = GateKeeperCustomGuard
    GateKeeperFactory.guards.push(result)
    return result
  }
}

and added the following to my app.module.ts

  providers: [GateKeeperFactory, ...GateKeeperFactory.guards],

To test this I created the following controllers

@Controller('/test1')
export class Test1Controller {
  @Get('1')
  @UseGuards(GateKeeperFactory.forRoute(false))
  test1() {
    return { in: true }
  }

  @Get('2')
  @UseGuards(GateKeeperFactory.forRoute(true))
  test2() {
    return { in: true }
  }
}

@UseGuards(GateKeeperFactory.forRoute(false))
@Controller('/test2')
export class Test2Controller {
  @Get('1')
  test1() {
    return { in: true }
  }
}

The issue is that all 3 routes are either blocked or all 3 are unblocked I can assume that the allow param is shared between the guards for some reason I'm missing (I verified that 3 different guards are created)


Note 1: according to the guide I know they offer a different approach of passing parameters to guards by using SetMetadata which at this point I want to avoid


Note 2: Trying to add a constructor to the guards and using them as instances led to the error: [ExceptionHandler] metatype is not a constructor when loading the server



Solution 1:[1]

It sounds like what you're really looing for is what's called a mixin, a function that returns a class with a closure that allows the class to use the mixin's parameters. This is how Nest creates it's Passport AuthGuard(), btw. To take from your example above and tweak it a bit, you'd do something like this:

import { CanActivate, ExecutionContext, mixin } from '@nestjs/common';

export function GateKeeper(allow: boolean) {
  
  class GateKeeperCustomGuard implements CanActivate {
    canActivate(context: ExecutionContext): boolean {
      return allow
    }
  }

  return mixin(GateKeeperCustomGuard) as CanActivate;
}

And now you should be able to use the guard like @UseGuards(GateKeeper(true))

Solution 2:[2]

Just an update to OP's 2nd note: "Trying to add a constructor to the guards and using them as instances led to the error: [ExceptionHandler] metatype is not a constructor when loading the server"

The exported mixin guard must be a function declared with the function keyword, and not an arrow function.

In order to fix this, replace

export const BlaBlaGuard = (params: any) => { /* Guard class goes here */ };

with

export function BlaBlaGuard(params: any) { /* Guard class goes here */ };

in your code, and it should fix the issue.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jay McDoniel
Solution 2 Okie