NestJS pipe Joi.validate() (is not a function) - hapi.js

I try to use Joi validator on NestJS with pipe.
https://docs.nestjs.com/pipes#object-schema-validation
import * as Joi from '#hapi/joi';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '#nestjs/common';
#Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(
private readonly schema: Joi.ObjectSchema,
) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = Joi.validate(value, this.schema);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
It doesn't work properly.
TypeError: Joi.validate is not a function

Use schema.validate in place of Joi.validate, for example:
const schema = Joi.object({
name: Joi.string().min(3).required()
});
const result = schema.validate(req.body);
or for more info go to https://hapi.dev/family/joi/?v=16.1.8

I have made an PR to update the https://docs.nestjs.com and it looks like it is already deployed, so you can refer to it.
#hapijs/joi deprecated Joi.validate with version 16 and you have to call .validate directly on schema.

Related

How to install Express middleware (express-openapi-validator) in NestJS?

I am writing a NestJS application. Now I want to install the Express middleware express-openapi-validator.
However, I can't get it to work. There is a description for how to install the express-openapi-validator in express, but it always results in errors.
For example
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(middleware({apiSpec "./bff-api.yaml"}))
.forRoutes(OrganizationController)
}
}
results in
error TS2345: Argument of type 'OpenApiRequestHandler[]' is not assignable to parameter of type 'Function | Type<any>'.
Type 'OpenApiRequestHandler[]' is missing the following properties from type 'Type<any>': apply, call, bind, prototype, and 4 more.
How can I install this middleware in NestJS?
I added a NestJS example to express-openapi-validator (static link for posterity).
The AppModule looks basically identical, although you don't need to iterate over the middlewares:
#Module({
imports: [PingModule],
providers: [{ provide: APP_FILTER, useClass: OpenApiExceptionFilter }],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
...OpenApiValidator.middleware({
apiSpec: join(__dirname, './api.yaml'),
}),
)
.forRoutes('*');
}
}
I also added an exception filter to convert the error from express-openapi-validator to a proper response; otherwise I would always get a 500 error. You could also use this approach to convert the error into a custom error format.
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
import { Response } from 'express';
import { error } from 'express-openapi-validator';
#Catch(...Object.values(error))
export class OpenApiExceptionFilter implements ExceptionFilter {
catch(error: ValidationError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
response.status(error.status).json(error);
}
}
interface ValidationError {
status: number;
message: string;
errors: Array<{
path: string;
message: string;
error_code?: string;
}>;
path?: string;
name: string;
}
I have now got it working:
configure(consumer: MiddlewareConsumer) {
middleware({
apiSpec: `${__dirname}/../api-doc/bff-api.yaml`
}).forEach(value => consumer.apply(value).forRoutes(OrganizationController))
}

Customise the response on verification failure for a jwt Strategy NestJs

I successfully implemented a jwt strategy for authentication using nestJs.
Below is the code for the jwt strategy
import { ServerResponse } from './../helpers/serverResponse.helper';
import { Injectable, UnauthorizedException, HttpStatus } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { config as env } from 'dotenv';
import { Bugsnag } from '../helpers/bugsnag.helper';
env();
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
private readonly logger: Bugsnag,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET_KEY,
passReqToCallback: true,
});
}
async validate(payload, done: Function) {
try {
const validClaims = await this.authService.verifyTokenClaims(payload);
if (!validClaims)
return done(new UnauthorizedException('invalid token claims'), false);
done(null, payload);
} catch (err) {
this.logger.notify(err);
return ServerResponse.throwError({
success: false,
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: 'JwtStrategy class, validate function',
errors: [err],
});
}
}
}
I saw here that the validate function will be called only when a valid token was provided in the request headers and I'm okay with that. However, I would like to know if it is possible to customize the response object which is sent in that case (invalid token provided).
If yes, how do I do that ?
You can use a exception filter to catch UnauthorizedExceptions and modify the response there if you'd like. The other option would be extending the AuthGuard('jwt') mixin class and adding in some logic around a try/catch for the super.canActivate(context), then in the error read what the reason is and throw a specific UnauthorizedException with your custom message
You can use the AuthGuard('jwt')'s handleRequest method to throw any exception on JWT Validation failure.
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err: any, user: any, info: any, context: any, status: any) {
if (info instanceof JsonWebTokenError) {
throw new UnauthorizedException('Invalid Token!');
}
return super.handleRequest(err, user, info, context, status);
}
}
JsonWebTokenError comes from jsonwebtoken library, which is used internally by passport.

How can I use type-graphql and RESTDataSource

