How to send response from middleware created in a Nest fastify server? - nestjs

I've created a NestJs project with Fastify, and have created a middleware for it, but I can't figure out how to send a response to the client, similar to how we could do in express, any help would be appreciated, thanks!, here's my middleware code:
import {
Injectable,
NestMiddleware,
HttpException,
HttpStatus,
} from '#nestjs/common';
#Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: any, res: any, next: Function) {
console.log('Request...', res);
// throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
next();
}
}

Looks like Fastify abstraction uses NodeJS vanila http objects (The res injected here is http,ServerResponse )
// app.middleware.ts
import { Injectable, NestMiddleware } from '#nestjs/common';
import { ServerResponse, IncomingMessage } from 'http';
#Injectable()
export class AppMiddleware implements NestMiddleware {
use(req: IncomingMessage, res: ServerResponse, next: Function) {
res.writeHead(200, { 'content-type': 'application/json' })
res.write(JSON.stringify({ test: "test" }))
res.end()
}
}
// app.module.ts
import { Module, MiddlewareConsumer, RequestMethod } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppMiddleware } from './app.middleware';
#Module({
imports: [],
controllers: [AppController],
providers: [],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AppMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL }); // apply on all routes
}
}

An example with adaptation of Daniel code to kill execution after middleware validations (cache) using express
import { BadRequestException, Injectable, NestMiddleware } from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
#Injectable()
export class CacheMiddleware implements NestMiddleware {
constructor(
private cacheService: CacheService
){}
async use(req: Request, res: Response, next: NextFunction) {
const cache = await this.cacheService.getCache(req.url)
if(cache){
res.writeHead(200, { 'content-type': 'application/json' })
res.write(cache)
res.end()
return
}
next();
}
}

Related

using passport-jwt in nest with secretOrKeyProvider option keep responsing http 401?

I am a new coder with nestjs, I want to use passport-jwt , nestjs/passport and firebase to build my app's authetication part, below are my codes. But I just got http 401 response, how can i fix it ?
here is my strategy:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { getAuth } from 'firebase-admin/auth';
#Injectable()
export class FirebaseStrategy extends PassportStrategy(Strategy, 'firebase') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKeyProvider: async (request, rawJwtToken, done) => {
try {
const decodedToken = await getAuth().verifyIdToken(rawJwtToken);
done(null, decodedToken);
} catch (error) {
done(error);
}
},
});
}
async validate(payload: any) {
console.log('validate');
return payload;
}
}
here is my guard:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class FirebaseAuthGuard extends AuthGuard('firebase') {}
here is my auth.module.ts:
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '#nestjs/passport';
import { JwtModule } from '#nestjs/jwt';
import { FirebaseStrategy } from './firebase.stragety';
import { AuthController } from './auth.controller';
#Module({
controllers: [AuthController],
imports: [UsersModule, PassportModule, JwtModule.register({})],
providers: [AuthService, FirebaseStrategy],
exports: [AuthService],
})
export class AuthModule {}
and here is my controller:
import { Controller, Get, Request, UseGuards } from '#nestjs/common';
import { AuthService } from './auth.service';
import { FirebaseAuthGuard } from './firebase.guard';
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#UseGuards(FirebaseAuthGuard)
#Get('login')
async logIn(#Request() req) {
return 'login';
}
}
I just found my validate method in FirebaseStrategy not invoked, it should be invoked everytime when secretOrKeyProvider verified jwt in http header, isn't it ?
If you're getting a 401 with no call of the validate method of your JwtStrategy that means that for some reason passport is reading the JWT you send as invalid. To find the specific reason for it you can modify your FirebaseAuthGuard to have the following handleRequest method, which will log out extra details
handleRequest(err, user, info, context, status) {
console.log({ err, user, info, context, status });
return super.handleRequest(err, user, info, context, status);
}
As mentioned, this will print out what error or info passport is reading and throwing for, and will allow you to properly adjust your JWT/know to refresh it/change it because of invalid signature/whatever else.

How to use external (from another package) exception filter Nest.js?

