How to Extract a Value from microservice returned Observable in NestJS? - nestjs

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;
}

Related

How to use mongoose.isValidObjectId as a middleware in nestjs?

I have an issue with repetitive requests for checking an Order id, if it is valid ObjectId or not. I got this error:
CastError: Cast to ObjectId failed for value "629b9fbd620dbc419a52e8" (type string) at path "_id" for model "Order"
After a lot of Googling, I found two approaches to tackle the problem, however I'll have to duplicate these codes for each service, which isn't a good idea.
First approach:
if (!mongoose.Types.ObjectId.isValid(req.params.id)) {
throw new HttpException('Not a valid ObjectId!', HttpStatus.NOT_FOUND);
} else {
return id;
}
Second approach:
if (!mongoose.isValidObjectId(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
} else {
return id;
}
I used below codes for making and using a middleware, thus I could check ID whenever a service using an id parameter.
validateMongoID.ts
import {
BadRequestException,
Injectable,
NestMiddleware,
} from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
import mongoose from 'mongoose';
#Injectable()
export class IsValidObjectId implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// Validate Mongo ID
if (req.params.id) {
if (!mongoose.isValidObjectId(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
}
}
next();
}
}
orders.module.ts
export class OrdersModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(IsValidObjectId).forRoutes('/');
}
}
After trying as a middleware in the orders.modules.ts, I got the same error mentioned above. So, any idea to use it as a middleware?
I had to do this exact thing a couple of weeks ago.
Here is my solution. Works perfectly fine. Not a middleware, though.
id-param.decorator.ts
import { ArgumentMetadata, BadRequestException, Param, PipeTransform } from '#nestjs/common';
import { Types } from 'mongoose';
class ValidateMongoIdPipe implements PipeTransform<string> {
transform(value: string, metadata: ArgumentMetadata) {
if (!Types.ObjectId.isValid(value)) {
throw new BadRequestException(`${metadata.data} must be a valid MongoDB ObjectId`);
}
return value;
}
}
export const IdParam = (param = '_id'): ParameterDecorator => (
Param(param, new ValidateMongoIdPipe())
);
Usage
// If param is called _id then the argument is optional
#Get('/:_id')
getObjectById(#IdParam() _id: string) {
return this.objectsService.getById(_id);
}
#Get('/:object_id/some-relation/:nested_id')
getNestedObjectById(
#IdParam('object_id') objectId: string,
#IdParam('nested_id') nestedId: string,
) {
return this.objectsService.getNestedById(objectId, nestedId);
}
How it works
When using the #Param decorator you can give it transform pipes that will validate and mutate incoming value.
#IdParam decorator is just a #Param with the ValidateMongoIdPipe provided as a second argument.
I have found another way to solve it with the help of Lhon (tagged in comments).
create a file (I named it globalErrorHandler.ts) as follows:
import {
ArgumentsHost,
ExceptionFilter,
HttpException,
HttpStatus,
InternalServerErrorException,
} from '#nestjs/common';
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: InternalServerErrorException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
/**
* #description Exception json response
* #param message
*/
const responseMessage = (type, message) => {
response.status(status).json({
statusCode: status,
path: request.url,
errorType: type,
errorMessage: message,
});
};
// Throw an exceptions for either
// MongoError, ValidationError, TypeError, CastError and Error
if (exception.message) {
const newmsg: any = exception;
responseMessage(
'Error',
newmsg.response?.message ? newmsg.response.message : exception.message,
);
} else {
responseMessage(exception.name, exception.message);
}
}
}
add below line to main.ts
app.useGlobalFilters(new AllExceptionsFilter());
create another file (I named it validateMongoID.ts) as follows:
import {
BadRequestException,
Injectable,
NestMiddleware,
} from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
#Injectable()
export class IsValidObjectId implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
// Validate Mongo ID
if (req.params.id) {
if (!/^[a-fA-F0-9]{24}$/.test(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
}
}
next();
}
}
last step: import it as a middleware in app.module.ts
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(IsValidObjectId).forRoutes('*');
}
}

How to inject repository into nestjs-rate-limiter guard

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);
}
}

NestJS, GraphQL, class-validator - access request object inside validate method

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.");
}
}

How can I return a non empty response using Nest JS api and postman