I wonder how it is possible to use RESTDataSource in type-graphql and thus cache correctly in a redis. I would be grateful for a small example.
At the moment I use the DI container to get a service, which is extended from the RestDataSource class, but this is not the right way.
BookmarkResolver.ts
import { Resolver, FieldResolver, Root, Query, Ctx, Authorized } from 'type-graphql';
import { DealService } from '../service/DealService';
import { AvailableLocale } from '../enum/AvailableLocale';
import { Bookmark } from '../entity/Bookmark';
#Resolver(_of => Bookmark)
export class BookmarkResolver {
constructor(private dealService: DealService) {}
#FieldResolver()
async wordpressDeal(#Root() bookmark: Bookmark) {
return await this.dealService.getDealById(bookmark.item_id, AvailableLocale.STAGING);
}
}
DealService.ts
import { Service } from 'typedi';
import { AbstractService } from './AbstractService';
import { AvailableLocale } from '../enum/AvailableLocale';
#Service()
export class DealService extends AbstractService {
baseURL = process.env.DEAL_SERVICE_URL;
async getDealById(dealId: string | number, locale: AvailableLocale) {
const response = await this.get(
'deals/' + dealId,
{ locale }
);
return this.dealReducer(response);
}
dealReducer(deal: any) {
return {
id: deal.id || 0,
title: deal.title
};
}
}
AbstractService.ts
import { RESTDataSource, HTTPCache } from 'apollo-datasource-rest';
import { Service } from 'typedi';
#Service()
export class AbstractService extends RESTDataSource {
constructor() {
super();
this.httpCache = new HTTPCache();
}
}
Share the RESTDataSource via ApolloServer's context. Use it in the resolver by accessing the context with the #Ctx() decorator.
1. Define a RESTDataSource
Define the data source according to the apollo-datasource-rest example.
export class TodoDataSource extends RESTDataSource {
constructor() {
super();
this.baseURL = "https://jsonplaceholder.typicode.com/todos";
}
async getTodos(): Promise<Todo[]> {
return this.get("/");
}
}
2. Create an instance of the DataSource and put it in the Context
When you start the server, add data sources to the context by defining a function that creates the data sources.
const server = new ApolloServer({
schema,
playground: true,
dataSources: () => ({
todoDataSource: new TodoDataSource(),
}),
});
3. Access the DataSource in the resolver
Use the #Ctx() decorator to access the context in the resolver so you can use the data source.
#Resolver(Todo)
export class TodoResolver {
#Query(() => [Todo])
async todos(#Ctx() context: Context) {
return context.dataSources.todoDataSource.getTodos();
}
}
Full, runnable example at https://github.com/lauriharpf/type-graphql-restdatasource

How to call a http post method from a service in a parent director

My http method returns results when it is contained in my component, but does not return any results when called from a service located one directory up.
I've checked the console and there are no errors. I have tried printing to the console, which works from within the service (returns the desired data), but does not when run from within the child component.
This is the service that I'm trying to build:
import { Injectable } from '#angular/core';
import { Resturant } from '../../models/resturant.model'
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class GetResturantsService {
fullListresturants: Resturant[];
constructor(private http:HttpClient) { }
fetchList(){
this.http.get('https://lunchlads.firebaseio.com/posts.json')
.pipe(map(responseData =>{
const postsArray: Resturant[] = [];
for (const key in responseData) {
if (responseData.hasOwnProperty(key)){
postsArray.push({ ...responseData[key], id:key })
}
}
return postsArray;
}))
.subscribe(posts => {
// this.fullListresturants = posts;
});
}
}
This is the component which is one file down in the directory:
import { Component, OnInit } from '#angular/core';
import { Resturant } from '../../../models/resturant.model'
import { GetResturantsService } from '../get-resturants.service'
import { HttpClient } from '#angular/common/http';
//import { map } from 'rxjs/operators';
#Component({
selector: 'app-list-all',
templateUrl: './list-all.component.html',
styleUrls: ['./list-all.component.css']
})
export class ListAllComponent implements OnInit {
fullListresturants: Resturant;
constructor(private http:HttpClient, private listAllResturants:GetResturantsService) { }
ngOnInit() {
this.onfullList();
}
onfullList(){
this.fullList();
}
private fullList(){
// this.http.get('https://lunchlads.firebaseio.com/posts.json')
// .pipe(map(responseData =>{
// const postsArray: Resturant[] = [];
// for (const key in responseData) {
// if (responseData.hasOwnProperty(key)){
// postsArray.push({ ...responseData[key], id:key })
// }
// }
// return postsArray;
// }))
// .subscribe(posts => {
// // this.fullListresturants = posts;
// });
this.listAllResturants.fetchList();
}
}
The firebase backend contains roughly 10 records with a name:string, votes:number, and selected:number fields. When run from the component, the html file simply returns the name values with an *ngFor loop.
When run from the service, nothing is returned and no errors are reported in the console.
I suspect the problem lies somewhere in how I am calling the fetchList method from the component, but google and me have not been able to suss out what I'm doing wrong.
Your service should return an observable to make it work. As per your current code, you are not returning anything from GetResturantsService.fetchList(). To make it work let change the service like this:
export class GetResturantsService {
fullListresturants: Resturant[];
constructor(private http:HttpClient) { }
fetchList(){
return this.http.get('https://lunchlads.firebaseio.com/posts.json')
.pipe(map(responseData =>{
const postsArray: Resturant[] = [];
for (const key in responseData) {
if (responseData.hasOwnProperty(key)){
postsArray.push({ ...responseData[key], id:key })
}
}
return postsArray;
}));
}
}
Now in component subscribe to the observable returned from fetchList method like this:
export class ListAllComponent implements OnInit {
fullListresturants: Resturant;
constructor(private http:HttpClient, private listAllResturants:GetResturantsService) { }
ngOnInit() {
this.onfullList();
}
onfullList(){
this.fullList();
}
private fullList(){
this.listAllResturants.fetchList()
.subscribe(posts => {
//DO whatever you want to do with posts
this.fullListresturants = posts;
});
}
}
Hope it helps.

