How to handle error while returning an observable in angular? - node.js

I am creating an AuthGuard for my app..now when i try to load the component without getting logged in it should redirect me to the login page..But i am getting an error like following
and nothing happens.
I am throwing this error from my backend {"status":401,"message":"Auth Token Not found!"}}
as there is no auth token
The following is the code of my AuthGuard
export class AuthGuardService implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> {
return this.authService.checkLogin().pipe(
map((data: HttpResponse) => {
if (data.status == 200) {
console.log("OUTPUT:", data)
return true
}
else return false
}),
)
}
}
The following is my function in AuthService:
public checkLogin():Observable<HttpResponse> {
return this.http.get<HttpResponse>('http://localhost:5000/auth/check-login', { withCredentials: true })
}
Now how can i handle the errors like these and set a fallback value to false so if any error occurs then that route could not be accessed

If I understood you correctly you want to achieve the following behavior:
If checkLogin() gets a response that indicates "success", the auth-guard shall return true
If checkLogin() gets an error-response, redirect the user to a fallback-page and return false
If you use the code below, please note that catchError() is only triggered if the backend responds with an exception, while map() is always triggered if a success-response comes in from the backend.
Therefore, in the positive case, you can simply return true without checking the contents of the response. However, if you receive an exception from the backend, you can redirect the user to the fallback page using this.router.navigate() and then return of(false) to prevent the request from passing the auth guard.
export class AuthGuardService implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> {
return this.authService.checkLogin().pipe(
map(() => true),
catchError(() => {
this.router.navigate(['route-to-fallback-page']);
return of(false);
})
);
}
}
Alternative solution for Angular 7.1+
Starting from Angular 7.1 you can just return a UrlTree-Object containing the fallback-route:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
return this.authService.checkLogin().pipe(
map(() => true),
catchError(() => this.router.parseUrl('/route-to-fallback-page'))
);
}

You can use shared error response
this.http.get<HttpResponse>('http://localhost:5000/auth/check-login', { withCredentials: true }).pipe(
catchError((error=>{
this.getErrorMessage(error);
return throwError(()=>error);
}))
)
The getErrorMessage Function will return the errors;
private getErrorMessage(error:HttpErrorResponse){
switch(error.status){
case 400:{
return this.toast.error(`Bad Request :${JSON.stringify(error.error?.Message)}`,error.status.toString())
}
case 401:{
return this.toast.error(`Unauthorized :${JSON.stringify(error.error?.Message)}`,error.status.toString())
}
case 403:{
return this.toast.error(`Access Denied :${JSON.stringify(error.error?.Message)}`,error.status.toString())
}
case 500:{
return this.toast.error(`Internal Server Error :${JSON.stringify(error.error?.Message)}`,error.status.toString())
}
case 404:{
return this.toast.error(`Page Not Found :${JSON.stringify(error.error?.Message)}`,error.status.toString())
}
default:{
return this.toast.error('Check your internet connection!');
}
}
}

Related

How to make class-validator to stop on error?

I want class validator to stop validation as soon as the first error is found.
I know there is a stopAtFirstError option, but that prevents further validation on property level only. I want to stop it globally:
#IsString() // Let's say this received invalid value
someStr: string
#IsInt() // I want this to be NOT executed
someNuber: number
Is this possible?
Since there is no official support for this, I implemented a hacky way to make it work. Although it doesn't stop the validation, only returns the message from initial one & ignores the rest.
ValidationErrorException
// validation-error.exception.ts
import { HttpException, HttpStatus } from "#nestjs/common";
export class ValidationErrorException extends HttpException {
data: any;
constructor(data: any) {
super('ValidationError', HttpStatus.BAD_REQUEST);
this.data = data
}
getData(){
return this.data;
}
}
Use Exception Factory to return the exception
// main.ts
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
stopAtFirstError: true,
exceptionFactory: (errors: ValidationError[]) => {
return new ValidationErrorException(errors);
},
}),
);
Validation Error Filter
// validation-error.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost } from '#nestjs/common';
import { Response } from 'express';
import { ValidationErrorException } from './validation-error.exception';
#Catch(ValidationErrorException)
export class ValidationErrorFilter implements ExceptionFilter {
catch(exception: ValidationErrorException, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>();
const status = exception.getStatus();
let constraints = exception.getData()[0].constraints
response
.status(status)
.json({
statusCode: status,
message: constraints[Object.keys(constraints)[0]],
error: exception.message
});
}
}
Use The filter
// app.module.ts
providers: [
{
provide: APP_FILTER,
useClass: ValidationErrorFilter,
},
],

In nestjs, how can we change default error messages from typeORM globally?

