Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

express-openapi-validator

Package Overview
Dependencies
Maintainers
1
Versions
281
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

express-openapi-validator - npm Package Compare versions

Comparing version 4.11.0-beta.2 to 4.11.0

dist/framework/base.serdes.d.ts

49

dist/framework/ajv/index.js

@@ -22,2 +22,20 @@ "use strict";

if (request) {
if (options.serDesMap) {
ajv.addKeyword('x-eov-serdes', {
modifying: true,
compile: (sch) => {
if (sch) {
return function validate(data, path, obj, propName) {
if (typeof data === 'object')
return true;
if (!!sch.deserialize) {
obj[propName] = sch.deserialize(data);
}
return true;
};
}
return () => true;
},
});
}
ajv.removeKeyword('readOnly');

@@ -49,17 +67,20 @@ ajv.addKeyword('readOnly', {

// response
ajv.addKeyword('x-eov-serializer', {
modifying: true,
compile: (sch) => {
if (sch) {
const isDate = ['date', 'date-time'].includes(sch.format);
return function validate(data, path, obj, propName) {
if (typeof data === 'string' && isDate)
if (options.serDesMap) {
ajv.addKeyword('x-eov-serdes', {
modifying: true,
compile: (sch) => {
if (sch) {
return function validate(data, path, obj, propName) {
if (typeof data === 'string')
return true;
if (!!sch.serialize) {
obj[propName] = sch.serialize(data);
}
return true;
obj[propName] = sch.serialize(data);
return true;
};
}
return () => true;
},
});
};
}
return () => true;
},
});
}
ajv.removeKeyword('writeOnly');

@@ -66,0 +87,0 @@ ajv.addKeyword('writeOnly', {

@@ -22,7 +22,7 @@ "use strict";

}, new Set()));
const validateApiDoc = 'validateApiDoc' in args ? !!args.validateApiDoc : true;
const validateApiSpec = 'validateApiSpec' in args ? !!args.validateApiSpec : true;
const validator = new openapi_schema_validator_1.OpenAPISchemaValidator({
version: apiDoc.openapi,
});
if (validateApiDoc) {
if (validateApiSpec) {
const apiDocValidation = validator.validate(apiDoc);

@@ -56,15 +56,8 @@ if (apiDocValidation.errors.length) {

const origCwd = process.cwd();
const specDir = path.resolve(origCwd, path.dirname(filePath));
const absolutePath = path.resolve(origCwd, filePath);
if (fs.existsSync(absolutePath)) {
// Get document, or throw exception on error
try {
process.chdir(specDir);
return $refParser.mode === 'dereference'
? $RefParser.dereference(absolutePath)
: $RefParser.bundle(absolutePath);
}
finally {
process.chdir(origCwd);
}
return $refParser.mode === 'dereference'
? $RefParser.dereference(absolutePath)
: $RefParser.bundle(absolutePath);
}

@@ -71,0 +64,0 @@ else {

@@ -31,3 +31,3 @@ /* istanbul ignore file */

return res;
if (!mungError && res.statusCode >= 400)
if (!mungError && res.statusCode >= 500)
return original.call(this, json);

@@ -34,0 +34,0 @@ // Run the munger

@@ -27,3 +27,3 @@ import * as ajv from 'ajv';

export interface Options extends ajv.Options {
schemaObjectMapper?: object;
serDesMap?: SerDesMap;
}

@@ -35,5 +35,6 @@ export interface RequestValidatorOptions extends Options, ValidateRequestOpts {

coerceTypes?: boolean | 'array';
removeAdditional?: boolean | 'all' | 'failing';
};
export declare type ValidateResponseOpts = {
removeAdditional?: 'failing' | boolean;
removeAdditional?: boolean | 'all' | 'failing';
coerceTypes?: boolean | 'array';

@@ -54,8 +55,25 @@ onError?: (err: InternalServerError, json: any) => void;

};
export declare type Serializer = {
export declare type SerDes = {
format: string;
serialize: (o: unknown) => string;
serialize?: (o: unknown) => string;
deserialize?: (s: string) => unknown;
};
export declare class SerDesSingleton implements SerDes {
serializer: SerDes;
deserializer: SerDes;
format: string;
serialize?: (o: unknown) => string;
deserialize?: (s: string) => unknown;
constructor(param: {
format: string;
serialize: (o: unknown) => string;
deserialize: (s: string) => unknown;
});
}
export declare type SerDesMap = {
[format: string]: SerDes;
};
export interface OpenApiValidatorOpts {
apiSpec: OpenAPIV3.Document | string;
validateApiSpec?: boolean;
validateResponses?: boolean | ValidateResponseOpts;

@@ -68,2 +86,3 @@ validateRequests?: boolean | ValidateRequestOpts;

unknownFormats?: true | string[] | 'ignore';
serDes?: SerDes[];
formats?: Format[];

@@ -400,3 +419,3 @@ fileUploader?: boolean | multer.Options;

apiDoc: OpenAPIV3.Document | string;
validateApiDoc?: boolean;
validateApiSpec?: boolean;
$refParser?: {

@@ -422,3 +441,3 @@ mode: 'bundle' | 'dereference';

export interface OpenApiRequest extends Request {
openapi?: OpenApiRequestMetadata | {};
openapi?: OpenApiRequestMetadata;
}

@@ -425,0 +444,0 @@ export declare type OpenApiRequestHandler = (req: OpenApiRequest, res: Response, next: NextFunction) => any;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Forbidden = exports.Unauthorized = exports.UnsupportedMediaType = exports.InternalServerError = exports.RequestEntityTooLarge = exports.BadRequest = exports.MethodNotAllowed = exports.NotAcceptable = exports.NotFound = exports.HttpError = void 0;
exports.Forbidden = exports.Unauthorized = exports.UnsupportedMediaType = exports.InternalServerError = exports.RequestEntityTooLarge = exports.BadRequest = exports.MethodNotAllowed = exports.NotAcceptable = exports.NotFound = exports.HttpError = exports.SerDesSingleton = void 0;
class SerDesSingleton {
constructor(param) {
this.format = param.format;
this.serialize = param.serialize;
this.deserialize = param.deserialize;
this.deserializer = {
format: param.format,
deserialize: param.deserialize
};
this.serializer = {
format: param.format,
serialize: param.serialize
};
}
}
exports.SerDesSingleton = SerDesSingleton;
;
class HttpError extends Error {

@@ -5,0 +22,0 @@ constructor(err) {

@@ -17,3 +17,3 @@ import * as res from './resolvers';

};
export * as serdes from './framework/base.serdes';
declare function openapiValidator(options: OpenApiValidatorOpts): import("./framework/types").OpenApiRequestHandler[];
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.error = exports.middleware = exports.resolvers = void 0;
exports.serdes = exports.error = exports.middleware = exports.resolvers = void 0;
const cloneDeep = require("lodash.clonedeep");
const res = require("./resolvers");

@@ -22,2 +23,3 @@ const openapi_validator_1 = require("./openapi.validator");

};
exports.serdes = require("./framework/base.serdes");
function openapiValidator(options) {

@@ -27,3 +29,4 @@ const oav = new openapi_validator_1.OpenApiValidator(options);

return oav.installMiddleware(new openapi_spec_loader_1.OpenApiSpecLoader({
apiDoc: options.apiSpec,
apiDoc: cloneDeep(options.apiSpec),
validateApiSpec: options.validateApiSpec,
$refParser: options.$refParser,

@@ -30,0 +33,0 @@ }).load());

@@ -6,2 +6,3 @@ "use strict";

const path_to_regexp_1 = require("path-to-regexp");
const types_1 = require("../framework/types");
function applyOpenApiMetadata(openApiContext, responseApiDoc) {

@@ -19,2 +20,8 @@ return (req, res, next) => {

const { expressRoute, openApiRoute, pathParams, schema } = matched;
if (!schema) {
throw new types_1.MethodNotAllowed({
path: req.path,
message: `${req.method} method not allowed`,
});
}
req.openapi = {

@@ -33,3 +40,6 @@ expressRoute: expressRoute,

else if (openApiContext.isManagedRoute(path)) {
req.openapi = {};
throw new types_1.NotFound({
path: req.path,
message: 'not found',
});
}

@@ -36,0 +46,0 @@ next();

@@ -31,15 +31,3 @@ "use strict";

const path = openapi.expressRoute;
if (!path) {
throw new types_1.NotFound({
path: req.path,
message: 'not found',
});
}
const reqSchema = openapi.schema;
if (!reqSchema) {
throw new types_1.MethodNotAllowed({
path: req.path,
message: `${req.method} method not allowed`,
});
}
// cache middleware by combining method, path, and contentType

@@ -46,0 +34,0 @@ const contentType = util_1.ContentType.from(req);

@@ -17,18 +17,2 @@ "use strict";

const openapi = req.openapi;
const expressRoute = openapi.expressRoute;
if (!expressRoute) {
return next(new types_1.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 types_1.MethodNotAllowed({
path: req.path,
message: `${req.method} method not allowed`,
}));
}
// use the local security object or fallbac to api doc's security or undefined

@@ -52,3 +36,3 @@ const securities = (_d = openapi.schema.security) !== null && _d !== void 0 ? _d : apiDoc.security;

function checkErrorWithOr(res) {
return res.forEach(r => {
return res.forEach((r) => {
if (r.success) {

@@ -64,3 +48,3 @@ success = true;

let allSuccess = false;
res.forEach(r => {
res.forEach((r) => {
if (!r.success) {

@@ -80,3 +64,3 @@ allSuccess = false;

}
results.forEach(result => {
results.forEach((result) => {
if (Array.isArray(result) && result.length > 1) {

@@ -136,3 +120,8 @@ checkErrorsWithAnd(result);

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 handler = (_b =
(_a = this.securityHandlers) === null || _a === void 0
? void 0
: _a[securityKey]) !== null && _b !== void 0
? _b
: fallbackHandler;
const scopesTmp = s[securityKey];

@@ -139,0 +128,0 @@ const scopes = Array.isArray(scopesTmp) ? scopesTmp : [];

@@ -1,3 +0,2 @@

import ajv = require('ajv');
import { OpenAPIV3, ValidateResponseOpts } from '../../framework/types';
import { OpenAPIV3, Options, ValidateResponseOpts } from '../../framework/types';
export declare class SchemaPreprocessor {

@@ -7,4 +6,5 @@ private ajv;

private apiDocRes;
private serDesMap;
private responseOpts;
constructor(apiDoc: OpenAPIV3.Document, ajvOptions: ajv.Options, validateResponsesOpts: ValidateResponseOpts);
constructor(apiDoc: OpenAPIV3.Document, ajvOptions: Options, validateResponsesOpts: ValidateResponseOpts);
preProcess(): {

@@ -11,0 +11,0 @@ apiDoc: OpenAPIV3.Document;

@@ -19,14 +19,2 @@ "use strict";

}
const dateTime = {
format: 'date-time',
serialize: (d) => {
return d && d.toISOString();
},
};
const date = {
format: 'date',
serialize: (d) => {
return d && d.toISOString().split('T')[0];
},
};
if (!Array.prototype['flatMap']) {

@@ -53,2 +41,3 @@ // polyfill flatMap

this.apiDoc = apiDoc;
this.serDesMap = ajvOptions.serDesMap;
this.responseOpts = validateResponsesOpts;

@@ -59,3 +48,3 @@ }

const r = this.gatherSchemaNodesFromPaths();
// Now that we've processed paths, clonse the spec
// Now that we've processed paths, clone a response spec if we are validating responses
this.apiDocRes = !!this.responseOpts ? cloneDeep(this.apiDoc) : null;

@@ -117,16 +106,20 @@ const schemaNodes = {

traverseSchemas(nodes, visit) {
const seen = new Set();
const recurse = (parent, node, opts) => {
const schema = this.resolveSchema(node.schema);
if (node.schema._seen || !schema) {
// if we can't dereference a path within the schema, skip preprocessing
// TODO handle refs like below during preprocessing
// #/paths/~1subscription/get/requestBody/content/application~1json/schema/properties/subscription
const schema = node.schema;
if (!schema || seen.has(schema))
return;
seen.add(schema);
if (schema.$ref) {
const resolvedSchema = this.resolveSchema(schema);
const path = schema.$ref.split('/').slice(1);
opts.req.originalSchema = schema;
opts.res.originalSchema = schema;
visit(parent, node, opts);
recurse(node, new Node(schema, resolvedSchema, path), opts);
return;
}
node.schema._seen = true;
// Save the original schema so we can check if it was a $ref
opts.req.originalSchema = node.schema;
opts.res.originalSchema = node.schema;
// TODO mark visited, and skip visited
// TODO Visit api docs
opts.req.originalSchema = schema;
opts.res.originalSchema = schema;
visit(parent, node, opts);

@@ -151,4 +144,4 @@ if (schema.allOf) {

}
else if (node.schema.properties) {
Object.entries(node.schema.properties).forEach(([id, cschema]) => {
else if (schema.properties) {
Object.entries(schema.properties).forEach(([id, cschema]) => {
const path = [...node.path, 'properties', id];

@@ -190,3 +183,3 @@ const child = new Node(node, cschema, path);

options.path = node.path;
if (nschema) {
if (nschema) { // This null check should no longer be necessary
this.handleSerDes(pschema, nschema, options);

@@ -255,15 +248,5 @@ this.handleReadonly(pschema, nschema, options);

handleSerDes(parent, schema, state) {
if (state.kind === 'res') {
if (schema.type === 'string' && !!schema.format) {
switch (schema.format) {
case 'date-time':
schema.type = ['object', 'string'];
schema['x-eov-serializer'] = dateTime;
break;
case 'date':
schema.type = ['object', 'string'];
schema['x-eov-serializer'] = date;
break;
}
}
if (schema.type === 'string' && !!schema.format && this.serDesMap[schema.format]) {
schema.type = ['object', 'string'];
schema['x-eov-serdes'] = this.serDesMap[schema.format];
}

@@ -270,0 +253,0 @@ }

@@ -10,2 +10,3 @@ "use strict";

const resolvers_1 = require("./resolvers");
const base_serdes_1 = require("./framework/base.serdes");
const schema_preprocessor_1 = require("./middlewares/parsers/schema.preprocessor");

@@ -262,3 +263,23 @@ var types_1 = require("./framework/types");

normalizeOptions(options) {
// Modify the request
if (!options.serDes) {
options.serDes = base_serdes_1.defaultSerDes;
}
else {
if (!Array.isArray(options.unknownFormats)) {
options.unknownFormats = Array();
}
options.serDes.forEach(currentSerDes => {
if (options.unknownFormats.indexOf(currentSerDes.format) === -1) {
options.unknownFormats.push(currentSerDes.format);
}
});
base_serdes_1.defaultSerDes.forEach(currentDefaultSerDes => {
let defautSerDesOverride = options.serDes.find(currentOptionSerDes => {
return currentDefaultSerDes.format === currentOptionSerDes.format;
});
if (!defautSerDesOverride) {
options.serDes.push(currentDefaultSerDes);
}
});
}
}

@@ -288,5 +309,6 @@ isOperationHandlerOptions(value) {

get request() {
const { allowUnknownQueryParameters, coerceTypes } = (this.options.validateRequests);
const { allowUnknownQueryParameters, coerceTypes, removeAdditional } = (this.options.validateRequests);
return Object.assign(Object.assign({}, this.baseOptions()), { allowUnknownQueryParameters,
coerceTypes });
coerceTypes,
removeAdditional });
}

@@ -297,3 +319,17 @@ get multipart() {

baseOptions() {
const { coerceTypes, unknownFormats, validateFormats } = this.options;
const { coerceTypes, unknownFormats, validateFormats, serDes } = this.options;
const serDesMap = {};
for (const serDesObject of serDes) {
if (!serDesMap[serDesObject.format]) {
serDesMap[serDesObject.format] = serDesObject;
}
else {
if (serDesObject.serialize) {
serDesMap[serDesObject.format].serialize = serDesObject.serialize;
}
if (serDesObject.deserialize) {
serDesMap[serDesObject.format].deserialize = serDesObject.deserialize;
}
}
}
return {

@@ -313,2 +349,3 @@ nullable: true,

}, {}),
serDesMap: serDesMap,
};

@@ -315,0 +352,0 @@ }

{
"name": "express-openapi-validator",
"version": "4.11.0-beta.2",
"version": "4.11.0",
"description": "Automatically validate API requests and responses with OpenAPI 3 and Express.",

@@ -37,3 +37,3 @@ "main": "dist/index.js",

"content-type": "^1.0.4",
"json-schema-ref-parser": "^9.0.6",
"json-schema-ref-parser": "^9.0.7",
"lodash.clonedeep": "^4.5.0",

@@ -44,2 +44,3 @@ "lodash.get": "^4.4.2",

"media-typer": "^1.1.0",
"@types/multer": "^1.4.5",
"multer": "^1.4.2",

@@ -55,3 +56,2 @@ "ono": "^7.1.3",

"@types/morgan": "^1.9.1",
"@types/multer": "^1.4.4",
"@types/node": "^14.14.2",

@@ -58,0 +58,0 @@ "@types/supertest": "^2.0.10",

@@ -18,3 +18,3 @@ # 🦋 express-openapi-validator

- 👮 security validation / custom security functions
- 👽 3rd party / custom formats
- 👽 3rd party / custom formats / custom data serialization-deserialization
- 🧵 optionally auto-map OpenAPI endpoints to Express handler functions

@@ -26,2 +26,4 @@ - ✂️ **\$ref** support; split specs over multiple files

[Koa](https://github.com/cdimascio/express-openapi-validator/tree/lerna-fastify/packages/koa-openapi-validator) and [Fastify](https://github.com/cdimascio/express-openapi-validator/tree/lerna-fastify/packages/fastify-openapi-validator) now available! 🚀
## Install

@@ -114,3 +116,2 @@

validateResponses: true, // <-- to validate responses
// unknownFormats: ['my-format'] // <-- to provide custom formats
}),

@@ -482,2 +483,3 @@ );

validateResponses: true,
validateApiSpec: true,
validateSecurity: {

@@ -497,2 +499,11 @@ handlers: {

unknownFormats: ['phone-number', 'uuid'],
serDes: [{
OpenApiValidator.serdes.dateTime,
OpenApiValidator.serdes.date,
{
format: 'mongo-objectid',
deserialize: (s) => new ObjectID(s),
serialize: (o) => o.toString(),
},
}],
operationHandlers: false | 'operations/base/path' | { ... },

@@ -503,3 +514,3 @@ ignorePaths: /.*\/pets$/,

mode: 'bundle'
}
},
});

@@ -589,3 +600,20 @@ ```

```
**removeAdditional:**
Determines whether to keep or remove additional properties in request body or to fail validation if schema has `additionalProperties` set to `false`. For futher details, refer to [AJV documentation](https://ajv.js.org/docs/validation.html#removing-additional-properties)
- `false` (**default**) - not to remove additional properties
- `"all"` - all additional properties are removed, regardless of additionalProperties keyword in schema (and no validation is made for them).
- `true` - only additional properties with additionalProperties keyword equal to false are removed.
- `"failing"` - additional properties that fail request schema validation will be removed (where additionalProperties keyword is false or schema).
For example:
```javascript
validateRequests: {
removeAdditional: true;
}
```
### ▪️ validateResponses (optional)

@@ -655,2 +683,10 @@

### ▪️ validateApiSpec (optional)
Determines whether the validator should validate the OpenAPI specification. Useful if you are certain that the api spec is syntactically correct and want to bypass this check.
- `true` (**default**) - validate the OpenAPI specification.
- `false` - do not validate the OpenAPI specification.
### ▪️ formats (optional)

@@ -714,2 +750,45 @@

### ▪️ serDes (optional)
Defines custom serialization and deserialization behavior for schemas of type `string` that declare a `format`. By default, `Date` objects are serialized as `string` when a schema's `type` is `string` and `format` is `date` or `date-time`.
e.g.
```javascript
// If `serDes` is not specified, the following behavior is default
serDes: [{
OpenApiValidator.serdes.dateTime.serializer,
OpenApiValidator.serdes.date.serializer,
}],
```
To create custom serializers and/or deserializers, define:
- `format` (required) - a custom 'unknown' format that triggers the serializer and/or deserializer
- `deserialize` (optional) - upon receiving a request, transform a string property to an object. Deserialization occurs _after_ request schema validation.
- `serialize` (optional) - before sending a response, transform an object to string. Serialization occurs _after_ response schema validation
e.g.
```javascript
serDes: [{
// installs dateTime serializer and deserializer
OpenApiValidator.serdes.dateTime,
// installs date serializer and deserializer
OpenApiValidator.serdes.date,
// custom serializer and deserializer for the custom format, mongo-objectid
{
format: 'mongo-objectid',
deserialize: (s) => new ObjectID(s),
serialize: (o) => o.toString(),
}
}],
```
The mongo serializers will trigger on the following schema:
```yaml
type: string
format: mongo-objectid
```
See [mongo-serdes-js](https://github.com/pilerou/mongo-serdes-js) for additional (de)serializers including MongoDB `ObjectID`, `UUID`, ...
### ▪️ operationHandlers (optional)

@@ -969,3 +1048,3 @@

See [OpenAPI 3](https://swagger.io/docs/specification/authentication/) authentication for `securityScheme` and `security` documentation
See [examples](https://github.com/cdimascio/express-openapi-validator/blob/security/test/security.spec.ts#L17) from unit tests
See [examples](https://github.com/cdimascio/express-openapi-validator/blob/master/test/security.handlers.spec.ts#L21) from unit tests

@@ -1147,2 +1226,8 @@ ## Example: Multiple Validators and API specs

## Related Projects
- [koa-openapi-validator](https://github.com/cdimascio/express-openapi-validator/tree/lerna-fastify/packages/koa-openapi-validator)
- [fastify-openapi-validator](https://github.com/cdimascio/express-openapi-validator/tree/lerna-fastify/packages/fastify-openapi-validator)
_Note: koa and fastify does not (yet) support response validation or operation handlers
## Contributors ✨

@@ -1149,0 +1234,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc