diff --git a/src/framework/types.ts b/src/framework/types.ts index 994b70ca..8f20d01b 100644 --- a/src/framework/types.ts +++ b/src/framework/types.ts @@ -70,7 +70,7 @@ export type Format = { }; export type Serializer = { - format: string, + format: string; serialize: (o: unknown) => string; }; @@ -436,7 +436,7 @@ export interface OpenApiRequestMetadata { } export interface OpenApiRequest extends Request { - openapi?: OpenApiRequestMetadata | {}; + openapi?: OpenApiRequestMetadata; } export type OpenApiRequestHandler = ( diff --git a/src/middlewares/openapi.metadata.ts b/src/middlewares/openapi.metadata.ts index 67817cd2..6c9ebf82 100644 --- a/src/middlewares/openapi.metadata.ts +++ b/src/middlewares/openapi.metadata.ts @@ -3,6 +3,8 @@ import { pathToRegexp } from 'path-to-regexp'; import { Response, NextFunction } from 'express'; import { OpenApiContext } from '../framework/openapi.context'; import { + MethodNotAllowed, + NotFound, OpenApiRequest, OpenApiRequestHandler, OpenApiRequestMetadata, @@ -24,6 +26,12 @@ export function applyOpenApiMetadata( const matched = lookupRoute(req); if (matched) { const { expressRoute, openApiRoute, pathParams, schema } = matched; + if (!schema) { + throw new MethodNotAllowed({ + path: req.path, + message: `${req.method} method not allowed`, + }); + } req.openapi = { expressRoute: expressRoute, openApiRoute: openApiRoute, @@ -36,7 +44,10 @@ export function applyOpenApiMetadata( (req.openapi)._responseSchema = (matched)._responseSchema; } } else if (openApiContext.isManagedRoute(path)) { - req.openapi = {}; + throw new NotFound({ + path: req.path, + message: 'not found', + }); } next(); }; diff --git a/src/middlewares/openapi.request.validator.ts b/src/middlewares/openapi.request.validator.ts index 6d4f4433..9e235119 100644 --- a/src/middlewares/openapi.request.validator.ts +++ b/src/middlewares/openapi.request.validator.ts @@ -63,21 +63,7 @@ export class RequestValidator { const openapi = req.openapi; const path = openapi.expressRoute; - if (!path) { - throw new NotFound({ - path: req.path, - message: 'not found', - }); - } - const reqSchema = openapi.schema; - if (!reqSchema) { - throw new MethodNotAllowed({ - path: req.path, - message: `${req.method} method not allowed`, - }); - } - // cache middleware by combining method, path, and contentType const contentType = ContentType.from(req); const contentTypeKey = contentType.equivalents()[0] ?? 'not_provided'; diff --git a/src/middlewares/openapi.security.ts b/src/middlewares/openapi.security.ts index 5a90a609..d09979b9 100644 --- a/src/middlewares/openapi.security.ts +++ b/src/middlewares/openapi.security.ts @@ -38,28 +38,6 @@ export function security( } const openapi = req.openapi; - const expressRoute = openapi.expressRoute; - if (!expressRoute) { - return next( - new NotFound({ - path: req.path, - message: 'not found', - }), - ); - } - - const pathSchema = openapi.schema; - if (!pathSchema) { - // add openapi metadata to make this case more clear - // its not obvious that missig schema means methodNotAllowed - return next( - new MethodNotAllowed({ - path: req.path, - message: `${req.method} method not allowed`, - }), - ); - } - // use the local security object or fallbac to api doc's security or undefined const securities: OpenAPIV3.SecurityRequirementObject[] = openapi.schema.security ?? apiDoc.security; @@ -82,54 +60,54 @@ export function security( securitySchemes, securityHandlers, securities, - ).executeHandlers(req); - + ).executeHandlers(req); + // TODO handle AND'd and OR'd security // This assumes OR only! i.e. at least one security passed authentication - let firstError: SecurityHandlerResult = null; + let firstError: SecurityHandlerResult = null; let success = false; - + function checkErrorWithOr(res) { - return res.forEach(r => { - if (r.success) { - success = true; - } else if (!firstError) { - firstError = r; - } - }) + return res.forEach((r) => { + if (r.success) { + success = true; + } else if (!firstError) { + firstError = r; + } + }); } function checkErrorsWithAnd(res) { - let allSuccess = false; - - res.forEach(r => { - if (!r.success) { - allSuccess = false; - if (!firstError) { - firstError = r; - } - } else if (!firstError) { - allSuccess = true; - } - }) - - if (allSuccess) { - success = true; + let allSuccess = false; + + res.forEach((r) => { + if (!r.success) { + allSuccess = false; + if (!firstError) { + firstError = r; + } + } else if (!firstError) { + allSuccess = true; } + }); + + if (allSuccess) { + success = true; + } } - results.forEach(result => { - if (Array.isArray(result) && result.length > 1) { - checkErrorsWithAnd(result); - } else { - checkErrorWithOr(result); - } + results.forEach((result) => { + if (Array.isArray(result) && result.length > 1) { + checkErrorsWithAnd(result); + } else { + checkErrorWithOr(result); + } }); if (success) { - next(); + next(); } else { - throw firstError; + throw firstError; } } catch (e) { const message = e?.error?.message || 'unauthorized'; @@ -177,50 +155,56 @@ class SecuritySchemes { // anonumous security return [{ success: true }]; } - return Promise.all(Object.keys(s).map(async (securityKey) => { - var _a, _b, _c; - try { - const scheme = this.securitySchemes[securityKey]; - const handler = (_b = (_a = this.securityHandlers) === null || _a === void 0 ? void 0 : _a[securityKey]) !== null && _b !== void 0 ? _b : fallbackHandler; - const scopesTmp = s[securityKey]; - const scopes = Array.isArray(scopesTmp) ? scopesTmp : []; - if (!scheme) { - const message = `components.securitySchemes.${securityKey} does not exist`; - throw new InternalServerError({ message }); - } - if (!scheme.hasOwnProperty('type')) { - const message = `components.securitySchemes.${securityKey} must have property 'type'`; - throw new InternalServerError({ message }); - } - if (!handler) { - const message = `a security handler for '${securityKey}' does not exist`; - throw new InternalServerError({ message }); - } - new AuthValidator(req, scheme, scopes).validate(); - // expected handler results are: - // - throw exception, - // - return true, - // - return Promise, - // - return false, - // - return Promise - // everything else should be treated as false - const securityScheme = scheme; - const success = await handler(req, scopes, securityScheme); - if (success === true) { - return { success }; - } - else { - throw Error(); + return Promise.all( + Object.keys(s).map(async (securityKey) => { + var _a, _b, _c; + try { + const scheme = this.securitySchemes[securityKey]; + const handler = + (_b = + (_a = this.securityHandlers) === null || _a === void 0 + ? void 0 + : _a[securityKey]) !== null && _b !== void 0 + ? _b + : fallbackHandler; + const scopesTmp = s[securityKey]; + const scopes = Array.isArray(scopesTmp) ? scopesTmp : []; + if (!scheme) { + const message = `components.securitySchemes.${securityKey} does not exist`; + throw new InternalServerError({ message }); + } + if (!scheme.hasOwnProperty('type')) { + const message = `components.securitySchemes.${securityKey} must have property 'type'`; + throw new InternalServerError({ message }); + } + if (!handler) { + const message = `a security handler for '${securityKey}' does not exist`; + throw new InternalServerError({ message }); + } + new AuthValidator(req, scheme, scopes).validate(); + // expected handler results are: + // - throw exception, + // - return true, + // - return Promise, + // - return false, + // - return Promise + // everything else should be treated as false + const securityScheme = scheme; + const success = await handler(req, scopes, securityScheme); + if (success === true) { + return { success }; + } else { + throw Error(); + } + } catch (e) { + return { + success: false, + status: (_c = e.status) !== null && _c !== void 0 ? _c : 401, + error: e, + }; } - } - catch (e) { - return { - success: false, - status: (_c = e.status) !== null && _c !== void 0 ? _c : 401, - error: e, - }; - } - })); + }), + ); }); return Promise.all(promises); }