nestjs save each request info without hitting database twice - node.js

I want to save each request (path, method, and userId) that comes to the server without having to hit the database twice, and also without messing up the main logic in services files with transactions.
Initially, I was trying to use an interceptor because it gets invoked after auth guards "which attaches the user to request" and before request handlers, but I faced two issues.
first, the fact that the interceptor will call the database to save a new record and then forward the request to handlers which will again hit DB again to handle the request. Secondly, It didn't work anyway because of dependancy injection problems.
code below is not working due to dependency errors as I mentioned, but It will give u an idea about what I need to acheive.
import { Injectable,
NestInterceptor,
Inject,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus } from '#nestjs/common';
import { Observable } from 'rxjs';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { HistoryEntity } from '../../modules/history/history.entity';
#Injectable()
export class HistoryInterceptor implements NestInterceptor {
constructor(
#Inject(getRepositoryToken(HistoryEntity))
private readonly historyRepo: Repository<HistoryEntity>
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { user, path, method } = request
if (!user) {
throw new HttpException('something terrible happened!', HttpStatus.BAD_GATEWAY);
}
const history = this.historyRepo.create({
path,
userId: user.id,
});
this.historyRepo.save(history);
return next.handle();
}
}
PS. from a performance point of view, It would also be great to not halt the request execution to save these info in db, in other words, Is it ok to NOT use await in this particular situation? because essecntially this is a system related operation and so, Node [rocess shouldn't wait for this step to process and return response to client.
Thanks in advance.

Related

MIssing TERMINUS_ERROR_LOGGER argument/dependency - NestJS HealthCheck controller

I'm trying to implement a simple HealtCheck controller in my NestJS api by following the offical example Healthchecks (Terminus): https://docs.nestjs.com/recipes/terminus#setting-up-a-healthcheck but I'm getting this error:
Nest can't resolve dependencies of the HealthCheckService (HealthCheckExecutor, ?). Please make sure that the argument TERMINUS_ERROR_LOGGER at index [1] is available in the HealthCheckModule context.
Since TERMINUS_ERROR_LOGGER seems like some kind of a enum, I'm not able to import it or add it as a provider inside my HealthModule and I haven't found any documentation/blog/post related to this.
Here's my HealthCHeck controller code:
import { Controller, Get } from '#nestjs/common';
import { HealthCheckService, HttpHealthIndicator, HealthCheck, TypeOrmHealthIndicator } from '#nestjs/terminus';
#Controller('health-check')
export class HealthCheckController {
constructor(
private readonly health: HealthCheckService,
private db: TypeOrmHealthIndicator,
) { }
#Get()
#HealthCheck()
readiness() {
return this.health.check([
async () => this.db.pingCheck('postgres', { timeout: 300 }),
]);
}
}
I just want to create a HealtCheck controller to check if the api is connected to my db and implement future health checks.

NestJS Is a good aproach using DTO classes in request object?

I'm learning NestJS and now I'm working in a simple authentication application, at this point I configured global pipes to validations and I'm using dto classes for example to validate #Body() fields. I don't know if I can use DTO to validate #Request fields sent from login endpoint.
import { Controller, Post, Request, UseGuards } from '#nestjs/common';
import { AuthService } from './auth.service';
import { AuthGuard } from '#nestjs/passport';
#Controller()
export class AuthController {
constructor(private authService: AuthService) {}
#UseGuards(AuthGuard('local'))
#Post('auth/login')
async login(#Request() req: reqDto /* I would like to use DTO for validations*/) {
return this.authService.login(req.user);
}
}
PS: I'm using DTO to validate SingUp body In UserController.
#Post('/signup')
createUser(#Body() createUserDto: CreateUserDto) {
return this.userService.createUser(createUserDto);
}
#Request() and #Headers() are two decorators that skip validation via pipes. You can make a custom request decorator that does get called via pipes, but annotating the request object would be a lot of work. What would be better is to create a decorator that gets just the data you need off the request, and then make a DTO for that object and validate that as necessary

Nestjs JwtStrategy access to context

I have a UseGuard in my WebSocket. Actually, this guard is a JwtAuthGuard that extends AuthGuard('jwt'). The JwtAuthGuard has a Strategy class called JwtStrategy. In this class, I have a validate method. In HTTP-based requests I return payload in this method. Then nestjs attach the payload to the req. Here is my Strategy class:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private authConfigService: AuthConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: authConfigService.JWT_SECRET,
});
}
async validate(payload: any) {
return payload;
}
}
I want to have access to context within validate method in order to attach the payload to the WebSocket's body (or anything that I can have access to the payload). Any idea?
You don't need to make any modifications to your strategy class. Instead, you should modify your JwtAuthGuard's getRequest method (if you don't have one then you should make one) that returns an object that has a headers proeprty that is an object with a authorization property that is a string. Something like
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ws = context.switchToWs().getClient(); // possibly `getData()` instead.
return {
headers: {
authorization: valueFromWs(ws),
}
}
}
}
You can also make this work across different context types by using an if/else or a switch statement and returning the correct object based on the contextType from context.getType(). Whatever is returned from this getRequest method is where passport will end up attaching the user property, so it may make sense to return the entire client with these extra values.

How to use the AWS X-Ray with Nest.js?

