Skip to content

Latest commit

 

History

History
559 lines (449 loc) · 24.1 KB

README.md

File metadata and controls

559 lines (449 loc) · 24.1 KB

OAS TOOLS

NPM

npm node-current npm Node.js CI Conventional Commits

Known Vulnerabilities Coverage Status

Setup

This module supports the management of RESTfull APIs defined with OpenAPI 3.0 Specs over express servers.

If you are creating an API from scratch we recommend you to take a look at oas-generator

We have a 3 min. tutorial:

oas-tools introduction (v2.0.3)- Click to Watch!

1. Install oas-tools

npm install oas-tools --save

2. Use oas-tools

Require the installed module in your app's index file, just like:

var oasTools = require('oas-tools');

The module oas-tools requires the specification file of the NodeJS application, therefore load it as follow using fs and jsyaml:

var jsyaml = require('js-yaml');
var fs = require('fs');

var spec = fs.readFileSync(path.join('path/to/spec/file'), 'utf8');
var oasDoc = jsyaml.safeLoad(spec);

[Note: you might want to use the module ‘path’ for loading the specification file, that way your app will work on any OS]

It is also possible to set configuration variables, these are them:

Name Type Explanation / Values
loglevel String Possible values from less to more level of verbosity are: error, warning, custom, info and debug. Ignored if customLogger is used. Default is info.
logfile String Logs file path. Ignored if customLogger is used.
customLogger Object Replaces the included logger with the one specified here, so that you can reuse your own logger. logLevel and logFile will be ignored if this variable is used. Null by default.
customErrorHandling Boolean Indicates if there should be a direct response (false) or next() should be called for custom error handling
controllers String Controllers location path.
checkControllers Boolean Checks if controllers exist for all specified methods. True by default.
strict Boolean Indicates whether validation must stop the request process if errors were found when validating according to specification file. false by default.
router Boolean Indicates whether router middleware should be used. True by default.
validator Boolean Indicates whether validator middleware should be used. True by default.
docs Object Settings used for Swagger UI, detailed in the following four rows. If you do not want to use the default values, you must specify values for all four settings, even if you only want to change one of them.
docs.apiDocs String Indicates the path where the OAS specification will be available. /api-docs by default.
docs.apiDocsPrefix String Indicates a prefix to be prepended to docs.apiDocs. Empty by default.
docs.swaggerUi String Indicates the path where Swagger UI will be available. Ignored if docs.apiDocs is not set. /docs by default.
docs.swaggerUiPrefix String Indicates a prefix to be prepended to docs.swaggerUi. Empty by default.
oasSecurity Boolean Indicates whether security components defined in the spec file will be handled based on securityFile settings. securityFile will be ignored if this is set to false. Refer to oasSecurity for more information. False by default.
securityFile Object Defines the settings that will be used to handle security. Ignored if oasSecurity is set to false. Null by default.
oasAuth Boolean Indicates whether authorization will be automatically handled based on grantsFile settings. grantsFile will be ignored if this is set to false. Refer to oasAuth for more information. False by default.
grantsFile Object Defines the settings that will be use to handle automatic authorization. Ignored if oasAuth is set to false. Null by default.
ignoreUnknownFormats Boolean Indicates whether z-schema validator must ignore unknown formats when validating requests and responses. True by default.

For setting these variables you can use the function configure and pass to it either a JavaScript object or a yaml/json file containing such object.

var options_object = {
  controllers: '/path/to/controllers',
  checkControllers: true,
  loglevel: 'info',
  logfile: '/path/to/logs/file',
  // customLogger: myLogger,
  strict: false,
  router: true,
  validator: true,
  docs: {
    apiDocs: '/api-docs',
    apiDocsPrefix: '',
    swaggerUi: '/docs',
    swaggerUiPrefix: ''
  }
  oasSecurity: true,
  securityFile: {
    // your security settings
  },
  oasAuth: true,
  grantsFile: {
    // your authorization settings
  },
  ignoreUnknownFormats: true
};

oasTools.configure(options_object);

[Note: it is not mandatory to set all configuration variables when using this functions, oas-tools will set the other ones by default]

Finally for initializing the middlewares right before your app is up and running, the function ‘initialize’ must be used. It receives the specification file, the express server object and a callback function where you must start your server:

