• Stars
    star
    431
  • Rank 100,866 (Top 2 %)
  • Language
    JavaScript
  • License
    Other
  • Created almost 11 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Generate Node.JS API middleware from a RAML definition

Osprey

NPM version NPM Downloads Build status Test coverage Greenkeeper badge

Generate API middleware from a RAML definition, which can be used locally or globally for validating API requests and responses.

Features

  • Automatic Request Validations
    • Bodies
      • Form data
      • Url Encoded bodies
      • JSON schemas
      • XML schemas
    • Headers
    • Query parameters
    • RAML 1.0 types
  • Automatic Request Parameters
    • Default Headers
    • Default Parameters
  • RAML Router
  • Integrates with Express-format middleware servers
    • Simple req/res/next middleware format that works with Connect, Express and even http
  • API documentation Currently disabled
    • Optionally mount API documentation generated from your RAML definition
  • Built-in Error Handling Middleware
    • I18n support
    • Map validation paths to readable strings (with i18n support)
  • Built-in Response Handling Coming soon
    • Validate response bodies against status code definition
    • Automatically fill default response headers
  • Authentication
    • OAuth 1.0 Coming Soon
    • OAuth 2.0
    • Basic Authentication
    • Digest Authentication
    • Custom Security Schemes
  • RAML Mock Service

Osprey is built to enforce a documentation-first approach to APIs. It achieves this by:

Server

  1. 404ing on undocumented resources
  2. Rejecting invalid requests bodies, headers and query parameters
  3. Populating default headers and query parameters
  4. Filtering undocumented headers and query parameters
  5. Validating API responses Coming soon
  6. Fill default response headers Coming soon

Security

  1. Setting up authentication endpoints and methods for you
  2. Authenticating endpoints as defined in RAML

Installation

Global

npm install osprey -g

Osprey can be used as a validation proxy with any other API server. Just install the module globally and use the CLI to set up the application endpoint(s) to proxy, as well as the RAML definition to use. Invalid API requests will be blocked before they reach your application server.

# Proxy to a running application (with optional documentation)
osprey -f api.raml -p 3000 -a localhost:8080

Options

  • -a Application endpoint address (can be fully qualified URLs) and specify multiple, comma-separated addresses
  • -f Path to the root RAML definition (E.g. /path/to/api.raml)
  • -p Port number to bind the proxy locally

Locally

npm install osprey --save

Usage

Osprey is normally used as a local node module and is compatible with any library supporting HTTP middleware, including Express and Connect. Just require the module locally and generate the middleware from a RAML definition file.

const osprey = require('osprey')
const express = require('express')
const join = require('path').join
const app = express()

const path = join(__dirname, 'assets', 'api.raml')

// Be careful, this uses all middleware functions by default. You might just
// want to use each one separately instead - `osprey.server`, etc.
osprey.loadFile(path)
  .then(function (middleware) {
    app.use(middleware)

    app.use(function (err, req, res, next) {
      // Handle errors.
    })

    app.listen(3000)
  })
   .catch(function(e) { console.error("Error: %s", e.message); });

Please note: The middleware function does not use the RAML baseUri. Make sure you mount the application under the correct path. E.g. app.use('/v1', middleware).

Server (Resource Handling)

const wap = require('webapi-parser').WebApiParser

// webapi-parser.WebApiDocument
const model = wap.raml10.parse('/some/api.raml')
const handler = osprey.server(model, options)

console.log(handler) //=> function (req, res, next) {}

console.log(handler.ramlUriParameters) //=> {} // A merged object of used URI parameters.

Undefined API requests will always be rejected with a 404.

Options

These are also passed along to osprey-method-handler).

  • cors Enable CORS by setting to true or an object from cors (default: false)
  • compression Enable response compression using compression (default: false)
  • notFoundHandler Use a 404 error in middleware to skip over invalid/undefined routes from RAML (default: true)

From Osprey Method Handler:

  • discardUnknownBodies Discard undefined request bodies (default: true)
  • discardUnknownQueryParameters Discard undefined query parameters (default: true)
  • discardUnknownHeaders Discard undefined header parameters (always includes known headers) (default: true)
  • parseBodiesOnWildcard Toggle parsing bodies on wildcard body support (default: false)
  • reviver The reviver passed to JSON.parse for JSON endpoints
  • limit The maximum bytes for XML, JSON and URL-encoded endpoints (default: '100kb')
  • parameterLimit The maximum number of URL-encoded parameters (default: 1000)
  • busboyLimits The limits for Busboy multipart form parsing

If you disable the default "not found" handler, it should be mounted later using osprey.server.notFoundHandler. For example, app.use(osprey.server.notFoundHandler).

