I implemented roles for my angular/nestjs/postgresql application ('Admin' and 'User') and i activated the the role guard for my getuserlist() to be only showable to admin upon login in yet it keeps guarding the list for both admin and user in addition of server getting shut down with the following error:
TypeError: Cannot read properties of undefined (reading 'roles')
at C:\Users\naceu\Desktop\MyDeveloperHub\projet internat\projet-internat\projet-internat1\backend\src\users\models\roles.guard.ts:23:45
//I made the guard with the following files :
//role.enum.ts
//role.decorator.ts
/role.guard.ts
//and in the controller file containing the getuserlist()
//this is the role.guard.ts:
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { Observable } from 'rxjs';
import { User } from './entities/user.entity';
import Role from './role.enum';
import { ROLES_KEY } from './roles.decorator';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requireRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requireRoles) {
return true;
}
// const { user } = context.switchToHttp().getRequest();
const request = context.switchToHttp().getRequest();
const user = request.user;
// return matchRoles(roles, user.roles);
return requireRoles.some((role) => user.roles.includes(role));
}
}
Related
I am trying to implement NestJS Microservice to Validate my Bearer Token:
Whenever I get TCP response from Microservice which decides whether token is valid or not, it return Observable object, I am unable to extract the response from that Observable, please help me. Not getting proper way to extract the value from Observable in Nestjs, already tried lastValueFrom from RXJS
Here is the Controller to Make a call to Microservice(consumer/caller of microservice):
app.controller.ts
import { Body, Controller, Get, Post } from '#nestjs/common';
import { AppService } from './app.service';
import { PayloadDTO} from './payload.dto';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(): string {
return this.appService.getHello();
}
#Post()
createUserData(#Body() payload: PayloadDTO) {
const token = header.authorization.replace('Bearer ', '')
const isValidToken = this.appService.validateToken(token);
}
if(isValidToken) {
this.appService.CreateRecord(Payload : PayloadDTO)
} else {
//Throw New UnathorizedException
}
}
app.service.ts
import { Inject, Injectable } from '#nestjs/common';
import { ClientProxy } from '#nestjs/microservices';
import { PayloadDTO } from './payload.dto';
#Injectable()
export class AppService {
constructor(
#Inject('COMMUNICATION') private readonly commClient: ClientProxy
) {}
// service method to call and validate token
validateToken(token: String) {
//calling microservice by sending token
const checkToken: Observable = this.commClient.send({cmd:'validate_token'},token);
//Unable to extract value from Observable here, it is returning observable object
return extractedResponse;
}
// service method to create record
createRecord(payload: PayloadDTO) {
//code related to creating new Record
}
}
app.controller.ts - Microservice Project
import { Controller, Get } from '#nestjs/common';
import { MessagePattern } from '#nestjs/microservices';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#MessagePattern({ cmd: 'validate_token' })
validateToken() {
return this.appService.checkToken();
}
}
Finally I got the answer on my own:
In Microservice Consumer/Caller app.controller.ts I forgot to use await before calling this.appService.validateToke(token)
#Post()
async createUserData(#Body() payload: PayloadDTO) {
const token = header.authorization.replace('Bearer ', '')
const isValidToken = await this.appService.validateToken(token);
}
To convert/extract value from Observable:
In app.service.ts (consumer/caller of microservice) :
use async before validateToken
use await lastValueFrom(checkToken)
return resp
// service method to call and validate token
async validateToken(token: String) {
//calling microservice by sending token
const checkToken = this.commClient.send({cmd:'validate_token'},token);
const resp = await lastValueFrom(checkToken);
reutn resp;
}
I have the following global guard:
authorization.guard.ts
import { ExecutionContext, Injectable } from "#nestjs/common"
import { Reflector } from "#nestjs/core"
import { AuthGuard } from "#nestjs/passport"
#Injectable()
export class AuthorizationGuard extends AuthGuard(["azure-ad"]) {
public constructor(private readonly reflector: Reflector) {
super()
}
async canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
"isPublic",
context.getHandler(),
)
if (isPublic) {
return true
}
const req = context.switchToHttp().getRequest()
if(req.headers.isbypass){
//help needed
}
const result = (await super.canActivate(context)) as boolean
await super.logIn(req)
return result
}
}
and the following auth module and strategy:
import { Module } from "#nestjs/common";
import { PassportModule } from "#nestjs/passport";
import { UsersModule } from "modules/users/users.module";
import { AzureADStrategy } from "./azureAD.strategy";
import { SessionSerializer } from "./session.serializer";
#Module({
imports: [PassportModule, UsersModule],
providers: [AzureADStrategy, SessionSerializer],
})
export class AuthModule {}
import {
BearerStrategy,
IBearerStrategyOption,
ITokenPayload,
VerifyCallback,
} from "passport-azure-ad";
import {
Inject,
Injectable,
OnModuleInit,
UnauthorizedException,
} from "#nestjs/common";
import passport = require("passport");
import { UsersService } from "modules/users/users.service";
import env from "../../config";
const tenantId = env.TENANT_ID;
const clientID = env.CLIENT_ID || "";
const azureCredentials: IBearerStrategyOption = {
//
};
#Injectable()
export class AzureADStrategy extends BearerStrategy implements OnModuleInit {
onModuleInit() {
passport.use("azure-ad", this);
}
constructor(
#Inject(UsersService) private usersService: UsersService
) {
super(
azureCredentials,
async (token: ITokenPayload, done: VerifyCallback) => {
if (Date.now() / 1000 > token.exp) {
return done(new UnauthorizedException("access token is expired"));
}
const tokenUsername = token?.preferred_username?.slice(0, 9);
const tokenAppId = !tokenUsername && token?.azp;
if (!tokenUsername && !tokenAppId) {
return done(new UnauthorizedException("Missing User"));
}
let user;
if (tokenUsername) {
try {
user = await this.usersService.getUser(
tokenUsername
);
if (!user) {
return done(
new UnauthorizedException("User is not a test user")
);
}
} catch (err) {
return done(err);
}
user.tz = tokenUsername;
} else {
user.appId = tokenAppId;
}
return done(null, user, token);
}
);
}
}
The guard is defined globally using:
const reflector = app.get(Reflector);
app.useGlobalGuards(
new AuthorizationGuard(reflector),
);
And the auth module is imported in app.module.ts:
#Module({
imports: [
AuthModule,
...
]
Now, for the question.
I would like to have a way to "backdoor" the global authorization by checking if req.headers.isbypass exists in the request's headers, and if it does use userService in authorizationGuard, so i can inject the user from the DB to req.user myself and continue the request.
How do I achieve that?
I would change the app.useGlobalGuards() to be a global guard provider, adding
{
provider: APP_GUARD,
useClass: AuthorizationGuard,
}
Into the providers of your AppModule so that Nest handles all of the DI for you. From there, it's just adding the UsersService to the constructor like you already have for the Reflector
#Injectable()
export class AuthorizationGuard extends AuthGuard(["azure-ad"]) {
public constructor(
private readonly reflector: Reflector,
private readonly usersService: UsersServce
) {
super()
}
async canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
"isPublic",
context.getHandler(),
)
if (isPublic) {
return true
}
const req = context.switchToHttp().getRequest()
if(req.headers.isbypass){
//help needed
}
const result = (await super.canActivate(context)) as boolean
await super.logIn(req)
return result
}
}
I've tried to inject a repository into the guard which extends from RateLimiterGuard nestjs-rate-limiter but I got an error when call super.canActivate(ctx). It said that this.reflector.get is not a function. Is there any mistake that I have?
Here is my code:
import { RateLimiterGuard, RateLimiterOptions } from 'nestjs-rate-limiter';
import type { Request } from 'express';
import config from 'config';
import { ExecutionContext, Injectable } from '#nestjs/common';
import { MockAccountRepository } from '../modules/mock/mock-accounts/accounts-mock.repository';
import { Reflector } from '#nestjs/core';
import { UserIdentifierType } from 'src/modules/users/user.types';
const ipHeader = config.get<string>('server.ipHeader');
#Injectable()
export class ForwardedIpAddressRateLimiterGuard extends RateLimiterGuard {
constructor(
reflector: Reflector,
options: RateLimiterOptions,
private readonly mockAccRepo: MockAccountRepository,
) {
super(options, reflector);
}
protected getIpFromRequest(request: Request): string {
return request.get(ipHeader) || request.ip;
}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const phoneNumber = req.body?.phoneNumber || req.params?.phoneNumber;
// If mock phone number, always allow
if (
await this.mockAccRepo.findOne({
identifier: phoneNumber,
identifierType: UserIdentifierType.PHONE_NUMBER,
})
) {
return true;
}
// Otherwise, apply rate limiting
return super.canActivate(ctx);
}
}
I have a custom class-validator rule:
import { Injectable } from "#nestjs/common";
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from "class-validator";
#ValidatorConstraint({ name: "Foo", async: true })
#Injectable()
export class FooRule implements ValidatorConstraintInterface {
async validate(value: unknown, args: ValidationArguments) {
// how to access request object from here?
}
defaultMessage(args: ValidationArguments) {
return "NOT OK.";
}
}
How do I access the request object inside the validate() method?
I ended up using a custom interceptor:
import { Injectable, NestInterceptor, CallHandler, ExecutionContext } from "#nestjs/common";
import { GqlExecutionContext } from "#nestjs/graphql";
import { ForbiddenError } from "apollo-server-core";
import { Observable } from "rxjs";
#Injectable()
export class FooInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// get gql execution context from http one
const gqlCtx = GqlExecutionContext.create(context);
// holds data passed in a gql input
const args: unknown[] = gqlCtx.getArgs();
// req object (can be used to obtain jwt payload)
const req = gqlCtx.getContext().req;
// validation logic here
const validationPassed = true;
if (validationPassed) {
// invoke the route handler method
return next.handle();
}
// will be caught by nest exceptions layer
throw new ForbiddenError("Not allowed.");
}
}
import { ROLES_KEY } from './role.decorator';
import { UserRole } from '../users/role.enum';
import { CanActivate, Injectable, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const requiredRole = this.reflector.getAllAndOverride<UserRole[]>(
ROLES_KEY,
[context.getClass(), context.getHandler()],
);
if (!requiredRole) {
return true
}
const req = context.switchToHttp().getRequest();
const user = req.user;
console.log('Before');
console.log(user)
return requiredRole.some((role) => {
if(role==="ADMIN"){
console.log("True")
}
return role === 'ADMIN'
});
}
}
The reason after return role==="ADMIN" then user can log but how can i get user before return role==="ADMIN execute ?
I can get user only after the role==="ADMIN" return only
Im using nest js working on RoleGuard