From eec4cba9c34e48f4204ad62308e1af5e2d3c8191 Mon Sep 17 00:00:00 2001 From: Carmine DiMascio Date: Sun, 13 Oct 2019 19:51:45 -0400 Subject: [PATCH] use default security handlersi when security handlers not undefined --- README.md | 72 +++++++++++++++++++++-------- src/index.ts | 10 ++++ src/middlewares/openapi.security.ts | 26 ++++++----- test/security.spec.ts | 11 +---- 4 files changed, 79 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index d2d03344..86362008 100644 --- a/README.md +++ b/README.md @@ -88,42 +88,80 @@ new OpenApiValidator(options).install({ validateRequests: true, validateResponses: true, unknownFormats: ['phone-number', 'uuid'], + multerOpts: { ... }, securityHandlers: { ApiKeyAuth: (req, scopes, schema) => { throw { status: 401, message: 'sorry' } } - }, - multerOpts: { ... }, + } }); ``` **Option details:** -**`apiSpec:` _required_** a string value specifying the path to the OpenAPI 3.0.x spec or a JSON object representing an OpenAPI spec. +### apiSpec (required) + +Specifies the path to an OpenAPI 3 specification or a JSON object representing the OpenAPI 3 specificiation + +```javascript +apiSpec: './path/to/my-openapi-spec.yaml' +``` -**`validateRequests:`** enable response validation. +or -- `true` - (default) validate requests. +```javascript +apiSpec: { + // the openapi specification as JSON +} +``` + + +### validateRequests (optional) + +Determines whether the validator should validate requests. + +- `true` (**default**) - validate requests. - `false` - do not validate requests. -**`validateResponses:`** enable response validation. +### validateResponses (optional) -- `true` - validate responses -- `false` - (default) do not validate responses +Determines whether the validator should validate responses. -**`unknownFormats:`** handling of unknown and/or custom formats. Option values: +- `true` - validate responses +- `false` (**default**) - do not validate responses -- `true` (default) - if an unknown format is encountered, validation will report a 400 error. -- `[string]` - an array of unknown format names that will be ignored by the validator. This option can be used to allow usage of third party schemas with format(s), but still fail if another unknown format is used. (_Recommended if unknown formats are used_) -- `"ignore"` - to log warning during schema compilation and always pass validation. This option is not recommended, as it allows to mistype format name and it won't be validated without any error message. +### unknownFormats (optional) - **For example:** +Defines how the validator should behave if an unknown or custom format is encountered. +- `true` **(default)** - When an unknown format is encountered, the validator will report a 400 error. +- `[string]` **_(recommended for unknown formats)_** - An array of unknown format names that will be ignored by the validator. This option can be used to allow usage of third party schemas with format(s), but still fail if another unknown format is used. + + e.g. + ```javascript unknownFormats: ['phone-number', 'uuid'] ``` -**`securityHandlers:`** register authentication handlers +- `"ignore"` - to log warning during schema compilation and always pass validation. This option is not recommended, as it allows to mistype format name and it won't be validated without any error message. + +### multerOpts (optional) + +Specifies the options to passthrough to multer. express-openapi-validator uses multer to handle file uploads. see [multer opts](https://github.com/expressjs/multer) + +### coerceTypes (optional) + +Determines whether the validator should coerce value types to match the type defined in the OpenAPI spec. + +- `true` (**default**) - coerce scalar data types. +- `false` - no type coercion. +- `"array"` - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema). + +### securityHandlers (optional) + +Specifies a set of custom security handlers to be used to validate security scenarios. If security handlers are ***not*** provided, a default handler is always used. The default handler will validate against the OpenAPI spec, then call the next middleware. + +If `securityHandlers` are specified, the validator will validate against the OpenAPI spec, then call the security handler providing it the Express request, the security scopes, and the security schema object. - `securityHandlers` is an object that maps security keys to security handler functions. Each security key must correspond to `securityScheme` name. The `securityHandlers` object signature is as follows: @@ -219,13 +257,7 @@ new OpenApiValidator(options).install({ See [examples](https://github.com/cdimascio/express-openapi-validator/blob/security/test/security.spec.ts#L17) from unit tests -**`coerceTypes:`** change data type of data to match type keyword. See the example in Coercing data types and coercion rules. Option values: - -- `true` - (default) coerce scalar data types. -- `false` - no type coercion. -- `"array"` - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema). -**`multerOpts:`** used to customize upload options. [multer opts](https://github.com/expressjs/multer) will passthrough to multer ## Example Express API Server diff --git a/src/index.ts b/src/index.ts index af28fd7f..c91a369c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -105,6 +105,16 @@ export class OpenApiValidator { private validateOptions(options: OpenApiValidatorOpts): void { if (!options.apiSpec) throw ono('apiSpec required.'); + const securityHandlers = options.securityHandlers; + if (securityHandlers != null) { + if ( + typeof securityHandlers !== 'object' || + Array.isArray(securityHandlers) + ) { + throw ono('securityHandlers must be an object or undefined'); + } + } + const unknownFormats = options.unknownFormats; if (typeof unknownFormats === 'boolean') { if (!unknownFormats) { diff --git a/src/middlewares/openapi.security.ts b/src/middlewares/openapi.security.ts index f6939585..2f05a33e 100644 --- a/src/middlewares/openapi.security.ts +++ b/src/middlewares/openapi.security.ts @@ -3,6 +3,12 @@ import { OpenAPIV3, OpenApiRequest } from '../framework/types'; import { validationError } from './util'; import { OpenApiContext } from '../framework/openapi.context'; +const defaultSecurityHandler = ( + req: Express.Request, + scopes: string[], + schema: OpenAPIV3.SecuritySchemeObject, +) => true; + interface SecurityHandlerResult { success: boolean; status: number; @@ -34,16 +40,7 @@ export function security( context.apiDoc.components && context.apiDoc.components.securitySchemes; if (!securitySchemes) { - const message = `security referenced at path ${path}, but not defined in components.securitySchemes`; - return next(validationError(500, path, message)); - } - - if (securities.length > 0 && !securityHandlers) { - const names = securities.reduce((a, i) => { - const p = Object.keys(i); - return a.concat(p); - }, []); - const message = `attempt to use securities ${names}, but no securityHandlers defined.`; + const message = `security referenced at path ${path}, but not defined in 'components.securitySchemes'`; return next(validationError(500, path, message)); } @@ -84,11 +81,18 @@ class SecuritySchemes { } executeHandlers(req: OpenApiRequest): Promise { + // use a fallback handler if security handlers is not specified + // This means if security handlers is specified, the user must define + // all security handlers + const fallbackHandler = this.securityHandlers + ? defaultSecurityHandler + : null; + const promises = this.securities.map(async s => { try { const securityKey = Object.keys(s)[0]; const scheme: any = this.securitySchemes[securityKey]; - const handler = this.securityHandlers[securityKey]; + const handler = this.securityHandlers[securityKey] || fallbackHandler; const scopesTmp = s[securityKey]; const scopes = Array.isArray(scopesTmp) ? scopesTmp : []; diff --git a/test/security.spec.ts b/test/security.spec.ts index e9a4032a..257edece 100644 --- a/test/security.spec.ts +++ b/test/security.spec.ts @@ -287,7 +287,7 @@ describe(packageJson.name, () => { .expect(200); }); - it('should return 500 if missing handler', async () => { + it('should return 200 and use default security handler, if security handler is not specified', async () => { delete (eovConf.securityHandlers).OpenID; (eovConf.securityHandlers).Test = function(req, scopes, schema) { expect(schema.type).to.equal('openIdConnect'); @@ -300,13 +300,6 @@ describe(packageJson.name, () => { }; return request(app) .get(`${basePath}/openid`) - .expect(500) - .then(r => { - const body = r.body; - const msg = "a handler for 'OpenID' does not exist"; - expect(body.message).to.equal(msg); - expect(body.errors[0].message).to.equal(msg); - expect(body.errors[0].path).to.equal(`${basePath}/openid`); - }); + .expect(200); }); });