Invalid Headers and Query Parameters

Invalid headers and query parameters are removed from the request. To read them they need to be documented in the RAML definition.

Request Bodies

Request bodies are parsed and validated for you, when you define the schema.

For application/json and application/x-www-form-urlencoded, the data will be an object under req.body. For text/xml, the body is stored as a string under req.body while the parsed XML document is under req.xml (uses LibXMLJS, not included). For multipart/form-data, you will need to attach field and file listeners to the request form (uses Busboy):

app.post('/users/{userId}', function (req, res, next) {
  req.form.on('field', function (name, value) {
    console.log(name + '=' + value)
  })

  req.form.on('file', function (name, stream, filename) {
    stream.pipe(fs.createWriteStream(join(os.tmpDir(), filename)))
  })

  req.form.on('error', next)

  req.pipe(req.form)
})

Headers, Parameters and Query Parameters

All parameters are automatically validated and parsed to the correct types according to the RAML document using webapi-parser and raml-sanitize. URL parameter validation comes with Osprey Router, available using osprey.Router.

// Similar to `express.Router`, but uses RAML paths.
const Router = require('osprey').Router
const utils = require('./utils')

// Array<webapi-parser.Parameter>
const parameters = utils.getUriParameters()

const app = new Router()

app.use(...)

app.get('/{slug}', parameters, function (req, res) {
  res.send('success')
})

module.exports = app

You can initialize a Router with ramlUriParameters. This is helpful, since every router collects an object with merged URI parameters. For example, you can combine it with the server middleware to generate a router with your RAML URI parameters:

const handler = osprey.server(model)
const router = osprey.Router({ ramlUriParameters: handler.ramlUriParameters })

// Uses an existing `userId` URI parameter, if it exists.
router.get('/{userId}', function (req, res, next) {})

Handling Errors

Osprey returns a middleware router instance, so you can mount this within any compatible application and handle errors with the framework. For example, using HTTP with finalhandler (the same module Express uses):

const http = require('http')
const osprey = require('osprey')
const finalhandler = require('finalhandler')
const join = require('path').join

osprey.loadFile(join(__dirname, 'api.raml'))
  .then(function (middleware) {
    http.createServer(function (req, res) {
      middleware(req, res, finalhandler(req, res))
    }).listen(process.env.PORT || 3000)
  })
   .catch(function(e) { console.error("Error: %s", e.message); });

Error Types

  • error.ramlAuthorization = true An unauthorized error containing an array of errors that occured is set on error.authorizationErrors
  • error.ramlValidation = true A request failed validation and an array of validation data is set on error.requestErrors (beware, different types contain different information)
  • error.ramlNotFound = true A request 404'd because it was not specified in the RAML definition for the API

Add JSON Schemas

JSON schemas can be added to the application for when external JSON references are needed. From osprey-method-handler.

osprey.addJsonSchema(schema, key)

Error Handler

Osprey comes with support for a built-in error handler middleware that formats request errors for APIs. It comes with built-in i18n with some languages already included for certain formats (help us add more!). The default fallback language is en and the default responder renders JSON, XML, HTML and plain text - all options are overridable.

const osprey = require('osprey')
const app = require('express')()

// It's best to use the default responder, but it's overridable if you need it.
app.use(osprey.errorHandler(function (req, res, errors, stack) { /* Override */ }, 'en'))

You can override the i18n messages or provide your own by passing a nested object that conforms to the following interface:

interface CustomMessages {
  [type: string]: {
    [keyword: string]: {
      [language: string]: (error: RequestError) => string
    }
  }
}

The request error interface is as follows:

interface RequestError {
  type: 'json' | 'form' | 'headers' | 'query' | 'xml' | string
  message: string /* Merged with i18n when available */
  keyword: string /* Keyword that failed validation */
  id?: string /* A unique identifier for the instance of this error */
  dataPath?: string /* Natural path to the error message (E.g. JSON Pointers when using JSON) */
  data?: any /* The data that failed validation */
  schema?: any /* The schema value that failed validation */
  detail?: string /* Additional details about this specific error instance */
  meta?: { [name: string]: string } /* Meta data from the error (XML validation provides a code, column, etc.) */
}

Want to format your own request errors? If you emit an error with a .status property of "client error" (400 - 499) and an array of requestErrors, it will automatically be rendered as the API response (using status as the response status code).

Security

// model is an instance of webapi-parser WebApiDocument
osprey.security(model, options)

Osprey accepts an options object that maps object keys to the security scheme name in the RAML definition.

OAuth 2.0

Provided by OAuth2orize and Passport.