Applying Middleware-like mechanism to Resolvers' Queries and Mutations

I'm building a GraphQL API using Nest framework and I'm trying to implement 3rd party express middlewares (express-rate-limit and express-slow-down) into some queries and mutations.
The problem is all graphql mutations and queries use the same endpoint, so I can't explicitly tell to which query or mutations shall the middleware be applied, because you can only do that using route's path (which is the same across the API).
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '#nestjs/common'
import * as rateLimit from 'express-rate-limit'
import * as RedisStore from 'rate-limit-redis'
import { RedisClient } from 'redis'
#Module({
providers: [],
exports: [],
})
export default class SecurityModule implements NestModule
{
constructor(protected readonly redisClient: RedisClient)
{
}
configure(consumer: MiddlewareConsumer)
{
consumer.apply(
new rateLimit({
max: 300,
windowMs: 15 * 60 * 1000,
store: new RedisStore({ client: this.redisClient }),
})).forRoutes({ path: '/graphql', method: RequestMethod.ALL }) // this would apply the middleware to all queries and mutations
}
}
So I tried using both guards and interceptors for that purpose, but failed miserably.
It's a fail for an obvious reason.
The Error: Can't set headers after they are sent is thrown.
/* !!! My Interceptor would like quite identical */
import { ExecutionContext, Injectable, CanActivate } from '#nestjs/common'
import * as speedLimit from 'express-slow-down'
import { Request, Response } from 'express'
#Injectable()
export default class SpeedLimitGuard implements CanActivate
{
constructor(
protected readonly options: speedLimit.Options,
) {
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const { req, res }: { req: Request, res: Response } = context.getArgs()[2]
speedLimit({ ...this.options })(req, res, req.next)
return true
}
}
import { NestInterceptor, ExecutionContext, Injectable, INestApplication, INestExpressApplication } from '#nestjs/common'
import { Observable } from 'rxjs'
import * as speedLimit from 'express-slow-down'
// import { Request, Response } from 'express'
import { ApplicationReferenceHost } from '#nestjs/core'
import { RedisClient } from 'redis'
import * as RedisStore from 'rate-limit-redis'
#Injectable()
export default class SpeedLimitInterceptor implements NestInterceptor
{
constructor(private readonly appRefHost: ApplicationReferenceHost,
private readonly redisClient: RedisClient, )
{}
intercept<T>(context: ExecutionContext, call$: Observable<T>): Observable<T>
{
// const { req: request, res: response }: { req: Request, res: Response } = context.getArgs()[2]
const httpServer = this.appRefHost.applicationRef
const app: INestApplication & INestExpressApplication = httpServer.getInstance()
app.use(speedLimit({
delayAfter: 1,
store: new RedisStore({
prefix: 'test_',
client: this.redisClient,
}),
}))
app.use((req, res, next) => {
console.log('is middleware triggered', { req, res })
next()
})
return call$
}
}
Is there any way to apply a 3rd party express middleware to a GraphQL Mutation/Query explicitly?
So from the bottom, guards are working, because I'm the living human bean that can prove it:
#Query('getHome')
#UseGuards(GraphqlGuard)
async findOneById(#Args('id') id: string): Promise<HomeEntity> {
return await this.homeService.findOneById(id);
}
and it's just working.
This is GraphqlGuard.ts
import {ExecutionContext, Injectable} from '#nestjs/common';
import {GqlExecutionContext} from '#nestjs/graphql';
import {AuthGuard} from '#nestjs/passport';
import {ExecutionContextHost} from '#nestjs/core/helpers/execution-context.host';
import {Observable} from 'rxjs';
#Injectable()
export class GraphqlGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const ctx = GqlExecutionContext.create(context);
const {req} = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
}
But to live with context, you have to make it works for you, so, wherever you're passing graphql config, there is an context callback, and for me it looks like this:
context: (context) => {
let req = context.req;
if (context.connection) {
req = context.connection.context.req;
}
return {req};
}
I'm checking here connection for context from websocket. Im using global interceptors so, they're working like a charm. But you still can use #UseInterceptors(SomeInterceptor) decorator and it also works. And btw Middlewares, at the end, I doesn't need any of them guards, pipes, validators and interceptors for me was quite enough.
Regards.