oasTools.initialize(oasDoc, app, function() {
  http.createServer(app).listen(8080, function() {
    console.log("App up and running!");
  });
});

1. Migrate from swagger-tools to oas-tools

Oas-tools works with Express while swagger-tools works with Connect, therefore in order to use your swagger-codegen generated NodeJS server with oas-tools you have to leave connect behind and use express and an openapi version 3 file. This can be easily achievable by following just 4 simple steps:

1.Change from connect to express:

Oas-tools needs express module to work, connect doesn’t have the needed features. Therefore you must install express:

npm install express --save

Then change the require on your app’s index file, get rid of connect!

var express = require('express');
var app = express();

2.Require oas-tools instead of swagger-tools:

Change from swagger-tools to oas-tools by just installing it and modifying the require:

npm install oas-tools --save
var swaggerTools = require('oas-tools');

3. Convert your specification file from version 2 to version 3:

As you may know oas-tools works only with the version 3 of the specification, therefore you should update you specification file. Use this tool to do so. Once you get the translation simply copy-past it to you former specification file.

[Note: Minor adjustment on the translated specification may be required. Validate it using swagger validator

4. Provide express server object to initialize method:

Finally give the server object to oas-tools. It needs it to manage routes the right way. Just place it as a second parameter for the initializeMiddleware function.

swaggerTools.initializeMiddleware(swaggerDoc, app, function(middleware) {
  // Configuration of usage of swagger middlewares with app.use()
  ...

  // Start the server
  ...
});

5. Add name property to request bodies:

OpenAPI Specification version 3 defines request's body in a different way, it is not a parameter as it is in Swagger version 2. Now requests bodies are defined in a section 'requestBody' which doesn't have name property, therefore it needs this property to work with your swagger-codegen generated controllers. Simply add to each requestBody secction the property 'x-name:' and the name of the resource. Check out this example:

post:
  summary: Create a pet
  operationId: createPets
  tags:
    - pets
  requestBody:
    description: Pet to add to the store
    x-name: pet
    required: true
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Pet'

6. (optional) use multer for binary uploads:

The default middleware for handling file uploads in express via multipart/form-data is multer. Use it in addition to the json body parser when initializing the server:

const bodyParser = require('body-parser');
const multer = require('multer');
const upload = multer();

const app = express();
app.use(bodyParser.json({
  strict: false
}));
app.use(upload.any());

Also make sure that the application consuming the openAPI server sends the file input form name according to the openAPI spec:

/endPoint:
    post:
      requestBody:
        required: true
        x-name: endPointPost
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
                - id
                - name
              properties:
                file: # <- this is the form field name that needs to be present in the POST payload
                  type: string
                  format: binary
                  description: a binary file uploaded via openAPI spec
                id:
                  type: string # it's a form field, so will always be a string
                name:
                  type: string

Once you have done all this, leave the rest the way it is and just run your appliaction with ‘node index.js’ or any other command you have specified at your package.json for running the application.

Known limitations and differences with swagger-tools:

Thanks to community contributions, we are aware of some limitations and differences that oas-tools currently has:

  • The res.json function from Express is not working properly (#71).
  • Multiple APIs per Node.js instance do not work (#115).
  • Response validation is done inside the router middleware (#119).
  • Middlewares returned by the initializeMiddleware function are now no-ops, and our middlewares are initialized beforehand.

If you find any other difference, please create a new issue so that we are aware and can update this list. We will try to minimize the number of differences so that the migration from swagger-tools to oas-tools is as smooth as possible.

2. oasSecurity

The configuration variables oasSecurity and securityFile allow the use of handlers to manage authentication. This works similarly to the swagger-security middleware found in swagger-tools. In fact, most of our code is reused from that same middleware. We have only adapted it to work with OAS 3.0 and made some changes to allow automatic validation of JWTs (more on that later).

To start using oasSecurity, you should include some security schemes in your specification file. For example, to define a scheme named Bearer that will use JWTs:

components:
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT

Then you need to bind the different schemes to your endpoints. You can bind a scheme to the whole API, or to specific paths and methods. Refer to the previous link for more information.

Now you need to define the handlers that will take care of the validation of the requests made to your API. To do this, simply create a function that takes the express req object, the security scheme, the value that will be validated, and a callback as inputs. Inside this function, perform all the checks and validations that you need, and finally call the callback function. Following the previous example, if we want to validate a JWT signed with the key 'secretKey' and check that the issuer is 'ISA Auth', we would define the following function:

var jwt = require('jsonwebtoken');

function verifyToken(req, secDef, token, next) {
  const bearerRegex = /^Bearer\s/;
  
  if (token && bearerRegex.test(token)) {
    var newToken = token.replace(bearerRegex, '');
    jwt.verify(newToken, 'secretKey',
      {
        issuer: 'ISA Auth'
      },
      (error, decoded) => {
        if (error === null && decoded) {
          return next();
        }
        return next(req.res.sendStatus(403));
      }
    );
  } else {
    return next(req.res.sendStatus(403));
  }
}

Finally, you must link the defined functions to their corresponding security schemes. In order to do that, you need to pass an object to the securityFile configuration variable containing these relationships. For example, to specify that our verifyToken function should be used to handle our previously defined Bearer scheme:

oasTools.configure({
  // other configuration variables
  oasSecurity: true,
  securityFile: {
    Bearer: verifyToken
  }
});

After following these steps, your validating function will be executed each time a request to any of the endpoints you applied the corresponding security scheme to is made. Please note that any schemes without a linked function will be ignored, except for the ones based on JWTs where configuration is included in the specification file (explained later).

Moreover, since JWT (JSON Web Token) validations are almost always the same, oas-tools can do them automatically, that is, you just need to specify some simple parameters instead of a whole function. However, only the issuer, the expiration date and the key are validated, so if you want to check something else, you will need to create a function.

To automatically validate a JWT, first ensure that your security scheme defines that its type is 'http', its scheme is 'bearer' and its bearerFormat is 'JWT'. Then, simply specify the issuer, the supported algorithms (optional, defaults to only HS256) and the key in the securityFile configuration variable. In our previous example, this would be:

oasTools.configure({
  // other configuration variables
  oasSecurity: true,
  securityFile: {
    Bearer: {
      issuer: 'ISA Auth',
      algorithms: ['HS256'],
      key: 'secretKey'
  }
});

You can also pass a file path (absolute or relative) or a URL containing the JSON representation of an object containing these parameters. For example, we have this grants.json file:

{
  "issuer": "ISA Auth",
  "algorithms": ["HS256"],
  "key": "secretKey"
}

Our corresponding configuration would be as follows:

oasTools.configure({
  // other configuration variables
  oasSecurity: true,
  securityFile: {
    Bearer: 'path/to/grants.json'
  }
});

You can also include these parameters directly in your specification file. Simply add an additional attribute in your security scheme definition called 'x-bearer-config' containing the parameters to be used during validation. For example:

components:
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      x-bearer-config:
        issuer: ISA Auth
        algorithms:
          - HS256
        key: secretKey

Similarly to the securityFile variable, you could specify a path or a URL with a JSON representation of an object with these parameters instead. Remember that even if 'x-bearer-config' is defined in a security scheme, it will be ignored if the oasSecurity variable is set to false. Moreover, if a security scheme has been configured in the securityFile variable, that configuration will take preference over the one included in 'x-bearer-config'.

3. oasAuth

When using JWTs in your API, oas-tools also allows you to automatically manage authorization to a certain degree. This is achieved through simple configuration files, and using the modules accesscontrol and accesscontrol-middleware.

First, create a security scheme which uses JWTs in your specification file, ensuring that the type is 'http', the scheme is 'bearer' and the bearerFormat is 'JWT':

components:
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT

Then, you need to create an object containing the access restrictions. This object is based on the one used in the module 'accesscontrol', so refer to its GitHub page if you need more information. We will create an example and further explain what it defines below:

var grants = {
  user: {
    "$extend": ["anonymous"],
    "pets/cats": {
      "update:own": ["*"]
    },
    "users": {
      "read:any": ["*"]
    }
  },
  anonymous: {
    "pets/cats": {
      "read:any": ["*"]
    }
  }
};

In this object, we have defined two roles: 'user' and 'anonymous'. 'anonymous' can read any cat in the system. 'user' extends 'anonymous', so it inherits the same access restrictions from its parent. Moreover, a 'user' can update its own cats, and it can also read any user in the system. Now, we will take a closer look at the syntax to see what everything means:

  • The '$extend' attribute is a list, that means that a role can extend multiple roles.
  • 'pets/cats' means that the defined restriction will be applied to every endpoint that includes 'pets' and 'cats' in its path, in that same order. For example, 'api/pets/{petstoreId}/cats/{catId}' or simply 'pets/cats' will count.
  • 'update' means that the restriction applies to PUT requests. Similarly, you can use 'read' for GET requests, 'create' for POST, and 'delete' for DELETE.
  • 'own' specifies that the role can only access one cat, based on a parameter that we will explain later. 'any' means that the role can access any cat in the whole API.
  • '["*"]' means that the role can access every resource attribute. oas-tools does not use this, but since you can reuse this restrictions in your controllers along with the 'accesscontrol' module, it may be interesting to take it into account. For example, '["id", "breed"]' would mean that the role can only access these two attributes from a cat.

To use this grants object in your API, simply provide it while configuring oas-tools, linking it to a JWT scheme definition:

oasTools.configure({
  // other configuration variables
  oasAuth: true,
  grantsFile: {
    Bearer: grants
  }
});

You can also pass a file path (absolute or relative) or a URL containing a JSON representation of your access restrictions. Additionally, you can specify the restrictions directly in the security scheme definition in your specification file, including the 'x-acl-config' attribute:

components:
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      x-acl-config:
        anonymous:
          pets:
            "read:any":
              - "*"

You can use a file path or a URL here too. Please note that even if you define 'x-acl-config' here, it will be ignored if the oasAuth configuration variable is set to false. Additionally, if a scheme was already linked to a grants object in the grantsFile variable, that configuration will take precedence over the one in 'x-acl-config'.

The role of a client will be taken from the provided JWT. This JWT should contain an attribute called 'role'. If this attribute is not defined, oas-tools will assume the default role of 'anonymous'. Moreover, the parameters used to check ownership should also be present in the JWT, and must be named in a specific way. For example, if a request is made to 'api/pets/{petstoreId}/cats/{catId}' and you want to check 'catId' for ownership, there must be a 'catId' attribute in the JWT. If it is not provided and no 'any' restriction was defined, the client will not have access to this resource. However, if you want to match the path 'catId' parameter to another attribute from the JWT, you can do that in the specification file adding a 'x-acl-binding' attribute to the corresponding parameter:

paths:
  api/pets/{petstoreId}/cats/{catId}:
    put:
      parameters:
        - name: catId
          in: path
          x-acl-binding: customAttribute
    # ...

This example will look for a 'customAttribute' attribute in the JWT.

4. Errors

Since oas-tools reports validation errors with a common structure, we provide a JSON Schema so that you can validate this errors accordingly in your API implementation. You can use speccy to convert this JSON Schema into an OpenAPI file.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "array",
  "items": {
    "type": "object",
    "required": [
      "message"
    ],
    "properties": {
      "message": {
        "type": "string",
        "examples": [
          "Wrong data in the response."
        ]
      },
      "error": {
        "type": "array",
        "items": {
          "type": "object",
          "required": [
            "code",
            "params",
            "message",
            "path"
          ],
          "properties": {
            "code": {
              "type": "string",
              "examples": [
                "INVALID_TYPE"
              ]
            },
            "params": {
              "type": "array",
              "items": {
                "type": "string",
                "examples": [
                  "array",
                  "object"
                ]
              }
            },
            "message": {
              "type": "string",
              "examples": [
                "Expected type array but found type object"
              ]
            },
            "path": {
              "type": "string",
              "examples": [
                "#/"
              ]
            }
          }
        }
      },
      "content": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "examples": [
              "This is the mockup controller for findPets"
            ]
          }
        }
      }
    }
  }
}

By default, the errors are returned by a direct HTTP 400 response. If you need custom error handling, you may set the customErrorHandling config property to true, which causes validation errors to be passed to next(). The following properties are set:

Name Type Explanation / Values
failedValidation Boolean Property can be used to check if the error comes from the oas validation.
validationResult Object Validation results conforming to the beforementioned schema.

Contributing

We are open to issues and pull requests. If you want to contribute to this project, please check our recommended guidelines.

License

Copyright 2018, ISA Group, University of Sevilla

For technical inquiry please contact to engineering team.

ISA Group

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.