Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In nest.js, is it possible to get service instance inside a param decorator?

I want to achieve something like this using nest.js: (something very similar with Spring framework)

@Controller('/test')
class TestController {
  @Get()
  get(@Principal() principal: Principal) {

  }
}

After hours of reading documentation, I found that nest.js supports creating custom decorator. So I decided to implement my own @Principal decorator. The decorator is responsible for retrieving access token from http header and get principal of user from my own auth service using the token.

import { createParamDecorator } from '@nestjs/common';

export const Principal = createParamDecorator((data: string, req) => {
  const bearerToken = req.header.Authorization;
  // parse.. and call my authService..
  // how to call my authService here?
  return null;
});

But the problem is that I have no idea how to get my service instance inside a decorator handler. Is it possible? And how? Thank you in advance

like image 951
jesuisgenial Avatar asked Apr 07 '19 15:04

jesuisgenial


People also ask

How does decorators work in NestJS?

A decorator is an expression that returns a function. It can take a target, name and property descriptor as arguments. We apply a decorator with an @ character and place it at the top of what we are trying to decorate. We can define decorators for class, method or a property. NestJS provides a set of param decorators.

What is @body in NestJS?

To instruct Nestjs that we need the body values from the request, we should use the @Body() decorator function from the @nestjs/common module before the body parameter. Doing this will bind the body values from the request to the body parameter in the createPost() method.

Is NestJS for frontend?

Controllers in Nest. js are meant to receive incoming HTTP requests from an application frontend and return an appropriate response.

Is NestJS open source?

NestJS is an open-source, extensible, versatile, progressive Node. Js framework for creating compelling and demanding backend systems. It's currently the fastest-growing Node. Js framework in TypeScript.


2 Answers

It is not possible to inject a service into your custom decorator.

Instead, you can create an AuthGuard that has access to your service. The guard can then add a property to the request object, which you can then access with your custom decorator:

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const bearerToken = request.header.Authorization;
    const user = await this.authService.authenticate(bearerToken);
    request.principal = user;
    // If you want to allow the request even if auth fails, always return true
    return !!user;
  }
}
import { createParamDecorator } from '@nestjs/common';

export const Principal = createParamDecorator((data: string, req) => {
  return req.principal;
});

and then in your controller:

@Get()
@UseGuards(AuthGuard)
get(@Principal() principal: Principal) {
  // ...
}

Note that nest offers some standard modules for authentication, see the docs.

like image 182
Kim Kern Avatar answered Oct 08 '22 10:10

Kim Kern


for NestJS v7

Create custom pipe

// parse-token.pipe.ts
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class ParseTokenPipe implements PipeTransform {
    // inject any dependency
    constructor(private authService: AuthService) {}
    
    async transform(value: any, metadata: ArgumentMetadata) {
        console.log('additional options', metadata.data);
        return this.authService.parse(value);
    }
}

Use this pipe with property decorator

// decorators.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { ParseTokenPipe} from './parse-token.pipe';

export const GetToken = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
  return ctx.switchToHttp().getRequest().header.Authorization;
});

export const Principal = (additionalOptions?: any) => GetToken(additionalOptions, ParseTokenPipe);

Use this decorator with or without additional options

@Controller('/test')
class TestController {
  @Get()
  get(@Principal({hello: "world"}) principal) {}
}

like image 35
Syao May Avatar answered Oct 08 '22 10:10

Syao May