AWS X-Ray is support Express and Restify middleware but not support Nest.js.
Nest.js can't open segment and close segment to AWSXRay because it routes with typescript decoration.
How to use the AWS X-Ray with the Nest.js
Hmm, this is one of those situations that could be very interesting and difficult to work with. You can of course set up the openSegement call in the standard Nest middleware (looks just like Express middleware), but the closeSegment is a bit more difficult. I think (and I'm taking a long shot here as I have no real way to test this) you can create an interceptor and inject the HttpAdapter into it, check in incoming route before the request is made and see if it is a route you want to cover with X-Ray, if so mark a boolean and in the observable response (next.handle()) you can get the HttpAdapter instance and call the closeSegment function. In other words (and this will be really rough code):
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '#nestjs/common';
import { HttpAdapterHost } from '#nesjts/core';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import * as xRay from 'aws-xray-sdk-express';
#Injectable
export class XRayInterceptor implements NestInterceptor {
constructor(private readonly httpAdapter: HttpAdapterHost) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
let coverRoute = false;
const req = context.switchToHttp().getRequest();
if (req.path === 'yourPath') {
coverRoute = true;
}
return next.handle()
.pipe(
tap(() => (coverRoute && this.httpAdapter.use(xRay.xrayExpress.closeSegment()))
);
}
You might also probably be able to set up the openSegment in the interceptor as well, but again, all of this is untested and may not work as expected. I'm jsut trying to think of a way to maybe make this possible. Without access to error handling middleware your options would be to look at interceptors and filters, and it seems the closeSegement is to be an error handler like filters would be, so I'm not sure how you would catch errors otherwise. Maybe a filter is the best route, you may just have to play with ideas from here. Hopefully someone can give a bit more insight.

Problem with Authentication chapter and the 19-auth sample

I downloaded the 19-auth sample and add some console.log debug code to it, then found some problems.
The code in JwtAuthGuard is never executed: '2222222' was not printed to the console in the code below:
canActivate(context: ExecutionContext) {
console.log('22222222222');
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
When I changed the guard to JwtAuthGuard in the AuthController:
#get('data')
#UseGuards(JwtAuthGuard)
findAll(#Req() req) {
return req.user;
// this route is restricted by AuthGuard
// JWT strategy
}
the code in JwtAuthGuard was invoked, but in the canActivate function, I can't get the user info from request. and the canActivate function was called before the JwtStrategy?
Can someone explain how the code executing for the auth module, and how to get the user info in the JwtAuthGuard?
paste the latest code and console log here:
JwtStrategy
/**
* jwt passport 调用validate方法来判断是否授权用户进行接口调用
* #param payload
*/
async validate(payload: AuthPayload) {
Logger.log(`payload is ${JSON.stringify(payload)}`, 'JwtStrategy');
const user = await this.authService.validateUser(payload.id);
if (!user) {
throw new UnauthorizedException('不存在的用户信息');
}
return user;
}
JwtAuthGuard
canActivate(context: ExecutionContext) {
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
// this.accessPriv = this.reflector.get<string>('accessPriv', context.getHandler());
console.log('canActivate executed 111111111111111111');
return super.canActivate(context);
}
and the console log as below:
canActivate executed 111111111111111111
[Nest] 14080 - 2019-04-01 11:19 [JwtStrategy] payload is {"userName":"fanliang","id":"1","iat":1553772641,"exp":1554377441} +2286ms
it seems that the canActivate() function of JwtAuthGuard executed before the validate() function of JwtStrategy, but the user info was attached to the request after JwtStrategy validate().
what I want is to get the user info from request in the canActivate() of custom AuthGuard such like JwtAuthGuard
I have a somewhat solution that works for me.
Calling the super.canActivate before my own logic.
seems like the population of req.user triggered by it.
An example:
import { ExecutionContext, Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
import { Request } from "express";
#Injectable()
export class AuthGuardWithAllowSentry extends AuthGuard("jwt") {
public async canActivate(context: ExecutionContext) {
// that code will call the passport jwt
const origCanActivate = await super.canActivate(context);
// now we have request.user!
const http = context.switchToHttp();
const request = http.getRequest<Request>();
console.log(request.user)
if (request.header("X-Sentry-Token") === "blablabla") {
if (request.method === "GET" && request.path.endsWith(".map")) {
return true;
}
}
// some random logic
return request.user.roles.includes("admin")
}
}
it feels for me more like a workaround than a real thing.
I agree that the 19-auth sample is a little bit confusing to follow. This is mainly because it includes the JWTAuthGuard (as a reference for building custom guards) but it is never actually used. Instead, the original use of plain AuthGuard is already set up to provide JWT functionality. However, both guards leverage the JWTStrategy. If you want to understand this better, you could try updating your AuthController:
#Get('data')
#UseGuards(AuthGuard())
findAll() {
// this route is restricted by AuthGuard
// JWT strategy
return {
message: 'Successfully passed AuthGuard',
};
}
#Get('custom-jwt')
#UseGuards(new JwtAuthGuard())
// this route is restricted by JWTAuthGuard custom
// JWT strategy
customJwt() {
return {
message: 'Successfully passed JWTAuthGuard',
};
}
The important part is that in order to get past either guard, you must send the request with the Authorization header properly set to the token that's returned from the token endpoint.
For example: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIiwiaWF0IjoxNTU0MDUyNDczLCJleHAiOjE1NTQwNTYwNzN9.3Q8_FC-qFXk1F4KmMrHVSmmNGPAyHdt2myr5c18_E-U
I find it easiest to use a tool like Postman or Insomnia for constructing requests and setting Headers, etc but you could also use CURL. Once you've set the Authorization header with a valid token you'll be able to hit both of the guarded endpoints. If you put a console.log in the JWTStrategy you'll see that both guards end up using the validate method to retrieve the user correctly.

Resources