securitySchemes:
  - oauth_2_0:
      type: OAuth 2.0
      settings:
        authorizationUri: https://example.com/oauth/authorize
        accessTokenUri: https://example.com/oauth/token
        authorizationGrants: [ code, token, owner, credentials ]
        scopes:
          - profile
          - history
          - history_lite
          - request
          - request_receipt

OAuth 2.0 can be fairly tricky to enforce on your own. With Osprey, any endpoint with securedBy will automatically be enforced.

Required Options (by grant type)

  • All

    • authenticateClient
    • exchange.refresh When refresh tokens are used
  • Code and Token

    • serializeClient
    • deserializeClient
    • authorizeClient
    • sessionKeys
    • ensureLoggedIn Has access to req.session
    • serveAuthorizationPage Has access to req.session
  • Code

    • grant.code
    • exchange.code
  • Token

    • grant.token
  • Credentials

    • exchange.credentials
  • Owner

    • exchange.owner

The authorization page must submit a POST request to the same URL with the transaction_id and scope properties set (from req.oauth2). If the dialog was denied, submit cancel=true with the POST body. If you wish to enable the ability to skip the authorization page (E.g. user already authorized or first-class client), use the immediateAuthorization option.

// model is an instance of webapi-parser WebApiDocument
osprey.security(model, {
  oauth_2_0: {
    // Optionally override `accessTokenUri` and `authorizationUri` when needed.
    // They need to match the suffix defined in the security scheme.
    accessTokenUri: '/oauth/token',
    authorizationUri: '/oauth/authorize',
    // Serialize the client object into the session.
    serializeClient: function (application, done) {
      return done(null, application.id)
    },
    // Deserialize client objects out of the session.
    deserializeClient: function (id, done) {
      Client.findById(id, function (err, client) {
        done(err, client)
      })
    },
    authorizeClient: function (clientId, redirectUri, scope, type, done) {
      Clients.findOne(clientId, function (err, client) {
        if (err) { return done(err) }
        if (!client) { return done(null, false) }
        if (!client.redirectUri != redirectUri) { return done(null, false) }
        return done(null, client, client.redirectUri)
      })
    },
    authenticateClient: function (clientId, clientSecret, done) {
      Clients.findOne({ clientId: clientId }, function (err, client) {
        if (err) { return done(err) }
        if (!client) { return done(null, false) }
        if (client.clientSecret != clientSecret) { return done(null, false) }
        return done(null, client)
      })
    },
    findUserByToken: function (token, done) {
      User.findOne({ token: token }, function (err, user) {
        if (err) { return done(err) }
        if (!user) { return done(null, false) }
        return done(null, user, { scope: 'all' })
      })
    },
    // An array of unique session keys to sign and verify cookies.
    sessionKeys: ['a', 'b', 'c', ...],
    ensureLoggedIn: function (req, res, next) {
      // For example: https://github.com/jaredhanson/connect-ensure-login
    },
    immediateAuthorization: function (client, user, scope, done) {
      return done(null, false)
    },
    serveAuthorizationPage: function (req, res) {
      res.render('dialog', {
        transactionId: req.oauth2.transactionID,
        user: req.user,
        client: req.oauth2.client
      })
    },
    grant: {
      code: function (client, redirectUri, user, ares, done) {
        AuthorizationCode.create(client.id, redirectUri, user.id, ares.scope, function (err, code) {
          if (err) { return done(err) }
          done(null, code)
        })
      },
      token: function (client, user, ares, done) {
        AccessToken.create(client, user, ares.scope, function (err, accessToken) {
          if (err) { return done(err) }
          done(null, accessToken /*, params */)
        })
      }
    },
    exchange: {
      code: function (client, code, redirectUri, done) {
        AccessToken.create(client, code, redirectUri, function (err, accessToken) {
          if (err) { return done(err) }
          done(null, accessToken /*, refreshToken, params */)
        })
      },
      credentials: function (client, scope, done) {
        AccessToken.create(client, scope, function (err, accessToken) {
          if (err) { return done(err) }
          done(null, accessToken /*, refreshToken, params */)
        })
      },
      owner: function (client, username, password, scope, done) {
        AccessToken.create(client, username, password, scope, function (err, accessToken) {
          if (err) { return done(err) }
          done(null, accessToken /*, refreshToken, params */)
        })
      },
      refresh: function (client, refreshToken, scope, done) {
        AccessToken.create(client, refreshToken, scope, function (err, accessToken) {
          if (err) { return done(err) }
          done(null, accessToken /*, refreshToken, params */)
        })
      }
    }
  }
})

Osprey will automatically block requests with invalid scopes, when defined in RAML using the inline option syntax.