I have this code to change the default message from typeorm when a value in a unique column already exists. It just creates a custom message when we get an error 23505.
if (error.code === '23505') {
// message = This COLUMN VALUE already exists.
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
throw new BadRequestException(message);
}
throw new InternalServerErrorException();
I will have to use it in other services, so I would like to abstract that code.
I think I could just create a helper and then I import and call it wherever I need it. But I don’t know if there is a better solution to use it globally with a filter or an interceptor, so I don’t have to even import and call it in different services.
Is this possible? how can that be done?
If it is not possible, what do you think the best solution would be?
Here all the service code:
#Injectable()
export class MerchantsService {
constructor(
#InjectRepository(Merchant)
private merchantRepository: Repository<Merchant>,
) {}
public async create(createMerchantDto: CreateMerchantDto) {
try {
const user = this.merchantRepository.create({
...createMerchantDto,
documentType: DocumentType.NIT,
isActive: false,
});
await this.merchantRepository.save(user);
const { password, ...merchantData } = createMerchantDto;
return {
...merchantData,
};
} catch (error) {
if (error.code === '23505') {
// message = This COLUMN VALUE already exists.
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
throw new BadRequestException(message);
}
throw new InternalServerErrorException();
}
}
public async findOneByEmail(email: string): Promise<Merchant | null> {
return this.merchantRepository.findOneBy({ email });
}
}
I created an exception filter for typeORM errors.
This was the result:
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpStatus,
InternalServerErrorException,
} from '#nestjs/common';
import { Response } from 'express';
import { QueryFailedError, TypeORMError } from 'typeorm';
type ExceptionResponse = {
statusCode: number;
message: string;
};
#Catch(TypeORMError, QueryFailedError)
export class TypeORMExceptionFilter implements ExceptionFilter {
private defaultExceptionResponse: ExceptionResponse =
new InternalServerErrorException().getResponse() as ExceptionResponse;
private exceptionResponse: ExceptionResponse = this.defaultExceptionResponse;
catch(exception: TypeORMError | QueryFailedError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
exception instanceof QueryFailedError &&
this.setQueryFailedErrorResponse(exception);
response
.status(this.exceptionResponse.statusCode)
.json(this.exceptionResponse);
}
private setQueryFailedErrorResponse(exception: QueryFailedError): void {
const error = exception.driverError;
if (error.code === '23505') {
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
this.exceptionResponse = {
statusCode: HttpStatus.BAD_REQUEST,
message,
};
}
// Other error codes can be handled here
}
// Add more methods here to set a different response for any other typeORM error, if needed.
// All typeORM erros: https://github.com/typeorm/typeorm/tree/master/src/error
}
I set it globally:
import { TypeORMExceptionFilter } from './common';
async function bootstrap() {
//...Other code
app.useGlobalFilters(new TypeORMExceptionFilter());
//...Other code
await app.listen(3000);
}
bootstrap();
And now I don't have to add any code when doing changes in the database:
#Injectable()
export class MerchantsService {
constructor(
#InjectRepository(Merchant)
private merchantRepository: Repository<Merchant>,
) {}
public async create(createMerchantDto: CreateMerchantDto) {
const user = this.merchantRepository.create({
...createMerchantDto,
documentType: DocumentType.NIT,
isActive: false,
});
await this.merchantRepository.save(user);
const { password, ...merchantData } = createMerchantDto;
return {
...merchantData,
};
}
}
Notice that now I don't use try catch because nest is handling the exceptions. When the repository save() method returns an error (actually it is a rejected promise), it is caught in the filter.

Nestjs Interceptor how to catch http 401 error and resubmit original request