I'm trying to create shared-module for microservices.
There are two packages:
#name/hub - ordinary HTTP-service
#name/lib - shared library
Library contains simple exception-filter module:
http.exception-filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '#nestjs/common';
import { Request, Response } from 'express';
#Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
});
}
}
exceptions-fitlers.module.ts
import { Module, Scope } from '#nestjs/common';
import { HttpExceptionFilter } from './http.exception-filter';
import { APP_FILTER } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_FILTER,
scope: Scope.REQUEST,
useClass: HttpExceptionFilter,
},
],
})
export class ExceptionsFiltersModule {}
Service contains controller that uses this filter:
app.module.ts
import { Module } from '#nestjs/common';
import { ExceptionsFiltersModule } from '#name/nodejs-lib/dist';
#Module({
imports: [ExceptionsFiltersModule, ...],
})
export class AppModule {}
controller.ts
#Controller('app')
#UseFilters(new HttpExceptionFilter())
export class AppController{
#Post('/check')
#HttpCode(200)
async check(#Body() dto: A): Promise<B> {
throw new BadRequestException('Invalid data');
}
}
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './modules/app.module';
import { ConfigService } from '#nestjs/config';
import { DocumentationBuilder, HttpExceptionFilter } from '#name/nodejs-lib/dist';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true });
const config = app.get(ConfigService);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(config.get<number>('HTTP_PORT'), () => {
logger.log(`HTTP Server: http://${config.get('HTTP_HOST')}:${config.get('HTTP_PORT')}`);
});
}
bootstrap().then();
Then I trying trigger this filter, I receive generic response:
{
"statusCode": 400,
"message": "Invalid data",
"error": "Bad Request"
}
If someone has opinion, please let me know. Thanks

Using failureRedirect option of passport-local with Nest.js

I need help with processing after authentication using Nest.js
here do I pass the failureRedirect option for passport-local when using Nest.js for authentication?
Without Nest.js
app.post('/login', passport.authenticate('local', {
//Passing options here.
successRedirect: '/',
failureRedirect: '/login'
}));
My code is. (with Nest.js)
local.strategy.ts
import { Injectable, UnauthorizedException } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import { Strategy } from "passport-local";
import { AuthService } from "./auth.service";
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
//I tried passing the option here. but failed.
})
}
async validate(username: string, password: string): Promise<string | null> {
const user = this.authService.validate(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
local.guard.ts
import { Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
#Injectable
export class LocalAuthGuard extends AuthGuard('local') {}
auth.controller.ts
import { Controller, Get, Post, Render, UseGuards } from "#nestjs/common";
import { LocalAuthGuard } from "./local.guard";
#Controller()
export class AuthController {
#Get("/login")
#Render("login")
getLogin() {}
//Redirect to '/login' when authentication failed.
#UseGuards(LocalAuthGuard)
#Post("/login")
postLogin() {}
}
auth.module.ts
import { Module } from "#nestjs/common";
import { PassportModule } from "#nestjs/passport";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { LocalStrategy } from "./local.strategy";
import { LocalAuthGuard } from "./local.guard";
#Module({
controllers: [AuthController],
imports: [PassportModule],
providers: [AuthService, LocalStrategy, LocalAuthGuard]
})
export class AuthModule {}
I tried adding code to AuthController#postLogin to redirect on login failure, but the code seems to run only on successful login.
I would like to redirect to the login page again in case of login failure with the failureRedirect option of passport-local.
I found a workaround since using the passport options sadly didn't work:
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
getAuthenticateOptions(context: ExecutionContext): IAuthModuleOptions {
return {
successReturnToOrRedirect: '/',
failureRedirect: '/login',
};
}
}
Instead I created a Nestjs Filter to catch an exception containing a redirect URL.
redirecting.exception.ts
export class RedirectingException {
constructor(public url: string) {}
}
redirecting-exception.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
import { Response } from 'express';
import { RedirectingException } from './redirecting.exception';
#Catch(RedirectingException)
export class RedirectingExceptionFilter implements ExceptionFilter {
catch(exception: RedirectingException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
response.redirect(exception.url);
}
}
In my validate method I'm throwing the RedirectingException with the correct error msg, e.g.
throw new RedirectingException('/login?error="User not found"');
And the controller handles the rest of the redirecting and passes the error to the view, so it can be displayed:
#Get('/login')
#Render('login.pug')
#Public()
async login(#Query() query) {
return { error: query.error };
}
#Post('/login')
#Public()
#UseGuards(LocalAuthGuard)
#Redirect('/')
async doLogin() {}
I'd rather use the passport functionality including the failureFlash, but I couldn't get it to work.

Nest.JS - Global authentication guard