/example:
  securedBy: [oauth_2_0: { scopes: [ ADMINISTRATOR ] } ]

To implement scope validation in your own application, without RAML, use osprey.security.scope('example') and users without the required scope will be rejected.

app.get('/foo/bar', osprey.security.scope('example'), function (req, res) {
  res.send('hello, world')
})

Please note: OAuth 2.0 does not (currently) take into account security scheme describedBy of specification.

OAuth 1.0

Coming soon...

Basic Authentication

Provided by Passport-HTTP.

securitySchemes:
  - basic_auth:
      type: Basic Authentication
// model is an instance of webapi-parser WebApiDocument
osprey.security(model, {
  basic_auth: {
    realm: 'Users', // Optional.
    passReqToCallback: false, // Optional. Default value: false. If true "req" is added as the first callback argument.
    validateUser: function (username, password, done) {
      User.findOne({ username: username }, function (err, user) {
        if (err) { return done(err) }
        if (!user) { return done(null, false) }
        if (!user.verifyPassword(password)) { return done(null, false) }
        return done(null, user)
      })
    }
  }
})

Digest Authentication

Provided by Passport-HTTP.

securitySchemes:
  - digest_auth:
      type: Digest Authentication
// model is an instance of webapi-parser WebApiDocument
osprey.security(model, {
  digest_auth: {
    realm: 'Users', // Optional.
    domain: 'example.com', // Optional.
    findUserByUsername: function (username, done) {
      User.findOne({ username: username }, function (err, user) {
        if (err) { return done(err) }
        if (!user) { return done(null, false) }
        return done(null, user, user.password)
      })
    }
  }
})

Custom Security Schemes

To register a custom security scheme, you can pass in your own function.

securitySchemes:
  - custom_auth:
      type: x-custom

The function must return an object with a handler and, optionally, a router. The router will be mounted immediately and the handler will be called on every secured route with the secured by options and the RAML path.

// model is an instance of webapi-parser WebApiDocument
osprey.security(model, {
  custom_auth: function (scheme, name) {
    return {
      handler: function (options, path) {
        return function (req, res, next) {
          return next()
        }
      },
      router: function (req, res, next) {
        return next()
      }
    }
  }
})

Proxy

osprey.proxy(middleware, addresses)

Pass in an Osprey middleware function with an array of addresses to proxy to and you have a fully-functioning validation and/or security proxy.

License

Apache 2.0

More Repositories

1

api-designer

A web editor for creating and sharing RAML API specifications
JavaScript
1,067
star
2

api-console

An interactive REST console based on RAML/OAS files
JavaScript
894
star
3

mule

Mule Community Edition
Java
387
star
4

api-notebook

Interactive API notebook
JavaScript
154
star
5

mule-transport-amqp

Mule AMQP Transport
Java
33
star
6

mule-maven-plugin

Maven plugins for Mule Runtime
Java
33
star
7

mule-migration-assistant

Migration Framework
Java
24
star
8

docs-connectors

Documentation for connectors
22
star
9

docs-mule-runtime

19
star
10

api-policies

Java
18
star
11

mule-api

Java
18
star
12

osprey-cli

The Command Line Interface (CLI) scaffolding tool to generate Osprey-based applications, ideally from a pre-defined RAML API spec, with just a single command.
CoffeeScript
18
star
13

apikit

APIkit is a tool for building REST APIs using MULE Runtime
JavaScript
16
star
14

mule-esb-maven-tools

ESB Maven Tools
Java
15
star
15

mule-db-connector

Java
14
star
16

data-weave-io

Everything that is related to IO support for data weave: file, HTTP, OAS/RAML, GraphQL
DataWeave
12
star
17

mule-cookbook

A basic web service that users can use to learn the features provided by the DevKit and incrementally learn to develop Connectors
Java
11
star
18

mule-http-connector

Java
11
star
19

docs-runtime-manager

11
star
20

mule-extensions-archetype

Java
9
star
21

docs-runtime-fabric

9
star
22

docs-studio

8
star
23

mule-wsc-connector

Java
8
star
24

docs-general

8
star
25

docs-access-management

7
star
26

mule-distributions

Java
6
star
27

mule-common

Mule Metadata shared APIs
Java
6
star
28

git-connector

Mule Git Connector
Java
6
star
29

mule-extensions-parent

Parent POM for SDK based extensions which are not shipped in the Mule distribution
Shell
6
star
30

docs-design-center

6
star
31

docs-dataweave

DataWeave
6
star
32

docs-api-manager

6
star
33

docs-monitoring

5
star
34

mule-oauth-module

