Tsed: [BUG] @Catch() does not catch exceptions generated by passport-jwt

Created on 16 Dec 2020  路  4Comments  路  Source: tsedio/tsed

Information

  • Version: 6.14.3
  • Packages:
"devDependencies": {
  "@tsed/cli-plugin-eslint": "2.7.5",
  "@tsed/cli-plugin-mocha": "2.7.5",
  "@tsed/cli-plugin-passport": "2.7.5",
  "@types/chai": "4.2.14",
  "@types/chai-as-promised": "7.1.3",
  "@types/compression": "^1.7.0",
  "@types/cookie-parser": "1.4.2",
  "@types/cors": "^2.8.8",
  "@types/express": "^4.17.9",
  "@types/lodash": "^4.14.162",
  "@types/method-override": "0.0.31",
  "@types/mime-types": "^2.1.0",
  "@types/mocha": "8.2.0",
  "@types/morgan": "^1.9.1",
  "@types/node": "14.14.11",
  "@types/nodemailer": "^6.4.0",
  "@types/passport": "1.0.4",
  "@types/passport-jwt": "^3.0.3",
  "@types/passport-local": "^1.0.33",
  "@types/sinon": "9.0.9",
  "@types/sinon-chai": "3.2.5",
  "@types/supertest": "2.0.10",
  "@typescript-eslint/eslint-plugin": "^4.9.1",
  "@typescript-eslint/parser": "^4.9.1",
  "chai": "4.2.0",
  "chai-as-promised": "7.1.1",
  "concurrently": "5.3.0",
  "eslint": "^7.15.0",
  "eslint-config-prettier": "^6.15.0",
  "eslint-plugin-prettier": "^3.2.0",
  "husky": "^4.3.5",
  "lint-staged": "^10.5.3",
  "mocha": "^8.2.1",
  "nodemon": "2.0.6",
  "nyc": "15.1.0",
  "prettier": "^2.2.1",
  "sinon": "^9.2.1",
  "sinon-chai": "3.5.0",
  "supertest": "^5.0.0",
  "ts-node": "^9.1.1",
  "tsconfig-paths": "^3.9.0",
  "tscpaths": "0.0.9",
  "typescript": "4.1.2"
},
"dependencies": {
  "@tsed/ajv": "6.14.3",
  "@tsed/common": "6.14.3",
  "@tsed/core": "6.14.3",
  "@tsed/di": "6.14.3",
  "@tsed/exceptions": "6.14.3",
  "@tsed/json-mapper": "^6.14.3",
  "@tsed/passport": "6.14.3",
  "@tsed/platform-express": "6.14.3",
  "@tsed/schema": "6.14.3",
  "@tsed/swagger": "6.14.3",
  "@types/bcrypt": "^3.0.0",
  "@types/express-session": "^1.17.3",
  "@types/express-useragent": "^1.0.0",
  "@types/jsonwebtoken": "^8.3.8",
  "@types/multer": "^1.4.5",
  "@types/oracledb": "^5.0.0",
  "@types/qs": "^6.9.5",
  "@types/swagger-schema-official": "^2.0.21",
  "ajv": "6.12.6",
  "axios": "^0.21.0",
  "bcrypt": "^5.0.0",
  "body-parser": "^1.19.0",
  "cerialize": "^0.1.18",
  "compression": "^1.7.4",
  "comtele-sdk": "^1.1.6",
  "cookie-parser": "^1.4.5",
  "cors": "^2.8.5",
  "cross-env": "^7.0.3",
  "dotenv": "^8.2.0",
  "express": "^4.17.1",
  "express-session": "^1.17.1",
  "express-useragent": "^1.0.15",
  "http-status-codes": "^2.1.4",
  "jsonwebtoken": "^8.5.1",
  "lodash": "^4.17.20",
  "memorystore": "^1.6.2",
  "method-override": "^3.0.0",
  "mime-types": "^2.1.26",
  "moment": "^2.29.1",
  "morgan": "^1.10.0",
  "multer": "^1.4.2",
  "nodemailer": "^6.4.16",
  "oracledb": "^5.1.0",
  "passport": "^0.4.1",
  "passport-jwt": "4.0.0",
  "passport-local": "1.0.0",
  "qs": "^6.9.3",
  "source-map-support": "^0.5.16",
  "typescript-param-validator": "^1.1.0"
},