I am using Nest Js to setup my server. I tried to fetch data from Postman to test if the API urls work. However I find that I get empty response from the server or undefined value from the postman request. The below code is the users.controller.ts
import {
Body,
Controller,
Delete,
Get,
Header,
Param,
Post,
Put,
} from '#nestjs/common';
import { User } from './users.entity';
import { UsersService } from './users.service';
#Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
/* #Get('/api/users')
index(): string {
return 'This will return user.';
} */
#Get()
index(): Promise<User[]> {
return this.usersService.findUsers();
}
#Post('create')
#Header('Content-Type', 'application/json')
async create(#Body() userData: User): Promise<any> {
console.log(userData.UserId, userData.UserType + ' Check if working');
return this.usersService.create(userData);
}
#Put(':userid/update')
async update(#Param('userid') userid, #Body() userData: User): Promise<any> {
userData.UserId = Number(userid);
console.log('Update #' + userData.UserId);
return this.usersService.update(userData);
}
#Delete(':userid/delete')
async delete(#Param('userid') userid): Promise<any> {
return this.usersService.delete(userid);
}
}
and this code is users.service.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './users.entity';
import { UpdateResult, DeleteResult } from 'typeorm';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User) private userRepository: Repository<User>,
) {}
async findUsers(): Promise<User[]> {
return await this.userRepository.find();
}
async create(user: User): Promise<User> {
console.log(user.UserType + 'User type');
return await this.userRepository.save(user);
}
async update(user: User): Promise<UpdateResult> {
return await this.userRepository.update(user.UserId, user);
}
async delete(userid): Promise<DeleteResult> {
return await this.userRepository.delete(userid);
}
}
If you can see the post request, there is a console.log() to check whether there is a response or not. Hence I am getting an undefined value instead. I am seeking for support to understand where I am going wrong. I am not able to traceback the error as well.
Welcome!
You are not logging what your service returns, instead you are logging what your service receives as the body of the request:
#Post('create')
#Header('Content-Type', 'application/json')
async create(#Body() userData: User): Promise<any> {
// ##################################
// here, you are logging userData, which is the object your
// service receives as input
// ##################################
console.log(userData.UserId, userData.UserType + ' Check if working');
return this.usersService.create(userData);
}
If that console.log line is logging undefined, it means you are not sending the right data in your HTTP Post request body in postman.
You should be sending a User json object as the request payload.
Had the same problem and solved it by annotating my return object.
export class CriarTarefaDto {
public nome: string;
public ativo?: boolean;
}
This was my object that always returned me empty when checking the return of the #Body annotation. I fixed it by including class-validator lib annotations in my DTO properties.
import { IsBoolean, IsString } from 'class-validator';
export class CriarTarefaDto {
#IsString()
public nome: string;
#IsBoolean()
public ativo?: boolean;
}
In your DTO object you need to use some annotation or extend the DTO with classes that register its properties.

Authentication & Roles with Guards/Decorators: How to pass user object?

With the help of Guards/Decorators I try to check a JWT first and then the roles a user has.
I have read the documentation regarding Authentication, Guards and Decorators and understand the principles behind them.
However, what I cannot do is to somehow make the authenticated user from JWT-Guard available to Roles-Guards.
In every example that I found, exactly this part that is interesting for me is skipped / left out...
Grateful for every tip!
This is my latest try:
jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { JwtPayload } from './jwt.model';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
passReqToCallback: true,
ignoreExpiration: false,
secretOrKey: '0000',
expiresIn: '3 days'
});
}
async validate(payload: JwtPayload) {
return {
id: payload.id,
email: payload.email,
username: payload.username
};
}
}
roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {
}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return false;
}
const request = context.switchToHttp().getRequest();
const user = request.user ??? // THIS is what is missing
return roles.some((role) => {
return role === user.role;
});
}
}
roles.decorator.ts
import { SetMetadata } from '#nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
users.controller.ts
#UseGuards(AuthGuard('jwt'))
#Roles('admin', 'member')
#Get('/')
async doSomething(#Req() req): Promise<User> {
return await this.usersService.doSomething(req.user.id);
}
Your decorator and guards look fine, but from the snippet of your users.controller.ts file it is not clear whether the roles guard is actually applied for the GET / route.
I do, however, have an NestJS app with a quite similar setup based on the guards documentation. The following code in users.controller.ts works as intended:
#UseGuards(JwtAuthGuard, RolesGuard)
#Controller('/users')
export class UserController {
constructor(private readonly userService: UserService) {}
#Get()
#Roles(UserRole.ADMIN)
public async index(): Promise<User[]> {
return this.userService.findAll();
}
// ...
}
Note how both the auth and roles guard are activated in the same scope and that JwtAuthGuard is added before RolesGuard. If I were to change the sequence of the guards then the RolesGuard would not be able to retrieve the user of the request.
Also, you might want to have a look at a similar question from some time ago which contains some details on the order of guards in different scopes.

Resources