Skip to content

Commit

Permalink
use default security handlersi when security handlers not undefined
Browse files Browse the repository at this point in the history
  • Loading branch information
Carmine DiMascio committed Oct 13, 2019
1 parent f9a8b0e commit eec4cba
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 40 deletions.
72 changes: 52 additions & 20 deletions README.md
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/index.ts
Expand Up @@ -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) {
Expand Down
26 changes: 15 additions & 11 deletions src/middlewares/openapi.security.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -84,11 +81,18 @@ class SecuritySchemes {
}

executeHandlers(req: OpenApiRequest): Promise<SecurityHandlerResult[]> {
// 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 : [];

Expand Down
11 changes: 2 additions & 9 deletions test/security.spec.ts
Expand Up @@ -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 (<any>eovConf.securityHandlers).OpenID;
(<any>eovConf.securityHandlers).Test = <any>function(req, scopes, schema) {
expect(schema.type).to.equal('openIdConnect');
Expand All @@ -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);
});
});

0 comments on commit eec4cba

Please sign in to comment.