From 717eb147a848657518e6992d9ebea4176bfc606e Mon Sep 17 00:00:00 2001 From: Chirag Chauhan Date: Sat, 14 Sep 2024 10:43:10 +0530 Subject: [PATCH 1/2] add cookie support for HTTP bearer authentication --- src/middlewares/openapi.security.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/middlewares/openapi.security.ts b/src/middlewares/openapi.security.ts index 8f43af14..3f1962de 100644 --- a/src/middlewares/openapi.security.ts +++ b/src/middlewares/openapi.security.ts @@ -232,14 +232,21 @@ class AuthValidator { const authHeader = req.headers['authorization'] && req.headers['authorization'].toLowerCase(); - - if (!authHeader) { + const authCookie = req.cookies[scheme.name] || req.signedCookies?.[scheme.name]; + if (!authHeader && !authCookie) { throw Error(`Authorization header required`); } const type = scheme.scheme && scheme.scheme.toLowerCase(); - if (type === 'bearer' && !authHeader.includes('bearer')) { - throw Error(`Authorization header with scheme 'Bearer' required`); + if (type === 'bearer') { + if (authHeader && !authHeader.includes('bearer')) { + throw Error(`Authorization header with scheme 'Bearer' required`); + } + if (!authHeader && authCookie === undefined) { + throw Error( + `Bearer token required in authorization header or cookie`, + ); + } } if (type === 'basic' && !authHeader.includes('basic')) { From 34863e9adcd5600f530916d90397a00b5bf4c884 Mon Sep 17 00:00:00 2001 From: Chirag Chauhan Date: Sat, 14 Sep 2024 10:58:10 +0530 Subject: [PATCH 2/2] Support ESM import of handler module in default resolver --- src/framework/types.ts | 6 +++--- src/openapi.validator.ts | 2 +- src/resolvers.ts | 27 +++++++++++++++++---------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/framework/types.ts b/src/framework/types.ts index 2df2ea07..07666c94 100644 --- a/src/framework/types.ts +++ b/src/framework/types.ts @@ -77,9 +77,9 @@ export type ValidateSecurityOpts = { }; export type OperationHandlerOptions = { - basePath: string; + basePath: string | URL; resolver: ( - handlersPath: string, + handlersPath: string | URL, route: RouteMetadata, apiDoc: OpenAPIV3.Document, ) => unknown; @@ -169,7 +169,7 @@ export interface OpenApiValidatorOpts { $refParser?: { mode: 'bundle' | 'dereference'; }; - operationHandlers?: false | string | OperationHandlerOptions; + operationHandlers?: false | string | URL | OperationHandlerOptions; validateFormats?: boolean | 'fast' | 'full'; } diff --git a/src/openapi.validator.ts b/src/openapi.validator.ts index 3265c187..3d130a35 100644 --- a/src/openapi.validator.ts +++ b/src/openapi.validator.ts @@ -56,7 +56,7 @@ export class OpenApiValidator { if (options.formats == null) options.formats = {}; if (options.useRequestUrl == null) options.useRequestUrl = false; - if (typeof options.operationHandlers === 'string') { + if (typeof options.operationHandlers === 'string' || options.operationHandlers instanceof URL) { /** * Internally, we want to convert this to a value typed OperationHandlerOptions. * In this way, we can treat the value as such when we go to install (rather than diff --git a/src/resolvers.ts b/src/resolvers.ts index 3092e3a3..405f1d6c 100644 --- a/src/resolvers.ts +++ b/src/resolvers.ts @@ -2,13 +2,17 @@ import * as path from 'path'; import { RequestHandler } from 'express'; import { RouteMetadata } from './framework/openapi.spec.loader'; import { OpenAPIV3 } from './framework/types'; +import { fileURLToPath, pathToFileURL } from 'url'; + +// Prevent TypeScript from replacing dynamic import with require() +const dynamicImport = new Function('specifier', 'return import(specifier)'); const cache = {}; -export function defaultResolver( - handlersPath: string, +export async function defaultResolver( + handlersPath: string | URL, route: RouteMetadata, apiDoc: OpenAPIV3.Document, -): RequestHandler { +): Promise { const tmpModules = {}; const { basePath, expressRoute, openApiRoute, method } = route; const pathKey = openApiRoute.substring(basePath.length); @@ -29,14 +33,17 @@ export function defaultResolver( `found x-eov-operation-handler for route [${method} - ${expressRoute}]. operationId or x-eov-operation-id required.`, ); } - if (oId && baseName && typeof handlersPath === 'string') { - const modulePath = path.join(handlersPath, baseName); - if (!tmpModules[modulePath]) { - tmpModules[modulePath] = require(modulePath); - } - - const handler = tmpModules[modulePath][oId] || tmpModules[modulePath].default[oId] || tmpModules[modulePath].default; + const hasHandlerPath = typeof handlersPath === 'string' || handlersPath instanceof URL; + if (oId && baseName && hasHandlerPath) { + const modulePath = typeof handlersPath === 'string' + ? path.join(handlersPath, baseName) + : path.join(fileURLToPath(handlersPath), baseName); + const importedModule = typeof handlersPath === 'string' + ? require(modulePath) + : await dynamicImport(pathToFileURL(modulePath).toString()); + const handler = importedModule[oId] || importedModule.default?.[oId] || importedModule.default; + if (!handler) { throw Error( `Could not find 'x-eov-operation-handler' with id ${oId} in module '${modulePath}'. Make sure operation '${oId}' defined in your API spec exists as a handler function (or module has a default export) in '${modulePath}'.`,