how to execute UseGuard in nestjs microservie - api

I'm planning to use my authentication part as a microservice so I user nest js TCP transporter but I don't know how to execute passport local strategy in my microservice I used the below code
#MessagePattern('login')
#UseGuards(LocalAuthGuard)
localLogin( loginDto: LoginDto) {
console.log('awa')
return loginDto
// return this.authService.localLogin(req.user, loginDto.email);
}
but it doesn't work any idea how can I authorized local strategy in microservice my local strategy looks like below
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthService } from './auth.service';
import { Request } from 'express';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true,
});
}
async validate(
email: string,
password: string,
): Promise<{ id: string; isVerified: boolean }> {
try {
const user = await this.authService.validateLocalUser(email, password);
if (!user) {
//throw new UnauthorizedException();
}
return { id: user.id,isVerified: user.isVerified };
} catch (error) {
console.log('error')
}
}
}
This is the error I got
node:71975) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'reply' of null
at AppExceptionFilter.catch (/Users/imanthaatapattu/apps/beta-identity-service/node_modules/#nestjs/core/exceptions/base-exception-filter.js:26:24)
at AppExceptionFilter.catch (/Users/imanthaatapattu/apps/beta-identity-service/dist/common/exception-filters/app-exception.filter.js:29:20)
at RpcExceptionsHandler.invokeCustomFilters (/Users/imanthaatapattu/apps/beta-identity-service/node_modules/#nestjs/microservices/exceptions/rpc-exceptions-handler.js:34:32)
at RpcExceptionsHandler.handle (/Users/imanthaatapattu/apps/beta-identity-service/node_modules/#nestjs/microservices/exceptions/rpc-exceptions-handler.js:13:36)
at RpcProxy.handleError (/Users/imanthaatapattu/apps/beta-identity-service/node_modules/#nestjs/microservices/context/rpc-proxy.js:24:34)
at /Users/imanthaatapattu/apps/beta-identity-service/node_modules/#nestjs/microservices/context/rpc-proxy.js:17:29
at processTicksAndRejections (internal/process/task_queues.js:93:5)
(node:71975) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 3)
(node:71975) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Passport is an Express middleware, meant for use over HTTP. You're using it with a middleware, which isn't quite expected. If you want to continue using passport with a microservice, you need to provide the same values it expects under the HTTP context. You can see Nest's implementation of the AuthGuard here. The most important part of this, is making sure that getRequest returns an object with the property body and with body having the properties email and password. It would most likely be easier to write your own microservice specific guard that can determine if the request is valid or not (via a JWT or similar)

Related

How are Guards and Strategies exactly working under the hood in Nestjs?

I am new to Nestjs and I am using guards, strategies and passport for authentification.
I don't understand what's going on under the hood.
I have a guard for a refreshToken mutation:
import { AuthGuard } from '#nestjs/passport';
import { ExecutionContext } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
export class RtGuard extends AuthGuard('jwt-refresh') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
console.log('REFRESH');
return ctx.getContext().req;
}
}
What does this guard exactly do? Somehow it calls my strategy right? But it only does it, if I provide a correct refreshToken.
This is my Strategy:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, ForbiddenException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { JwtPayloadWithRt } from '../types/jwtPayloadWithRt.type';
import { JwtPayload } from 'src/auth/types/jwtPayload.type';
import { Request } from 'express';
#Injectable()
export class RefreshTokenStrategy extends PassportStrategy(
Strategy,
'jwt-refresh',
) {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.get<string>('REFRESH_TOKEN_SECRET'),
passReqToCallback: true,
});
}
validate(req: Request, payload: JwtPayload): JwtPayloadWithRt {
const refreshToken = req.get('authorization')?.replace('Bearer', '').trim();
if (!refreshToken) throw new ForbiddenException('Refresh token malformed');
return {
...payload,
refreshToken,
};
}
}
How is the guard able to decide whether my refresh token is the one in my database and if so, then it calls my strategy?
If I use a wrong refreshToken, not the one I got when I signed in, I get this error:
when providing the correct key, I get this:
Using console.log, I can see that my strategy is not called, whenever the refreshtoken is invalid.
How does the validation work exactly? How do guard and strategy work together under the hood?
Thanks a lot for help!
Named strategies
When implementing a strategy, you can provide a name for it by passing a second argument to the PassportStrategy function. If you don't do this, each strategy will have a default name
(e.g., 'jwt' for jwt-strategy):
export class JwtStrategy extends PassportStrategy (Strategy, 'myjwt')
Default name !== class name. The default name is imposed by the strategy you use.
For example, the default strategy for passport-jwt https://github.com/mikenicholson/passport-jwt is jwt
Source:
https://github.com/nestjs/nest/issues/4753#issuecomment-625675342
About how the guard is able to decide whether the token is in the database:
It doesn't. It just verifies that is valid, it is done using the secret key, which has to be the same that you signed the token with in the beggining. If it isn't valid it will throw a ForbiddenException thus the application never reaches the console.log('REFRESH') part of your code.
You could validate that it is in the db by your self, that's what the validate() method could be used for.
A quote from the nestjs docs:
It's also worth pointing out that this approach leaves us room
('hooks' as it were) to inject other business logic into the process.
For example, we could do a database lookup in our validate() method to
extract more information about the user, resulting in a more enriched
user object being available in our Request. This is also the place we
may decide to do further token validation, such as looking up the
userId in a list of revoked tokens, enabling us to perform token
revocation. The model we've implemented here in our sample code is a
fast, "stateless JWT" model, where each API call is immediately
authorized based on the presence of a valid JWT, and a small bit of
information about the requester (its userId and username) is available
in our Request pipeline.
Source: https://docs.nestjs.com/security/authentication#implementing-passport-jwt
validate(req: Request, payload: JwtPayload): JwtPayloadWithRt {
const refreshToken = req.get('authorization')?.replace('Bearer', '').trim();
if (!refreshToken) throw new ForbiddenException('Refresh token malformed');
/*
Perform database checks in this part of your code
*/
//Whatever you return here gets attached to the Request object as `req.user`,
//you can change it to whatever you want
return {
...payload,
refreshToken,
};
}

