How to get request query parameters in interceptor - nestjs

I am developing a Nest.js project.
I have a interceptor which simply logs request parameters:
#Injectable()
export class MyRequestInterceptor implements NestInterceptor {
constructor(private logger: MyLogger) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<string> {
const http = context.switchToHttp();
const request = http.getRequest();
const params = request.params;
// output is {}, no parameters showing
this.logger.info(JSON.stringify(params));
return next.handle()
}
}
When I send request GET http://localhost:9001/data?foo=1,2, I expect to see log for foo=1,2 but I see empty object {}. So, where am I wrong? How to access query parameters of request in Nest.js interceptor?

Try request.query
#Injectable()
export class MyRequestInterceptor implements NestInterceptor {
constructor(private logger: MyLogger) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<string> {
const http = context.switchToHttp();
const request = http.getRequest();
const query = request.query;
this.logger.info(JSON.stringify(query));
return next.handle()
}
}

example log interceptor
#Injectable()
export class LogRequestInterceptor implements NestInterceptor {
constructor(
) { }
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
const http = context.switchToHttp();
const request = http.getRequest();
const id = v4();
const { params, query, body, headers, user } = request;
const { url, method } = request.raw;
console.log('___________________________________________________________________________');
console.log('url : ', url);
console.log('method : ', method);
console.log('params : ', params);
console.log('query : ', query);
console.log('body : ', body);
console.log('headers : ', headers);
console.log('id : ', id);
// const message = { id, type: 'request', method, url, params, query, body, headers, user: (user || {}) };
// this.logger.log(message);
return next.handle()
.pipe(tap(data => {
}),
catchError(err => {
const { message: error, status, stack } = err;
const errorMessage = {
id, type: 'error', method, url, params, query, body, headers,
user: (user || {})._id, data: { error, status, stack },
};
console.log('err', err);
return throwError(err);
}));
}
}

Related

Get param class type in NestJS cutom param decorator

