Search code examples
typescriptdependency-injectionnestjs

Create an OR Guard on NestJS


I have a controller with a route that I want to protect with Guards. I have these three gards: IsAuthentifiedGuard, HasRoleGuard and IsSafeGuard and I want at least one of these two conditions to be valid:

  • IsAuthentifiedGuard and HasRoleGuard are OK
  • IsSafeGuard is OK

For example, if the user is authentified AND has an enough role, it should be OK but if not but the call is "safe" I also want it to be OK.

My OrGuard:

@Injectable()
export class OrGuard implements CanActivate {
  constructor(private readonly orGuards: CanActivate[][]) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const orGuardsPromises = await Promise.all(
      this.orGuards.map(async andGuards => {
        const andGuardPromises = await Promise.all(
          andGuards.map(async guard => {
            return guard.canActivate(context)
          }),
        )
        return andGuardPromises.every(canActivate => canActivate === true)
      }),
    )

    return orGuardsPromises.some(canActivate => canActivate === true)
  }
}

And my controller:

@Controller('my_route')
export class MyController {
  @Post('/')
  @UseGuards(new OrGuard([[new IsAuthentifiedGuard(), new HasRoleGuard()], [new IsSafeGuard()]]))
  async myRoute() {}
}

But I've a problem with the guards that require dependencies. For example if HasRoleGuard requires a ConfigService in constructor, this doesn't work.

An other way would be to use my or guard as following:

@UseGuards(OrGuard)
@SetMetadata('orGuards', [[IsAuthentifiedGuard, HasRoleGuard], [IsSafeGuard]])

But in my guard, I don't succeed to instanciate these guards from their class name with all the dependencies they need.

What can you suggest?


Solution

  • What you're looking to do is possible with @nest-lab/or-guard. You'll need to register your guards as providers (just adding them to the providers array is fine) and you'll want to create a custom provider for the AndGuard so that it can later be referenced by the OrGuard. Something like this should do for you:

    @Module({
      providers: [
        IsAuthentifiedGuard,
        HasRoleGuard,
        IsSafeGuard,
        {
          provide: 'AndGuard',
          useClass: AndGuard([IsAuthenticatedGuard, HasRoleGuard]),
        },
        ...
      ]
    })
    

    And then you can use @UseGuards(OrGuard(['AndGuard', IsSafeGuard])) and it should work just fine. You can view the tests for some more inspiration on how it works if you'd like