Good day !
I created a basic controller BaseController with basic endpoints...
import { Get, Post, Put, Delete, HttpStatus, Request, Response } from '#nestjs/common';
import { MessageCodeError } from './../index';
export class BaseController {
public dataService: any;
constructor(public DataService: any) {
this.dataService = DataService;
}
#Get('/')
public async findAndCountAll(#Request() req, #Response() res) {
const params = req.query;
const offset = Number(params.skip) || 0;
const limit = Number(params.limit) || 10;
delete params.offset;
delete params.limit;
const records = await this.dataService.findAndCountAll({ where: params, offset, limit });
return res.status(HttpStatus.OK).send({ total: records.count, data: records.rows });
}
}
I try to use it in other controllers. For example, in UserController...
import { Controller, Get, Post, Put, Delete, HttpStatus, Request, Response } from '#nestjs/common';
import { MessageCodeError } from '../common/index';
import { UserService } from './user.service';
import { BaseController } from './../common/shared/base.controller';
#Controller('users')
export class UserController extends BaseController {
constructor(public userService: UserService) {
super(userService);
}
}
I thought since I was in class UserController doing inheritance from class BaseController, then the functions (which act as endpoints) should be available in UserController. But it seems that I was wrong...
The database is OK, the connection is stable, the code is working (at least was before I decided to take out endpoints from the UserController to the BaseController). What could be the mistake ?
Annotate your BaseController with #Controller() decorator.
import { Get, Post, Put, Delete, HttpStatus, Request, Response } from '#nestjs/common';
import { MessageCodeError } from './../index';
#Controller()
export class BaseController {
public dataService: any;
constructor(public DataService: any) {
this.dataService = DataService;
}
#Get('/')
public async findAndCountAll(#Request() req, #Response() res) {
const params = req.query;
const offset = Number(params.skip) || 0;
const limit = Number(params.limit) || 10;
delete params.offset;
delete params.limit;
const records = await this.dataService.findAndCountAll({ where: params, offset, limit });
return res.status(HttpStatus.OK).send({ total: records.count, data: records.rows });
}
}
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 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('*');
}
}
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);
}
}
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.