I want to implement a global authentication guard in My NestJS application that will simply read certain headers and assign user values based on those headers, for every request that comes in.
I implemented this simple logic and registered my global guard in my main module, however for some reason all my requests fail with '401 Unauthorized'. I tried to place log messages inside internal.strategy.ts, however I don't see them getting called.
Any idea why the strategy is not called?
This is my main.ts:
import { NestFactory, Reflector } from '#nestjs/core';
import * as logging from './logging';
import { AppModule } from './app.module';
import config from './config';
import { LocalAuthGuard } from './auth/guards/local-auth.guard';
async function bootstrap(port: string | number) {
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new LocalAuthGuard())
await app.listen(port, '0.0.0.0');
logging.logger.info(`Listening on 0.0.0.0:${port}`);
}
bootstrap(config.port);
This is my auth.module.ts:
import { Module } from '#nestjs/common';
import { PassportModule } from '#nestjs/passport';
import { AuthService } from './auth.service';
import { InternalStrategy } from './stategies/internal.strategy';
#Module({
imports: [PassportModule],
providers: [AuthService, InternalStrategy ]
})
export class AuthModule {}
This is my auth.service.ts:
import { Injectable } from '#nestjs/common';
import { Role } from 'src/workspaces/interfaces/models';
#Injectable()
export class AuthService {
validateUser(headers: Headers): any {
const workspaceId = headers['workspace-id'];
const workspaceRole = Role[headers['workspace-role']];
return {
workspaceId: workspaceId,
workspaceRole: workspaceRole
}
}
}
This is my internal.strategy.ts:
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthService } from '../auth.service';
#Injectable()
export class InternalStrategy extends PassportStrategy(Strategy, 'internal') {
constructor(private authService: AuthService) {
super({ passReqToCallback: true });
}
async validate(req: Request): Promise<any> {
console.log('Validate internal strategy')
const user = await this.authService.validateUser(req.headers);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Here is my local-auth.guard.ts:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalAuthGuard extends AuthGuard('internal') {}

Method in http-proxy-middleware does not work, how to fix it?

From one service I send a request to an address for example http://gateway:3000/users, proxied using the http-proxy-middleware library to http://webamqplib:5557/. Works if I use the function
reverse-proxy-middlewareBuilder.ts
import { Request, Response, NextFunction } from 'express';
import { createProxyMiddleware, Filter, Options, RequestHandler } from 'http-proxy-middleware';
import { MiddlewareBuilder } from '#nestjs/core';
export function ReverseProxyMiddleware(req: Request, res: Response, next: NextFunction) {
const proxy = createProxyMiddleware(req.path, {
target: 'http://webamqplib:5557/',
changeOrigin: true,
})
proxy(req, res, next)
}
все хорошо отрабатывает, в консоле этого сервиса [HPM] Proxy created: /users -> webamqplib:5557
Module -> users.module.ts
import { Request, Response, NextFunction } from 'express';
import { Injectable, NestMiddleware, Scope } from '#nestjs/common';
import { createProxyMiddleware, Filter, Options, RequestHandler } from 'http-proxy-middleware';
import configs from './config/config.json';
#Injectable()
export class ReverseProxyMiddleware implements NestMiddleware {
private proxy(path: Filter | Options, option?: Options): RequestHandler {
return createProxyMiddleware(path, option)
}
use(req: Request, res: Response, next: () => void) {
this.proxy(
req.path,
{
target: configs.users.target,
changeOrigin: true
}
)
next()
}
Using a class reverse-proxy-middlewareBuilder.ts
import { Request, Response, NextFunction } from 'express';
import { Injectable, NestMiddleware } from '#nestjs/common';
import { createProxyMiddleware, Filter, Options, RequestHandler } from 'http-proxy-middleware';
import configs from './config/config.json';
#Injectable()
export class ReverseProxyMiddleware implements NestMiddleware {
private proxy(path: Filter | Options, option?: Options): RequestHandler {
return createProxyMiddleware(path, option)
}
use(req: Request, res: Response, next: () => void) {
this.proxy(
req.path,
{
target: configs.users.target,
changeOrigin: true
}
)
next()
}
}
When requesting http://gateway:3000/users, the console still displays [HPM] Proxy created: /users -> webamqplib:5557, as with the function, but the redirect does not occur to the address as in the first example
translated with the help of google translator :)
In your code, you create a middleware for each request, instead of connecting it once, after which it will catch the requests.
In your case, you have to do this in main.ts file:
// main.ts
import { createProxyMiddleware } from 'http-proxy-middleware';
...
app.use("*", createProxyMiddleware({
target: 'http://webamqplib:5557',
changeOrigin: true,
}));
That's how it works for me.

Resources