I'm trying a create a shared Guard as an external library in order to be imported and used across services. I'm not doing anything special that what is described in some guides but with the particularity that the code will reside in a shared library. Everything is working but the Exception to return a 401 error.
My guard looks something like this:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class MainGuard extends AuthGuard('jwt') {}
Nothing else. If I use that in a service folder it works, but at the time that I move as in their own library, the response changes.
The way that I'm using in the service has nothing special:
import { MainGuard } from 'shared-guard-library';
import { Controller, Get, UseGuards } from '#nestjs/common';
import { SomeService } from './some.service';
#Controller()
export class SomeController {
constructor(private someService: SomeService) {}
#Get('/foo')
#UseGuards(MainGuard)
async getSomething(): Promise<any> {
return this.someService.getSomething();
}
}
The client receives an error 500:
http :3010/foo
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 52
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Dec 2021 04:11:42 GMT
ETag: W/"34-rlKccw1E+/fV8niQk4oFitDfPro"
Keep-Alive: timeout=5
Vary: Origin
X-Powered-By: Express
{
"message": "Internal server error",
"statusCode": 500
}
And in the logs shows:
[Nest] 93664 - 12/08/2021, 10:11:42 PM ERROR [ExceptionsHandler] Unauthorized
UnauthorizedException: Unauthorized
at MainGuard.handleRequest (/sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:68:30)
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:49:128
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:86:24
at allFailed (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:101:18)
at attempt (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:174:28)
at Object.strategy.fail (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:296:9)
at Object.JwtStrategy.authenticate (/sharedGuardLibrary/node_modules/passport-jwt/lib/strategy.js:96:21)
at attempt (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:360:16)
at authenticate (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:361:7)
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:91:3
The logs are telling me that the correct exception was thrown, but is ignored at some point and I don't know the reason. Again: the same code in the same project works.
I took a look at the original class and I don't see any particular way to treat the exception
Any clue or guide it will appreciate.
So, this happens to be a "feature" of Typescript and how JavaScript object equality works in general. So in Nest's BaseExceptionFilter there's a check that exception instanceof HttpException, and normally, UnauthorizedException would be an instance of this, but because this is a library there's a few things that need to be considered.
All of the NestJS dependencies you're using have to be peerDependencies. This makes sure that when the library is installed, there's only one resulting package for the #nestjs/* package.
during local development, you'll need to take care to ensure that you're not resolving multiple instances of the same package (even if it's the exact same version, to JavaScript { hello: 'world' } === { hello: 'world' } // false). To take care of this, things like npm/yarn/pnpm link should not be used, but instead you should copy the dist and the package.json to the main application's node_modules/<package_name> directory.
a. The other option is using a monorepo tool like Nest's monorepo approach or Nx which have single package version approaches, and use the paths of the libraries rather than internal links.
If you follow this, when your production application installs the npm library, everything will work without an issue. It's an annoyance for sure, but it's a side effect of how JavaScript works
I had a similar problem with a custom auth package inside a monorepo.
My auth-library exposed AuthModule, JwtAuthGuard, and some utility functions. All needed packages were installed under my library so any other projects that were using it had not installed other versions of dependencies. Unfortunately using a custom guard caused Internal Server Error.
I've solved this issue by adding a global custom exception filter. It looks for a workaround but at least solves this issue.
This filter is exported from auth-library, so UnauthorizedException indicates on the same object as AuthGuard.
import { ArgumentsHost, Catch, ExceptionFilter, UnauthorizedException } from '#nestjs/common';
import { Response } from 'express'
#Catch(UnauthorizedException)
export class UnauthorizedExceptionFilter implements ExceptionFilter {
public catch(exception: UnauthorizedException, host: ArgumentsHost): Response {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
return response.status(401).json({ statusCode: 401 });
}
}
See Extending Guards: https://docs.nestjs.com/security/authentication#extending-guards
In most cases, using a provided AuthGuard class is sufficient. However, there might be use-cases when you would like to simply extend the default error handling or authentication logic. For this, you can extend the built-in class and override methods within a sub-class.
Implement handleRequest(err, user, info) as follows:
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
Related
I want to serve spa app with nestjs and fastify, it works but when I refresh a 404 is triggered. So I wrote an exception to catch the 404 and send the file but I keep getting FST_ERR_REP_ALREADY_SENT error.
My code below
import { Catch, ExceptionFilter, ArgumentsHost, HttpException, NotFoundException } from '#nestjs/common';
import { FastifyReply } from 'fastify';
#Catch(NotFoundException)
export class NotFoundExceptionFilter implements ExceptionFilter {
catch(_exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse() as FastifyReply;
response.sendFile('index.html');
}
}
I want 404 to serve the index.html file
I have had this issue in the past and an update from Nest 8 to 9.x fixed the bug. #Cavdy you may want to update to the latest Nest, atleast anything >=9 and you should be good to go.
It's probably catching something on notFoundHandler before it reaches your exception filters. I recommend setting it on notFoundHandler instead of using exception filters reference
I am in the process of rewriting an existing PHP-based API into NestJS (while learning NestJS at the same time).
Important bit is I cannot touch the frontend service, so I need to make sure that the new app can handle anything the frontend send to it, and respond with the same data structure the original api did.
And here is where I am hitting a wall. Everything submitted by frontend app is in the form of formdata. The only example give in NestJS documentation pertains to getting the uploaded file, but not any additional data that may come with it. Google search was not much of a help so far.
The best I found was this SO post Accept form-data in Nest.js but I am still unable to get the submitted data.
Submitted data:
> POST /public/login HTTP/1.1
> Host: localhost:3000
> Content-Type: multipart/form-data;
> Accept: */*
> Content-Length: 196
| Content-Disposition: form-data; name="login"
| sampleuser
| Content-Disposition: form-data; name="password"
| userpassword
content of my authentication.controller.ts:
import { Controller, Post, Body, Get, Param, Patch, Delete, Req, Header, UseInterceptors, } from "#nestjs/common";
#Controller()
export class AuthenticationController {
constructor() { }
#Post('public/login')
#UseInterceptors()
login(#Body() body) {
console.log(body);
}
}
the log result is simply {}
What am I missing/doing wrong here. Do I need to install some extra npm package to make this work? It surely cannot be that difficult to obtain such common data. Can someone point me to a right direction or a working example.
Thank you
When providing form data (which is mostly for file uploading, or, at least, that's the only way I apply this content type), I think NestJS gives it the same approach. So, I would suggest using the FileInterceptor.
Try this:
import { Controller, Post, Body, Get, Param, Patch, Delete, Req, Header, UseInterceptors, } from "#nestjs/common";
import { FileInterceptor } from '#nestjs/platform-express';
#Controller()
export class AuthenticationController {
constructor() { }
#Post('public/login')
// You're not actually providing a file, but the interceptor will expect "form data"
// (at least in a high level, that's how I think this interceptor works)
#UseInterceptors(FileInterceptor('file'))
login(#Body() body) {
console.log(body);
}
}
You can try this below solution it may help you.
Controller:
import { FileInterceptor } from '#nestjs/platform-express';
#Post('public/login')
#UseInterceptors(FileInterceptor('file'))
public async publicLogin(
#UploadedFile() file: Express.Multer.File,
#Body() body,
) {
// calling service function.
return await this.loginService.publicLogin(file, body);
}
Service:
public async publicLogin(file: Express.Multer.File, body: any): Promise<any> {
console.log(file); // we will get the file object here
console.log(body); // we will get the additiondata of formData.
}
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.
I am trying to access to Request object from within a Validation Pipe in nestjs
In order to verify uniqueness of certain fields, I require the ID/UUID parameters supplied with PUT/PATCH request (not available in the data structure itself)
any idea?
Currently, it is not possible to access the request object at all in a pipe. If you need the request you can use a guard or an interceptor.
If you are working on verifying uniqueness, that sounds like a part of business logic more than just about anything else, so I would put it in a service and handle the query to the database there. Just my two cents.
Edit 11/17/2020
After learning way more about how the framework works as a whole, technically it is possible to get the entire request object in a pipe. There are two ways to go about it.
Make the pipe #Injectable({ scope: Scope.REQUEST }) so that it is request scoped. This will end up creating a new pipe on each request, but hey, if that's your cup of tea then great.
Make a custom parameter decorator as custom decorators get completely passed into pipes as they are. Do note, that this could impact how the ValidationPipe is functioning if that is bound globally, at the class, or method level.
We can create a Pipe and access request object. We can move further and update the Body as well, if needed.
Following is an example scenario, where createdBy field should be added to the Body dynamically. Let's say user details are available from request:
// user.pipe.ts
import { Request } from 'express'
import { REQUEST } from '#nestjs/core'
import { Injectable, Inject, Scope, PipeTransform } from '#nestjs/common'
#Injectable({ scope: Scope.REQUEST })
export class UserPipe implements PipeTransform<any> {
constructor(#Inject(REQUEST) protected readonly request: Request) {}
transform(value) {
let email = this.request["user"].email;
value['createdBy'] = email;
return value
}
}
We can now use this in controller like this:
// someentity.controller.ts
#Post()
public async create(
#Body(SetUserPipe) dto: SomeEntityDto,
): Promise<SomeEntity> {
....
}
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.