how to use asynchronous error handlers in onError callback?

I'm creating a link to remove user auth token on a 401 response from server. The problem here, is that I need to remove tokesn from storage in my case AsyncStorage, which, as its name suggests, performs asynchronous operations, but if I try to pass an asynchronous function to onError callback from #apollo/client/link/error, the code editor immediately throws the following error: Type 'Promise<void>' is not assignable to type 'void | Observable<FetchResult<{ [key: string]: any; },.
So there is a way to pass an async callback to onError?
Invalidate token link
import {ErrorResponse, onError} from '#apollo/client/link/error';
import {isJwtError, JWTError} from './errors';
// async function to remove auth token from asyncstorage
import {removeTokens} from './utils';
interface ResponseError extends ErrorResponse {
networkError?: Error & {
statusCode?: number;
bodyText?: string;
};
}
export const invalidateTokenLink = onError((error: ResponseError) => {
if (
(error.networkError && error.networkError.statusCode === 401) ||
error.graphQLErrors?.some(isJwtError)
) {
if (error.graphQLErrors[0].extensions.code !== JWTError.expired) {
// this is where I run an asynchronous function to remove the token
removeTokens();
}
}
});
The error message mentions that you can return an Observable<FetchResult<{ [key: string]: any; }. It seems that apollo-client uses the zen-observable library.
You can build an observable from your promise. Or use the promiseToObservable function from observable-helpers.

How Do I consume errors in my Vue Graphql component and let other errors be handled globally?

