mulesoft_nodejs

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 10

7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.

JS API middleware from a RAML definition

This repository has been archived by the owner on May 10, 2024. It is now read-only.

mulesoft / osprey Public archive

Generate Node.JS API middleware from a RAML definition

View license

431 stars 66 forks Branches Tags Activity

Star Notifications

Code Issues 33 Pull requests Actions Wiki Security Insights

master 10 Branches 28 Tags Go to file Go to file Code

jstoiko Merge pull request #205 from mulesoft/rework_webapi_parser b7481e9 · 4 years ago

bin Not use instance of to check amf types 5 years ago

examples Replace var with let, const 5 years ago

lib Optimize addSecurityHeaders a little 5 years ago

test Restore addJsonSchema API 4 years ago

.editorconfig Rewrite Osprey as native middleware 10 years ago

.gitignore Migrate from istanbul to nyc 5 years ago

.travis.yml Update deps 4 years ago

LICENSE Rewrite Osprey as native middleware 10 years ago

README.md Restore addJsonSchema API 4 years ago

osprey.js Restore addJsonSchema API 4 years ago

package-lock.json Bump version 4 years ago

package.json Bump version 4 years ago

README License

Osprey
npm v1.0.0 downloads 1.9k/month Build status coverage 97% Greenkeeper Move to Snyk

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

https://github.com/mulesoft/osprey 1/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition
Headers
Query parameters
RAML 1.0 types
Automatic Request Parameters
Default Headers
Default Parameters
RAML Router
Uses osprey-router for RAML paths
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. 404 ing 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)

https://github.com/mulesoft/osprey 2/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition
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 )
https://github.com/mulesoft/osprey 3/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition
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')
})

https://github.com/mulesoft/osprey 4/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition

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:

https://github.com/mulesoft/osprey 5/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition

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

https://github.com/mulesoft/osprey 6/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition
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' })
})

https://github.com/mulesoft/osprey 7/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition
},
// 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.

https://github.com/mulesoft/osprey 8/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition

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.

https://github.com/mulesoft/osprey 9/10
7/8/24, 11:46 AM GitHub - mulesoft/osprey: Generate Node.JS API middleware from a RAML definition

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)

Releases 19

Rewrote to work with webapi-parser Latest


on Jul 28, 2020

+ 18 releases

Packages

No packages published

Contributors 24

+ 10 contributors

Languages

JavaScript 100.0%

https://github.com/mulesoft/osprey 10/10

You might also like