From 929f6e866fdbd7b33e673c0581dc89464f6aec1e Mon Sep 17 00:00:00 2001 From: Matthew Logan <62873746+LocalNewsTV@users.noreply.github.com> Date: Wed, 20 Nov 2024 07:15:36 -0800 Subject: [PATCH] [INV-3662] Create Error Handler, move middleware to named files, create common Database caller (#3696) * create error handling middleware * Create commonDbRequest function to handle setup/teardown * Move bearer logic to separate file * move cors logic to separate file, create index for imports * refactor App.ts to use the imported functions * Remove common Db Request --- api/src/app.ts | 62 ++---------------------- api/src/middleware/bearerHandler.ts | 21 ++++++++ api/src/middleware/cors.ts | 21 ++++++++ api/src/middleware/globalErrorHandler.ts | 27 +++++++++++ api/src/middleware/index.ts | 3 ++ 5 files changed, 76 insertions(+), 58 deletions(-) create mode 100644 api/src/middleware/bearerHandler.ts create mode 100644 api/src/middleware/cors.ts create mode 100644 api/src/middleware/globalErrorHandler.ts create mode 100644 api/src/middleware/index.ts diff --git a/api/src/app.ts b/api/src/app.ts index 1fd3a9049..1e1a7c3d9 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -5,9 +5,8 @@ import compression from 'compression'; import { initialize } from 'express-openapi'; import { api_doc } from 'sharedAPI/src/openapi/api-doc/api-doc'; import { applyApiDocSecurityFilters } from 'utils/api-doc-security-filter'; -import { authenticate, InvasivesRequest } from 'utils/auth-utils'; import { getLogger } from 'utils/logger'; -import { MDC, MDCAsyncLocal } from 'mdc'; +import * as middleware from './middleware'; const defaultLog = getLogger('app'); @@ -30,30 +29,7 @@ function shouldCompress(req, res) { } // Enable CORS -app.use(function (req: any, res: any, next: any) { - // - // if (req.url !== '/api/misc/version') { - // // filter out health check for log brevity - // MDC - // } - - res.setHeader('Access-Control-Allow-Credentials', true); - res.setHeader( - 'Access-Control-Allow-Headers', - 'X-Requested-With, Content-Type, Authorization, responseType, Access-Control-Allow-Origin, If-None-Match, filterForSelectable' - ); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE, HEAD'); - res.setHeader('Access-Control-Allow-Origin', '*'); - - // create a context if there isn't one - let mdc = MDCAsyncLocal.getStore(); - if (!mdc) { - mdc = new MDC(); - MDCAsyncLocal.run(mdc, next); - } else { - next(); - } -}); +app.use(middleware.cors); // Initialize express-openapi framework initialize({ @@ -69,43 +45,13 @@ initialize({ 'application/x-www-form-urlencoded': bodyParser.urlencoded({ limit: '50mb', extended: true }) }, securityHandlers: { - Bearer: async function (req) { - try { - let mdc = MDCAsyncLocal.getStore(); - if (!mdc) { - mdc = new MDC(); - await MDCAsyncLocal.run(mdc, authenticate, req); - } else { - await authenticate(req); - } - } catch (e) { - defaultLog.error({ error: e }); - return false; - } - // await applyApiDocSecurityFilters((req)); - return true; - } + Bearer: middleware.bearerHandler }, - securityFilter: applyApiDocSecurityFilters, + errorMiddleware: middleware.globalErrorHandler, errorTransformer: function (openapiError: object, ajvError: object): object { - // Transform openapi-request-validator and openapi-response-validator errors defaultLog.error({ label: 'errorTransformer', message: 'ajvError', ajvError }); return ajvError; - }, - // If `next` is not included express will silently skip calling the `errorMiddleware` entirely. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - errorMiddleware: function (error, req, res, next) { - defaultLog.error({ - label: 'errorHandler', - message: 'unexpected error', - error: error?.message + error?.stack || error - }); - if (!res.headersSent) { - // streaming responses cannot alter headers after dispatch - res.status(error.status || error.code || 500).json(error); - } else { - } } }); diff --git a/api/src/middleware/bearerHandler.ts b/api/src/middleware/bearerHandler.ts new file mode 100644 index 000000000..fa0982469 --- /dev/null +++ b/api/src/middleware/bearerHandler.ts @@ -0,0 +1,21 @@ +import { MDC, MDCAsyncLocal } from 'mdc'; +import { authenticate, InvasivesRequest } from 'utils/auth-utils'; +import { getLogger } from 'utils/logger'; + +const bearerHandler = async (req) => { + const logger = getLogger('Error'); + try { + let mdc = MDCAsyncLocal.getStore(); + if (!mdc) { + mdc = new MDC(); + await MDCAsyncLocal.run(mdc, authenticate, req); + } else { + await authenticate(req); + } + } catch (e) { + logger.error({ error: e }); + return false; + } + return true; +}; +export default bearerHandler; diff --git a/api/src/middleware/cors.ts b/api/src/middleware/cors.ts new file mode 100644 index 000000000..53cdbda1e --- /dev/null +++ b/api/src/middleware/cors.ts @@ -0,0 +1,21 @@ +import { MDC, MDCAsyncLocal } from 'mdc'; + +const cors = (_: any, res: any, next) => { + res.setHeader('Access-Control-Allow-Credentials', true); + res.setHeader( + 'Access-Control-Allow-Headers', + 'X-Requested-With, Content-Type, Authorization, responseType, Access-Control-Allow-Origin, If-None-Match, filterForSelectable' + ); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE, HEAD'); + res.setHeader('Access-Control-Allow-Origin', '*'); + + // create a context if there isn't one + let mdc = MDCAsyncLocal.getStore(); + if (!mdc) { + mdc = new MDC(); + MDCAsyncLocal.run(mdc, next); + } else { + next(); + } +}; +export default cors; diff --git a/api/src/middleware/globalErrorHandler.ts b/api/src/middleware/globalErrorHandler.ts new file mode 100644 index 000000000..2b31c7574 --- /dev/null +++ b/api/src/middleware/globalErrorHandler.ts @@ -0,0 +1,27 @@ +import { NextFunction, Request, Response } from 'express'; +import { getLogger } from 'utils/logger'; + +export class CustomError extends Error { + code: number; + constructor(message?: string, code?: number) { + super(message); + this.code = code; + } +} +const globalErrorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => { + const logger = getLogger('Error'); + const code = err instanceof CustomError ? err.code : 500; + const errorResponse = { + req: req.body, + error: err.message || 'Internal Server Error', + method: req.method, + namespace: req.url + }; + logger.error(errorResponse); + if (!res.headersSent) { + res.status(code).json(errorResponse); + } + next(); +}; + +export default globalErrorHandler; diff --git a/api/src/middleware/index.ts b/api/src/middleware/index.ts new file mode 100644 index 000000000..7fd2f2a0f --- /dev/null +++ b/api/src/middleware/index.ts @@ -0,0 +1,3 @@ +export { default as globalErrorHandler } from './globalErrorHandler'; +export { default as bearerHandler } from './bearerHandler'; +export { default as cors } from './cors';