Albert
Albert

Reputation: 2664

Is it possible to pass a parameter to a nestjs guard?

I'm trying to come up with a somewhat reusable guard and it looks like I need to pass a string param to a guard. Is it achievable in nestjs?

Upvotes: 30

Views: 23650

Answers (5)

Chhay Sotheara
Chhay Sotheara

Reputation: 399

Update on the accepted answer by Jay

For NestJS 9 users: If you encounter a reference error, make sure to add @Injectable() to your Mixin class. This should resolve the issue.

export const RoleGuard = (role: string) => {
  @Injectable() // => Here
  class RoleGuardMixin implements CanActivate {
    canActivate(context: ExecutionContext) {
      // do something with context and role
      return true;
    }
  }

  const guard = mixin(RoleGuardMixin);
  return guard;
}

Upvotes: 1

Jeffrey Chu
Jeffrey Chu

Reputation: 241

It seems impossible to use mixin in Guard in NestJs. It will throw Exported variable 'RoleGuard' has or is using private name 'RoleGuardMixin'.

Actually, you may use setMetadata to pass in the parameters one by one, then get it from the Guard using reflector from from '@nestjs/core'.

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

        canActivate(context: ExecutionContext) {
          const roleName = this.reflector.get<string>('roleName', context.getHandler());
          return true;
        }
    }
    @Get()
    @UseGuards(RoleGuard)
    @SetMetadata('roleName', 'developer')
    async testRoleGuard() {
      return true;
    }

Or you may define a decorator to pass the parameter in.

export const RoleName = (roleName: string) => SetMetadata('roleName', roleName);
    @Get()
    @UseGuards(RoleGuard)
    @RoleName('developer')
    async testRoleGuard() {
      return true;
    }

UseGuards is imported from @nestjs/common.

Upvotes: 21

Nikola
Nikola

Reputation: 45

If anyone is struggling with testing the mixin approach @JayMcDoniel brilliantly pointed out, here's a sample setup from a guard I implemented using the mixin.

describe('PaymentGuard', () => {
  let guard: CanActivate;
  beforeEach(async () => {
    const guardProvider = PaymentGuard(PaymentGuardHandlerToken.EXPECT_PLATFORM_ENABLED);
    const module = await Test.createTestingModule({
      providers: [
        guardProvider,
        {
          provide: PaymentService,
          useValue: MOCK_PAYMENT_SERVICE,
        },
        {
          provide: EntityManager,
          useValue: MOCK_ENTITY_MANAGER,
        }
      ],
    }).compile();
    guard = module.get(guardProvider);
  });
});

And then you just call the guard.canActivate within your test.

Upvotes: 1

Nismi Mohamed
Nismi Mohamed

Reputation: 762

Usage:

@UseGuards(new AuthorizeGuard('read', 'users'))

Guard:

@Injectable()
export class AuthorizeGuard implements CanActivate {

  constructor(private action, private subject) {}

  canActivate(context: ExecutionContext): boolean {
    //you can use this.action and this.subject
  }
}

Upvotes: -2

Jay McDoniel
Jay McDoniel

Reputation: 70061

It sounds like you are looking to use a mixin, a function that returns a class. I'm not sure what kind of parameter you're passing, but the idea is

export const RoleGuard = (role: string) => {
  class RoleGuardMixin implements CanActivate {
    canActivate(context: ExecutionContext) {
      // do something with context and role
      return true;
    }
  }

  const guard = mixin(RoleGuardMixin);
  return guard;
}

mixin as a function is imported from @nestjs/common and is a wrapper function that applies the @Injectable() decorator to the class

Now to use the guard, you need to do something like @UseGuards(RoleGuard('admin'))

Upvotes: 61

Related Questions