Java
5
star
35

mule-ftp-connector

Java
5
star
36

hbase-connector

HBase Connector
Java
5
star
37

docs-object-store

5
star
38

mule-file-commons

Java
5
star
39

linkedin-connector

LinkedIn is a business-related social networking site. Founded in December 2002 and launched in May 2003, it is mainly used for professional networking. This connector allows you to interact with LinkedIn API.
Java
5
star
40

mule-integration-tests

Java
4
star
41

docs-hosting

4
star
42

mule-wsdl-parser

A parser for WSDL files written in kotlin.
Kotlin
4
star
43

mule-extensions-api

Mule Extensions API
Java
4
star
44

mule-assembly-verifier-plugin

Maven Assembly Verifier for Mule Build
Groovy
4
star
45

facebook-connector

Facebook Cloud Connector
Java
4
star
46

api-sync-tool

Tool that synchronizes an API in API platform with local filesystem
JavaScript
4
star
47

mule-objectstore-connector

Java
4
star
48

docs-mq

Java
4
star
49

mule-tool-schema-doc

XSLT
3
star
50

mule-sockets-connector

Java
3
star
51

docs-mule-sdk

3
star
52

mule-xml-module

Java
3
star
53

google-drive-connector

Mule connector for Google Drive API
Java
3
star
54

google-calendar-connector

Mule connector for Google Calendar API
Java
3
star
55

mule-tooling-incubator

Mule Tooling Incubator Projects
Java
3
star
56

docs-munit

3
star
57

docs-anypoint-security

3
star
58

mule-amazon-s3-connector-crud-app-demo

HTML
3
star
59

docs-service-mesh

3
star
60

mule-jms-connector

Java
3
star
61

data-weave-jwt-library

DataWeave
3
star
62

apikit-rest-module

HTML
3
star
63

mule-scripting-module

Java
3
star
64

google-spreadsheets-connector

Mule connector for Google Spreadsheets API
Java
3
star
65

mule-vm-connector

Java
3
star
66

mule-email-connector

Java
3
star
67

docs-composer

3
star
68

apikit-odata-extension

Extension module that enables OData into APIkit rest
Java
3
star
69

mule-custom-properties-providers-module-example

Example of mule module for creating a custom properties provider
Java
3
star
70

docs-exchange

2
star
71

docs-apikit

RAML
2
star
72

docs-visualizer

2
star
73

apikit-rest-scaffolder

Java
2
star
74

docs-functional-monitoring

2
star
75

docs-site-ui

This project produces the UI bundles used by the MuleSoft documentation sites.
Handlebars
2
star
76

docs-partner-manager

2
star
77

docs-connector-devkit

Java
2
star
78

data-weave-mock-data-generators-library

DataWeave
2
star
79

mule-file-connector

Java
2
star
80

mule-validation-module

Java
2
star
81

mule-cookbook-connector

Mule Cookbook Example - Connector Only
Java
2
star
82

api-gateway-custom-policies-examples

2
star
83

data-weave-analytics-library

DataWeave
2
star
84

jira-connector

JIRA is a proprietary issue tracking product, developed by Atlassian, commonly used for bug tracking, issue tracking, and project management.
Java
2
star
85

mule-cookbook-tutorial

Anypoint Devkit Tutorial for the Cookbook Service
Java
2
star
86

hubspot-connector

HubSpot all-in-one marketing software helps more than 8,000 companies in 56 countries attract leads and convert them into customers. A pioneer in inbound marketing, HubSpot aims to help its customers make marketing that people actually love.
Java
2
star
87

docs-gov-cloud

2
star
88

mule-ce-services-parent

Shell
2
star
89

docs-gateway

2
star
90

mule-compression-module

Java
2
star
91

mule-tooling-mock-connector

Mule Tooling Mock Connector
Java
2
star
92

documentum-connector

Documentum is an enterprise content management platform. Documentum provides management capabilities for all types of content. The core of Documentum is a repository in which the content is stored securely under compliance rules and a unified environment, although content may reside on multiple servers and physical storage devices within a networked environment. Documentum provides a suite of services, such as document management, collaboration, search, content classification, input management, Business Process Management (BPM), customer communication management, and Web content management.
Java
2
star
93

mule-http-service

Java
2
star
94

mule-soap-service

Java
1
star
95

apikit-rest-metadata-api

Java
1
star
96

mule3-tutorial-connector

1
star
97

mule-json-module

Java
1
star
98

mule-servicenow-connector-operations-demo

HTML
1
star
99

mule-module-maven-plugin

Maven plugin to work with mule modules
Java
1
star
100

mule-spring-module

Java
1
star