Hi,
first thanks for the amazing library.
I'm having some problems with @Catch()
Exceptions like Unauthorized, Forbidden, BadRequest among others, work perfectly
but when passport-jwt throws an exception, that exception does not pass through @Catch()

Here my HttpExceptionFilter

import {Catch, ExceptionFilterMethods, PlatformContext, ResponseErrorObject} from "@tsed/common";
import {Exception} from "@tsed/exceptions";
import {JsonWebTokenError, TokenExpiredError} from "jsonwebtoken";
import {ResultMessageModel} from "../core/response/result-message.model";

@Catch(Error, Exception, TokenExpiredError, JsonWebTokenError)
export class HttpExceptionFilter implements ExceptionFilterMethods {
    catch(exception: Exception & Error, ctx: PlatformContext): void {
        const {response, logger} = ctx;
        const error = this.mapError(exception);
        const headers = this.getHeaders(exception);

        logger.error({
            error,
        });

        response
            .setHeaders(headers)
            .status(error.httpStatus || 500)
            .body(error);
    }

    mapError(error: Exception): ResultMessageModel {
        return {
            name: error.origin?.name || error.name,
            message: error.message,
            httpStatus: error.status || 500,
            reasons: this.getErrors(error),
        };
    }

    protected getErrors(error: Exception): string[] {
        return [error, error.origin].filter(Boolean).reduce((errs, {errors}: ResponseErrorObject) => {
            return [...errs, ...(errors || [])];
        }, []);
    }

    protected getHeaders(error: Exception): {[key: string]: unknown} {
        return [error, error.origin].filter(Boolean).reduce((obj, {headers}: ResponseErrorObject) => {
            return {
                ...obj,
                ...(headers || {}),
            };
        }, {});
    }
}

JwtProtocol

import {Inject, Req} from "@tsed/common";
import {Forbidden} from "@tsed/exceptions";
import {Arg, OnInstall, OnVerify, Protocol} from "@tsed/passport";
import {ExtractJwt, Strategy, StrategyOptions} from "passport-jwt";
import {UserModel} from "../models/user.model";
import {UserService} from "../services/user/user.service";
import {Randon} from "../utils/randon";

@Protocol<StrategyOptions>({
    name: "jwt",
    useStrategy: Strategy,
    settings: {
        jwtFromRequest: ExtractJwt.fromExtractors([
            ExtractJwt.fromAuthHeaderAsBearerToken(),
            ExtractJwt.fromHeader(process.env.JWT_ACCESS_TOKEN || ""),
            ExtractJwt.fromUrlQueryParameter("auth_token"),
            ExtractJwt.fromUrlQueryParameter(process.env.JWT_ACCESS_TOKEN || ""),
        ]),
        secretOrKey: process.env.API_SECRET,
        algorithms: ["HS256"],
        issuer: "localhost",
        audience: "localhost",
    },
})
export class JwtProtocol implements OnVerify, OnInstall {
    @Inject()
    private userService: UserService;

    async $onVerify(@Req() req: Req, @Arg(0) jwtPayload: UserModel): Promise<UserModel | boolean> {
        if (jwtPayload.underAnalysis == true) {
            throw new Forbidden("Account under analysis!");
        }

        const userType = Randon.decrypt(jwtPayload.type) as "ADMIN" | "CLIENT";

        const user = await this.userService.findById(+Randon.decrypt(`${jwtPayload.id || ""}`));
        if (!user || user.type !== userType) {
            throw new Forbidden("Access not allowed! ");
        }

        req.user = user;
        if (req.session) {
            const session = req.session;
            session.user = user;
        }

        return user ? user : false;
    }

    $onInstall(): void {
        // intercept the strategy instance to adding extra configuration
    }
}

Exception thrown

Screen Shot 2020-12-16 at 9 37 43 AM

Swagger Responses

Screen Shot 2020-12-16 at 10 40 25 AM

bug released

Most helpful comment

Fixed
See: https://tsed.io/tutorials/passport.html#catch-passport-exception

All 4 comments

Hello @teusemanuel :)
Unfortunately, I'm not sure if it's possible. Passport send the error directly to the client without forward error to the middlewares chain. It means, Ts.ED cannot intercept this error. I'll investigate to find a solution.

See you
Romain

Hello @teusemanuel
I found the reason. Passport doesn't use class inherited from Error.
The fix is ready. It should solve your issue!

:tada: This issue has been resolved in version 6.18.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

Fixed
See: https://tsed.io/tutorials/passport.html#catch-passport-exception

Was this page helpful?
0 / 5 - 0 ratings