I need to write an http header interceptor to add Authorization header, if there is a 401 error, submit another request for a new token, then resubmit the original request with the new token.
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
return next.handle().pipe(
catchError(async error => {
if (error.response.status === 401) {
const originalRequest = error.config;
var authRes = await this.authenticationService.getAccessToken();
this.authenticationService.accessTokenSubject.next(authRes.access_token);
// I need to resubmit the original request with the new token from here
// but return next.handle(originalRequest) doesn't work
}
return throwError(error);
}),
);
}
But next.handle(originalRequest) doesn't work. How to resubmit the original request in the interceptor? Thank you very much in advance for your help.
I just encountered a similar problem, where I can catch the exception from exception filter but can't do so in interception layer.
So I looked up the manual and found it says:
Any exception thrown by a guard will be handled by the exceptions layer
(global exceptions filter and any exceptions filters that are applied to the current context).
So, if the exception is thrown from AuthGuard context(including the validate method in your AuthService), probably better to move the additional logic by extending the Authguard
like this:
export class CustomizedAuthGuard extends AuthGuard('strategy') {
handleRequest(err, user, info, context, status) {
if (err || !user) {
// your logic here
throw err || new UnauthorizedException();
}
return user;
}
}
or simply using customized exception filter.
It's been a while since the question but maybe it will help someone.
Ok, suppose that we need handle unauthorize exception out of route and guards, maybe service to service. So you can implement a interceptor like that and add some logic to get some data if needed, Ex: inject some Service in the interceptor.
So, throw an unauthorize exception and we are going to intercept it:
#Injectable()
export class UnauthorizedInterceptor implements NestInterceptor {
constructor(
private readonly authService: AuthService,
private readonly httpService: HttpService,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError((err) => {
const {
response: { status, config },
} = err;
// assuming we have a request body
const jsonData = JSON.parse(config.data);
if (status === HttpStatus.UNAUTHORIZED) {
// We can use some data in payload to find user data
// here for example the user email
if (jsonData?.email) {
return
from(this.authService.getByUserEmail(jsonData.email)).pipe(
switchMap((user: User) => {
if (user) {
// Ex: we can have stored token info in user entity.
// call function to refresh access token and update user data
// with new tokens
return from(this.authService.refreshToken(user)).pipe(
switchMap((updatedUser: User) => {
// now updatedUser have the new accessToken
const { accessToken } = updatedUser;
// set the new token to config (original request)
config.headers['Authorization'] = `Bearer ${accessToken}`;
// and use the underlying Axios instance created by #nestjs/axios
// to resubmit the original request
return of(this.httpService.axiosRef(config));
}),
);
}
}),
);
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
}),
);
}
}

Class-Validator node.js provide custom error

I have a custom validator constraint and annotation created for checking whether entity with given property already exists or not, here is the code
import { Inject, Injectable } from '#nestjs/common';
import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint } from 'class-validator';
import { ValidatorConstraintInterface } from 'class-validator/types/validation/ValidatorConstraintInterface';
import { Connection } from 'typeorm';
import { InjectConnection } from '#nestjs/typeorm';
#ValidatorConstraint({ async: true })
#Injectable()
export class EntityExistsConstraint implements ValidatorConstraintInterface {
constructor(#InjectConnection() private dbConnection: Connection) {
}
defaultMessage(validationArguments?: ValidationArguments): string {
return `${validationArguments.constraints[0].name} with ${validationArguments.property} already exists`;
}
validate(value: any, validationArguments?: ValidationArguments): Promise<boolean> | boolean {
const repoName = validationArguments.constraints[0];
const property = validationArguments.property;
const repository = this.dbConnection.getRepository(repoName);
return repository.findOne({ [property]: value }).then(result => {
return !result;
});
}
}
export function EntityExists(repoName, validationOptions?: ValidationOptions) {
return function(object: any, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [repoName],
validator: EntityExistsConstraint,
});
};
}
Everything works fine, but I receive this response when the validation fails
{
"statusCode": 400,
"message": [
"User with email already exists"
],
"error": "Bad Request"
}
I want the error be Conflict Exception=> statusCode 409, how can I achieve this?
class-validator doesn't do anything with the http codes. It only validates and returns a list of errors or an empty array.
What you need to do is to check framework you use, I assume it's nestjs or routing-controllers.
In the case of routing-controllers you need to write own after middleware and disable default middleware (it converts validation errors to 400 bad requests).
More info is here: https://github.com/typestack/routing-controllers#error-handlers
In the case of nestjs - the same steps.
More info you can find here: https://docs.nestjs.com/exception-filters#catch-everything

Angular2 Passing parameters to web service http GET

I have a profileComponent which is making a GET call to service endpoint as follows , AparmentService is injected in bootstarp, hence no providers
#Component({
selector: 'profile',
template: `<h1>Profile Page</h1>
{{userEmail.email}}
{{profileObject | json}}
`,
directives: [ROUTER_DIRECTIVES]
})
export class ProfileComponent implements OnInit {
userEmail = JSON.parse(localStorage.getItem('profile'));
public profileObject: Object[];
constructor(private apartmentService: ApartmentService) {
this.apartmentService = apartmentService;
}
ngOnInit(): any {
console.log(this.userEmail.email); <--This value displays fine in the console
this.apartmentService.getProfile(this.userEmail.email).subscribe(res => this.profileObject = res); <-- getting [] response for this
console.log(JSON.stringify(this.profileObject)); <-- undefined
}
}
The service looks like this
#Injectable()
export class ApartmentService {
http: Http;
constructor(http: Http) {
this.http = http;
}
getProfile(userEmail :string){
return this.http.get('/api/apartments/getprofile/:userEmail').map((res: Response) => res.json());
}
}
when I try to hit the endpoint directly in the browser with the parameter, I am getting the respone. But not within Angular.
Any Ideas ?
http.get() is async
ngOnInit(): any {
console.log(this.userEmail.email); <--This value displays fine in the console
this.apartmentService.getProfile(this.userEmail.email).subscribe(res => this.profileObject = res); <-- getting [] response for this
// at this position the call to the server hasn't been made yet.
console.log(JSON.stringify(this.profileObject)); <-- undefined
}
When the response from the server arives res => this.profileObject = res is executed. console.log() is made before the call to the server was even initalized
Use instead
ngOnInit(): any {
console.log(this.userEmail.email); <--This value displays fine in the console
this.apartmentService.getProfile(this.userEmail.email)
.subscribe(res => {
this.profileObject = res;
console.log(JSON.stringify(this.profileObject));
});
}
I think :userEmail in the URL isn't doing what you expect. Try instead:
getProfile(userEmail :string){
return this.http.get(`/api/apartments/getprofile/${userEmail}`).map((res: Response) => res.json());
}

Resources