We use Vue with apollo and I have difficulties to handle errors properly.
This is on of our components
<template>
<div v-if="product">
<router-view :key="product.id" :product="product" />
</div>
<div v-else-if="!this.$apollo.loading">
<p>Product is not available anymore</p>
</div>
</template>
<script>
import productQuery from "#/graphql/product.graphql";
export default {
name: "ProductWrapper",
props: ["productId"],
apollo: {
product: {
query: productQuery,
variables() {
return {
productId: this.productId,
};
},
},
},
};
</script>
If the product is not available anymore, we have three options in the backend:
a) the backend just can send null without errors
b) send an error object as part of the data with unions
c) send some error extensions with apollo for easy error handling in the client
Option a) seems to be a strange option
Option b) is too complicated for our use case.
So I decided for option c):
In our backend we use apollo error extensions to send some proper errorCode for the client to handle.
{
data: { }
errors: [
{
"message": "NOT_FOUND",
"locations": ...,
"path": ...
"extensions": {
"details": [],
"errorCode": "NOT_FOUND",
"message": "NOT_FOUND",
"classification": "DataFetchingException"
}
]
}
Everything works fine as product results in null anyway as no data is sent, just an error.
But vue-apollo is logging this to console.error. I don't want any logging to console.error as the user sees an red mark in his browser. But I want it to pop up in the console.error if nobody else has handled this error.
I can add an error handling in three places:
error() inside query definition
$error() default handler for all queries
ErrorLink
ErrorLink seems to be the wrong place as only the query in the component knows that NOT_FOUND is not fatal but can happen sometimes. Same is true for $error
So how do I say: this error might happen, I am well prepared for this. All other errors should be handled by the ErrorLink. How can I consume an error in my component?
My overview over vue-apollo error handling.
SmartQuery error handler
Documentation: https://apollo.vuejs.org/api/smart-query.html
error(error, vm, key, type, options) is a hook called when there are
errors. error is an Apollo error object with either a graphQLErrors
property or a networkError property. vm is the related component
instance. key is the smart query key. type is either 'query' or
'subscription'. options is the final watchQuery options object.
Not documented: If you return false the error processing is stopped and the default error handler (Apollo Provider) is not called. If you return true or do not return anything (aka undefined) the default error handler is called.
Code Example
export default {
name: "ProductWrapper",
props: ['productId'],
apollo: {
product: {
query: productQuery,
variables() {
return {
productId: this.productId
}
},
error(errors) {
console.log("Smart Query Error Handler")
console.log(errors.graphQLErrors)
return false;
}
}
}
}
VueApolloProvider errorHandler
Documentation: https://apollo.vuejs.org/api/apollo-provider.html
Global error handler for all smart queries and subscriptions
Important: This is NOT for mutations.
Not documented: This is not called when an error handler of a smart query returned false
Code Example
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
errorHandler: (errors) => {
console.log("Provider errorHandler")
console.log(errors)
}
});
VueApolloProvider $error special option (useless)
Documentation
https://apollo.vuejs.org/guide/apollo/special-options.html
$error to catch errors in a default handler (see error advanced options > for smart queries)
Code Example (wrong)
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
error(errors) {
console.log("Provider $error")
console.log(errors)
}
}
});
This was my first try, but it results in a call when the component is mounted and gives us the complete component. See https://github.com/vuejs/vue-apollo/issues/126
Code Example (kind of ok?)
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$error() {
return (errors) => {
console.log("Provider $error handler")
console.log(errors.graphQLErrors)
}
},
},
});
This way the $error function is called.
But it is called just like the default errorHandler (see above). the documentation seems to be wrong. So this method looks rather useless to me. As you can see in this debugging screenshot, it is used just like the other error handlers:
ErrorLink
Documentation: https://www.apollographql.com/docs/react/data/error-handling/#advanced-error-handling-with-apollo-link
Code example
import {onError} from "apollo-link-error";
const errorHandler = onError(({ networkError, graphQLErrors }) => {
console.log("Link onError")
console.log({ graphQLErrors, networkError})
})
const link = split(
// split based on operation type
({query}) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
},
wsErrorHandler.concat(wsLink),
errorHandler.concat(httpLink)
);
Important: this is called before any other ErrorHandler and it works for queries, subscriptions and mutations
Mutation catch
Documentation https://apollo.vuejs.org/guide/apollo/mutations.html
Code example 1
async submit() {
await this.$apollo.mutate({
mutation: productDeleteMutation,
variables: {
productId: this.product.id
},
}).catch((errors) => {
console.log("mutation .catch error")
console.log(errors)
})
}
Code example 2
You can use try/catch too:
async submit() {
try {
await this.$apollo.mutate({
mutation: productDeleteMutation,
variables: {
productId: this.product.id
},
})
} catch (errors) {
console.log("mutation try/catch error")
console.log(errors)
}
}
the only other handler which can be called is the onError ErrorLink (see above), which would be called before the catch error handling.
Mutation handled by vue with errorCaptured
Add this lifecycle hook to your component to catch errors from mutations.
errorCaptured(err, vm, info) {
console.log("errorCaptured")
console.log(err.graphQLErrors)
return false
}
Documentation: https://v2.vuejs.org/v2/api/#errorCaptured
More links: https://medium.com/js-dojo/error-exception-handling-in-vue-js-application-6c26eeb6b3e4
It works for mutations only as errors from smart queries are handled by apollo itself.
Mutation errors with vue global errorHandler
You can have a vue default error handler to catch graphql errors from mutations
import Vue from 'vue';
Vue.config.errorHandler = (err, vm, info) => {
if (err.graphQLErrors) {
console.log("vue errorHandler")
console.log(err.graphQLErrors)
}
};
It works for mutations only as errors from smart queries are handled by apollo itself.
Documentation https://v2.vuejs.org/v2/api/#errorHandler
window.onerror
Errors outside vue can be handled with plain javascript
window.onerror = function(message, source, lineno, colno, error) {
if (error.graphQLErrors) {
console.log("window.onerror")
console.log(error.graphQLErrors)
}
};
This does not work for mutations, queries or subscriptions inside or outside vue components as these are handle by vue or apollo itself (and printed to console.err if not handled)
Documentation https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror
Summary
The only way to catch errors for mutations AND queries at the same time is onError with an ApolloLink. Server, Network and Authentication errors should be catched there as these are not specific to any operation.
Additional notes
Approach how to handle errors in the backend
https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces/

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.

