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('*');
}
}
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 created a interface which is extending the request (of express)
I m adding property called all() in it
which contains the body , query and params in it
import { Request as BaseRequest } from 'express';
export interface Request extends BaseRequest {
all(): Record<string, any>;
}
this is the interface
which is extending the express request
and i m adding this all() property using the guard
this is the implementation of it
#Injectable()
export class RequestGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
this.requestHelper(context.switchToHttp().getRequest());
return true;
}
requestHelper(request: any): any {
const all = function (): Record<string, any> {
return {
...request.query,
...request.body,
...request.params,
};
};
request.all = all;
return request;
}
}
in the main.ts file i have used this guard
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common';
import { RequestGuard } from './core/guards/request.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
app.useGlobalGuards(new RequestGuard());
await app.listen(3000);
}
bootstrap();
and i have tried consoling the all() property in the guard and it's working
its mean request is flowing in it
when i try to get this all() property in my controller then it showing
Cannot read properties of undefined (reading 'all')
That's how i m calling it
import {
Controller,
Get,
Post,
Param,
Body,
Req,
Res,
UseGuards,
} from '#nestjs/common';
import { RequestGuard } from 'src/core/guards/request.guard';
import { Request } from 'src/core/http/Request';
import { Response } from 'src/core/http/Response';
#UseGuards(RequestGuard)
#Controller('customers')
export class CustomersController {
constructor(private customersService: CustomersService) {}
#Get('/order-data/:id')
async OrderData(#Param('id') id: string, req: Request, #Res() res: Response) {
console.log(req.all());
const data = await this.customersService.allOrdersData(parseInt(id));
return data;
}
}
I m calling the route localhost:3000/customers/order-data/1
console.log(req.all());
It should print {id:{'1'}}
But it's giving error
Cannot read properties of undefined (reading 'all')
You're missing the #Req() for your req property in the OrderData 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.");
}
}
I've followed the official Nest doc (https://docs.nestjs.com/security/authentication) step by step, but I can't get validate() method called when using #AuthGuard('local') or #AuthGuard(LocalAuthGuard) on login action.
If I don't use that guard decorator, all works as expected (but I need to use it to get my token added to request object).
auth.controller.ts
#UseGuards(AuthGuard('local')) // or AuthGuard(LocalAuthGuard)
#Post('login')
async login(
#Request() req
) {
const { access_token } = await this.authService.login(req.user);
return access_token;
}
}
local.strategy.ts
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<any> { // class is constructed but this method is never called
const user: UserDto = await this.authService.login({
email,
password,
});
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
auth.module.ts
#Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: "bidon",
signOptions: {
expiresIn: '3600',
},
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService, PassportModule, JwtModule],
controllers: [AuthController],
})
export class AuthModule {}
PS : I've already read all stack overflow related posts (for exemple : NestJS' Passport Local Strategy "validate" method never called) but they didn't help me.
I found that if we don't pass email or password, also the wrong value of both, the guard will response Unauthorized message. The problem is how to ensure that validation of the required field before run guard logic if it not defined, In other words, frontend not pass it to server. If we add #Body() data: loginDto in controller method, it won't validate the body params.
to solve it, I add some validation code in local.guard.ts file. Here is my code in my project:
import { HttpException, HttpStatus, Injectable, UnauthorizedException } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
handleRequest(err, user, info, context, status) {
const request = context.switchToHttp().getRequest();
const { mobile, password } = request.body;
if (err || !user) {
if (!mobile) {
throw new HttpException({ message: '手机号不能为空' }, HttpStatus.OK);
} else if (!password) {
throw new HttpException({ message: '密码不能为空' }, HttpStatus.OK);
} else {
throw err || new UnauthorizedException();
}
}
return user;
}
}
ValidationPipe doesn't validate your request. Because, Gurads are executed before any interceptor or pipe. But, guards are executed after middleware. So, we can create a validation middleware to solve this issue. Here is my solution. Hope it will help somebody.
login.dto.ts
import { ApiProperty } from '#nestjs/swagger';
import { IsEmail, IsNotEmpty } from 'class-validator';
export class LoginDto {
#ApiProperty({ required: true })
#IsNotEmpty()
#IsEmail()
username: string;
#ApiProperty({ required: true })
#IsNotEmpty()
password: string;
}
authValidationMiddleware.ts
import {
Injectable,
NestMiddleware,
BadRequestException,
} from '#nestjs/common';
import { Response, NextFunction } from 'express';
import { validateOrReject } from 'class-validator';
import { LoginDto } from '../dto/login.dto';
#Injectable()
export class AuthValidationMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
const body = req.body;
const login = new LoginDto();
const errors = [];
Object.keys(body).forEach((key) => {
login[key] = body[key];
});
try {
await validateOrReject(login);
} catch (errs) {
errs.forEach((err) => {
Object.values(err.constraints).forEach((constraint) =>
errors.push(constraint),
);
});
}
if (errors.length) {
throw new BadRequestException(errors);
}
next();
}
}
auth.module.ts
import { MiddlewareConsumer, RequestMethod } from '#nestjs/common';
import { AuthController } from './auth.controller';
import { AuthValidationMiddleware } from './middleware/authValidationMiddleware';
#Module({
imports: ['.. imports'],
controllers: [AuthController],
})
export class AuthModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthValidationMiddleware)
.forRoutes({ path: 'auth/login', method: RequestMethod.POST });
}
}
This local strategy expects your body to have username and password fields, on your code change email to username
When you use NestJs Guard then it executed before Pipe therefore ValidationPipe() doesn't validate your request.
https://docs.nestjs.com/guards
Guards are executed after all middleware, but before any interceptor or pipe.
My use case requires only one parameter.
import { Injectable, UnauthorizedException, BadRequestException } from '#nestjs/common'
import { PassportStrategy } from '#nestjs/passport'
import { Request } from 'express'
import { Strategy } from 'passport-custom'
import { AuthService } from '../auth.service'
#Injectable()
export class CustomStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super()
}
async validate(req: Request): Promise<any> {
// req.body.xxx can verify any parameter
if (!req.body.code) {
throw new BadRequestException('Missing code parameters!')
// Using the above, this is how the response would look:
// {
// "message": "Missing code parameters!",
// "error": "Bad Request",
// "statusCode": 400,
// }
}
const user = await this.authService.validateUser(req.body.code)
console.log('user', user)
if (!user) {
throw new UnauthorizedException()
}
return user
}
}
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.