I have a NestJS TS application that has an xml endpoint. I want to validate an xml body. Here's how I went with parsing xml to js object:
#Injectable()
export class XmlToJsInterceptor implements NestInterceptor {
constructor(private parser: CxmlParserService) {}
public intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const req: XmlRequest<unknown> = context.switchToHttp().getRequest();
if (req.rawBody) {
req.xml = this.parser.convertXMLToObject(req.rawBody) as unknown;
}
return next.handle();
}
}
export const XmlBody = createParamDecorator((data: unknown, ctx: ExecutionContext): unknown => {
const request: XmlRequest<unknown> = ctx.switchToHttp().getRequest();
return request.xml;
});
And I use it like this:
#UseInterceptors(XmlToJsInterceptor)
#Controller('/endpoint')
export class MyController {
#Post('/')
#HttpCode(HttpStatus.OK)
async handleEndpoint(
#XmlBody() body: MyClassValidator,
): Promise<void> {
Now I want to use class-validator to check if xml request has a proper structure. I thought to extend XmlBody nestjs param decorator to include validation and manually call class-validator like this:
export const XmlBody = createParamDecorator((data: unknown, ctx: ExecutionContext): unknown => {
const request: XmlRequest<unknown> = ctx.switchToHttp().getRequest();
const validatedConfig = plainToClass(HOWDO_I_GET_PARAM_CLASS, request.xml, {
enableImplicitConversion: true,
});
const errors = validateSync(validatedConfig, { skipMissingProperties: false });
if (errors.length > 0) {
throw new Error(errors.toString());
}
return request.xml;
});
But I don't know to get typescript class from annotated parameter.

How to pass jwt to prisma middleware in nestjs

I am using nestjs, graphql, & prisma. I am trying to figure out how to pass my jwt token for each database request to the prisma service iv created. Iv tried an object to the constructor but then wont compile saying I am missing a dependency injection for whatever I reference in the constructor paramter.
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleDestroy {
constructor() {
super();
//TODO how do I pass my jwt token to this for each request?
this.$use(async (params, next) => {
if (params.action === 'create') {
params.args.data['createdBy'] = 'jwt username goes here';
}
if (params.action === 'update') {
params.args.data['updatedBy'] = 'jwt username goes here';
}
const result = await next(params);
return result;
});
}
async onModuleDestroy() {
await this.$disconnect();
}
}
Are you using a nest middleware?
JWT is normally passed to a Controller, not a service.
Example:
#Injectable()
export class MyMiddleware implements NestMiddleware {
private backend: any // This is your backend
constructor() {
this.backend = null // initialize your backend
}
use(req: Request, res: Response, next: any) {
const token = <string>req.headers.authorization
if (token != null && token != '') {
this.backend
.auth()
.verifyIdToken(<string>token.replace('Bearer ', ''))
.then(async (decodedToken) => {
const user = {
email: decodedToken.email,
uid: decodedToken.uid,
tenantId: decodedToken.tenantId,
}
req['user'] = user
next()
})
.catch((error) => {
log.info('Token validation failed', error)
this.accessDenied(req.url, res)
})
} else {
log.info('No valid token provided', token)
return this.accessDenied(req.url, res)
}
}
private accessDenied(url: string, res: Response) {
res.status(403).json({
statusCode: 403,
timestamp: new Date().toISOString(),
path: url,
message: 'Access Denied',
})
}
}
So every time I get an API call with a valid token, the token is added to the user[] in the request.
In my Controller Class I can then go ahead and use the data:
#Post()
postHello(#Req() request: Request): string {
return 'Hello ' + request['user']?.tenantId + '!'
}
I just learned about an update in Nest.js which allows you to easily inject the header also in a Service. Maybe that is exactly what you need.
So in your service.ts:
import { Global, INestApplication, Inject, Injectable, OnModuleInit, Scope } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
import { REQUEST } from '#nestjs/core'
#Global()
#Injectable({ scope: Scope.REQUEST })
export class PrismaService extends PrismaClient implements OnModuleInit {
constructor(#Inject(REQUEST) private readonly request: any) {
super()
console.log('request:', request?.user)
}
async onModuleInit() {
// Multi Tenancy Middleware
this.$use(async (params, next) => {
// Check incoming query type
console.log('params:', params)
console.log('request:', this.request)
return next(params)
})
await this.$connect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
As you can see in the log output, you have access to the entire request object.

NestJs : How to implement node.js Post and Get logic in NestJs

I'm trying to implement node.js Spotify Authorization flow in NestJs.
But HttpService Post and Get functions doesn't work as in node.js.
Node.js working example:
var request = require('request'); // "Request" library
app.get('/callback', function(req, res) {
var authOptions = {
url: 'https://some-url.com/api/token',
form: {
code: code,
redirect_uri: redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (Buffer.from(client_id + ':' + client_secret).toString('base64'))
},
json: true
};
// I'm trying to implement this post in NestJS
request.post(authOptions, function(error, response, body) {
var options = {
url: 'https://api.spotify.com/v1/me',
headers: { 'Authorization': 'Bearer ' + access_token },
json: true
};
request.get(options, function(error, response, body) {
console.log(body);
});
}
I'm using HttpService Post method in NestJS
and that doesn't work:
constructor(private httpService: HttpService) {}
#Get('callback')
callback(#Request() req, #Res() res): any {
let code = req.query.code || null;
const url = 'https://some-url.com/api/token';
const form = {
code: code,
redirect_uri: this.redirect_uri,
grant_type: 'authorization_code'
}
const headers = {
'Authorization': 'Basic ' + (Buffer.from(this.client_id + ':' + this.client_secret))
}
// doesn't work
this.httpService.post( url, form, { headers: headers }).pipe(
map((response) => {
console.log(response);
}),
);
}
In NestJS, you do not need to send req, res object to your function parameter. Nest Js provide build-in decorator for req.body, req.query and req.param as #Body, #Query, and #Param. I write down to call post method and get method. You can also use put, patch, delete, and other methods. Please make a data transfer object file in your module.
for further reference, you can check this: https://docs.nestjs.com/controllers
export class yourController {
constructor(private readonly httpService: HttpService) {}
#Post('your-route-name')
public postMethod(#Body() yourDTO: YourDTOClass): Promise<interface> {
try {
return this.httpService.method(yourDTO);
} catch (err) {
throw new HttpException(err, err.status || HttpStatus.BAD_REQUEST);
}
}
#Get('your-route-name')
find(#Query() query: QueryDTO): Promise<interface> {
try {
return this.httpService.methodName(query);
} catch (err) {
throw new HttpException(err, err.status || HttpStatus.BAD_REQUEST);
}
}
}
You should put return before this.httpService.post(...). Normally you would have to subscribe to the Observable returned by the post method but NestJS handles this for you through the #Get() decorator.
You should prefix your controller with "async" and use "await" followed by "toPromise()"...
constructor(private httpService: HttpService) {}
#Get('callback')
async callback(#Request() req, #Res() res): any {
// ... remaining code here
const response =
await this.httpService.post(url, form, { headers: headers }).toPromise();
return response;
}
Add this imports to the controller:
import { Observable } from 'rxjs';
import { take, tap, map } from 'rxjs/operators';
Then try this:
constructor(private httpService: HttpService) {}
#Get('callback')
callback(#Request() req, #Res() res): Observable<any> {
let code = req.query.code || null;
const url = 'https://some-url.com/api/token';
const form = {
code: code,
redirect_uri: this.redirect_uri,
grant_type: 'authorization_code'
}
const headers = {
'Authorization': 'Basic ' + (Buffer.from(this.client_id + ':' +
this.client_secret))
}
return this.httpService.post( url, form, { headers: headers }).pipe(
// Take first result to complete the observable..
take(1),
// [OPTIONAL] Some debug log to see the response.
tap((response: { data: any }) => {
console.log(`Response: ${JSON.stringify(response.data)}`);
})
// Map the response object to just return its data.
map((response: { data: any }) => response.data),
);
}

User is not available in request with NestJS passport strategy (other than documentation exemples)

I'm trying to implement a passport strategy (passport-headerapikey), I was able to make it work and I can secure my routes.
But the request is empty and cannot access the logged in user ?
import { HeaderAPIKeyStrategy } from "passport-headerapikey";
import { PassportStrategy } from "#nestjs/passport";
import { Injectable, NotFoundException } from "#nestjs/common";
import { CompanyService } from "../../companies/companies.service";
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy, "api-key") {
constructor(private readonly companyService: CompanyService) {
super(
{
header: "Authorization",
prefix: "Api-Key "
},
true,
async (apiKey, done) => {
return this.validate(apiKey, done);
}
);
}
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
throw new NotFoundException("Company not found");
}
return company;
}
}
#UseGuards(AuthGuard("api-key"))
export class CompaniesController {
constructor(private companyService: CompanyService) {}
#Get()
#ApiOperation({ title: "Get company information" })
public getCompany(#Request() req) {
// here request is empty, so i cannot access the user..
console.log("request", req);
return [];
}
}
Thanks for your help !
To access the logged user, you can inject the object in the request. To do that, in your ApiKeyStrategy constructor, change the third parameter to something like this:
async (apiKey, verified, req) => {
const user = await this.findUser(apiKey);
// inject the user in the request
req.user = user || null;
return verified(null, user || false);
}
Now, you can access the logged user:
getCompany(#Request() req) {
console.log(req.user);
}
I hope that could help you.
As show in the documentation you should do some works to get the current user : here the documetation
First of all in the app.module make sure that the context is set :
context: ({ req }) => ({ req })
Then you can add this in the controller/resolver, this example use the Gql (GraphQL):
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);
if this one doesnt work for you try this one instead :
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext();
request.body = ctx.getArgs();
return request.user;
},
);
Modify your validate method like so:
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
return done(new NotFoundException("Company not found"), null);
}
return done(null, company);
}