Asynchronous controller in Express for Form parsing to Mongoose

Currently, I'm developing a way to upload a message (file and fields) from Dropzone to Mongoose using Express Router. My back-end controller (which is called after authentication and data validation) goes as follows:
//Import Internal Dependencies
const Loader = require('../models/loader.js');
const Formidable = require('formidable');
const fs = require('fs');
module.exports = {
load: async (req, res, next) => {
var form = new Formidable.IncomingForm();
let path;
let contentType;
await form.parse(req, async function (err, fields, files) {
if (err) {
return res.status(404).json(err);
} else {
const {
user,
patient,
condition,
compound,
classi
} = fields;
path = files.image.path;
contentType = files.image.type;
fs.readFile(path, async function (err, data) {
if (err) {
return res.status(404).json(err);
} else {
//Save load
const newLoader = new Loader({
user,
patient,
condition,
compound,
classi,
image: {
data,
contentType
}
});
//Delete image in local storage
await fs.unlink(path, function (error) {
if(error){
return res.status(404).json(error)
}
});
await newLoader.save();
res.status(200).json("Load image sucessfully.");
next()
}
})
}
});
}
};
When I test it with Postman I got a status 202 and images are successfully upload to the database. However, when I try to upload with dropzone without the fields (which should cause some error and be displayed in dropzone) I got the following errors/warning in the back-end console (Dropzone stoped at upload and didn't show any error):
(node:20834) UnhandledPromiseRejectionWarning: ValidationError: load validation failed: user: Path `user` is required., classi: Path `classi` is required.
at new ValidationError (/root/aimuneBack/node_modules/mongoose/lib/error/validation.js:27:11)
at model.Document.invalidate (/root/aimuneBack/node_modules/mongoose/lib/document.js:1876:32)
at p.doValidate.skipSchemaValidators (/root/aimuneBack/node_modules/mongoose/lib/document.js:1744:17)
at /root/aimuneBack/node_modules/mongoose/lib/schematype.js:808:9
at _combinedTickCallback (internal/process/next_tick.js:131:7)
at process._tickCallback (internal/process/next_tick.js:180:9)
(node:20834) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:20834) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
at emitWarning (internal/process/promises.js:92:15)
at emitPendingUnhandledRejections (internal/process/promises.js:109:11)
at process._tickCallback (internal/process/next_tick.js:189:7)
POST /load - - ms - -
So I know I have done something wrong with my asynchronous code and unfortunately cannot figure it out. Hope you can help. Best regards, Andre