How to make a dynamic roles guard, to work in both controllers and handlers

4.1k views Asked by At

I'm defining a roles guard like this:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { User } from './user.entity';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
  ) { }

  async matchRoles(roles: string[], userRole: User["roles"]) {
    let match = false;

    if (roles.indexOf(userRole) > -1) {
      match = true;
    }

    return match
  }

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const roles = this.reflector.get<string[]>('roles', context.getClass());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user: User = request.user;

    return this.matchRoles(roles, user.roles)
  }
}

in this roles example it works only in a controller level like this:

@Controller('games')
@hasRoles('user')
@UseGuards(AuthGuard(), JwtGuard, RolesGuard)
export class GamesController {
...

But i want it to work dynamically with both, at controller level, and handler level. so i can appy a @hasRoles('user') for every route in the controller, and @hasRoles('admin') for some routes in that controller.

So to do this i need to change the reflector method from getClass to getHandler dynamically.

1

There are 1 answers

0
Jay McDoniel On BEST ANSWER

Nest's Reflector has a built-in method to merge the metadata set on controllers and route handlers with getAllAndMerge which will merge the metadata from the class and the method. To use it you would do something like

const roles = this.reflector.getAllAndMerge(
  'roles',
  [
    context.getHandler(),
    context.getClass()
  ]
);

If you wanted to get just one set of metadata and have a fallback (say if you want only the handler metadata if it exists and if not get the class's metadata) you can use getAllAndOverride in a similar manner

const roles = this.reflector.getAllAndOverride(
  'roles',
  [
    context.getHandler(),
    context.getClass()
  ]
);

You can read more in-depth about it here.