Angular: JWT Token works fine on Postman but not in my app

I'm a bit of a beginner with Angular so please bear with me.
I have a simple app which allows people to register, login and retrieve their own user data (which is the part I am stuck at).
Backend user.routes.js :
const auth = require('./middlewares/auth')
module.exports = (app) => {
const user = require('./user.controller.js');
app.post('/login', user.login);
app.post('/register', user.register);
app.get('/getuser', auth, user.getuser);
}
Backend user.controller.js:
exports.getuser = async (req, res, next) => {
let user
try {
user = await User.findById(req.payload._id)
} catch (err) {
next(new InternalServerError('Could not fetch user', err))
return
}
if (!user) {
next(new NotFoundError('User not found'))
return
}
res.json(
pick(user, [
'email',
'firstName',
'lastName',
'accountType'
])
)
}
Backend user.service.ts :
#Injectable()
export class UserService {
private _isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject(false);
public readonly isLoggedIn$ = this._isLoggedIn.asObservable();
constructor(private http: HttpClient) {
this._isLoggedIn.next(this.isLoggedIn());
}
login(
email: string,
password: string,
rememberMe = false
): Observable<boolean | any> {
return this.http
.post<LoginResponse>('http://localhost:3001/login', { email, password })
.map(res => {
setToken(res.token, rememberMe);
this._isLoggedIn.next(true);
return true;
})
.catch(this.handleError);
}
register(
email: string,
password: string,
lastName: string,
firstName: string
): Observable<boolean | any> {
return this.http
.post<LoginResponse>('http://localhost:3001/register', {
email,
password,
lastName,
firstName
})
.map(res => {
setToken(res.token);
return true;
})
.catch(this.handleError);
}
logout() {
removeToken();
}
isLoggedIn() {
return tokenNotExpired();
}
getProfile() {
return this.http.get<Profile>('http://localhost:3001/getuser');
}
And finally, my backend auth.js :
// Dependencies
import { JwtHelperService } from '#auth0/angular-jwt';
// Angular
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '#angular/common/http';
import { Injectable } from '#angular/core';
// RXJS
import { Observable } from 'rxjs/Observable';
// Environment
import { DecodedToken } from './decoded-token';
// Services
const helper = new JwtHelperService();
// Constants
export const TOKEN_NAME = 'access_token';
// Exports
export function getToken(storage = null) {
if (storage) {
const token = storage.getItem(TOKEN_NAME);
if (token && !helper.isTokenExpired(token)) {
return token;
}
removeToken(storage);
return null;
}
return getToken(localStorage) || getToken(sessionStorage);
}
export function setToken(token: string, rememberMe = false) {
const storage = rememberMe ? localStorage : sessionStorage;
storage.setItem(TOKEN_NAME, token);
}
export function removeToken(storage = null) {
if (storage) {
storage.removeItem(TOKEN_NAME);
} else {
localStorage.removeItem(TOKEN_NAME);
sessionStorage.removeItem(TOKEN_NAME);
}
}
export function tokenNotExpired() {
return !helper.isTokenExpired(getToken());
}
export function decodeToken(): DecodedToken {
return helper.decodeToken(getToken());
}
#Injectable()
export class JwtHttpInterceptor implements HttpInterceptor {
constructor() {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const token = getToken();
let clone: HttpRequest<any>;
if (token) {
clone = request.clone({
setHeaders: {
Accept: `application/json`,
'Content-Type': `application/json`,
Authorization: `Bearer ${token}`
}
});
} else {
clone = request.clone({
setHeaders: {
Accept: `application/json`,
'Content-Type': `application/json`
}
});
}
return next.handle(clone);
}
}
On my dashboard, I do a very simple request:
this.userService.getProfile().subscribe(data => (this.profile = data));
Now, my problem is the following:
Using Postman, if I do a POST request to /login, I get a token back. Everything fine so far. And if I use this token (in Postman) in my next GET request to /getuser, I also get the results I want (email, firstName, lastName, accountType of the user).
However, the problem is on the front-end. I login and arrive to the main page (no issues there), but once getProfile() is called, I get a GET http://localhost:3001/getuser 401 (Unauthorized) . I've been stuck on this for hours and not sure where the problem is from.
I appreciate any help I can get.
Thanks!
I found my issue. I had forgotten to add the Interceptor I had created to my providers in app.module.ts.
// Auth
{
provide: HTTP_INTERCEPTORS,
useClass: JwtHttpInterceptor,
multi: true
}

Resources