simple-proxy-gateway v1.0.1
Simple Proxy Gateway
This app is meant to be a very simple proxy gateway for API's, that is run alongside another node app or micro-service(s).
Requirements
- NodeJS 6.x
- Redis
- Your own API's from somewhere
Getting Started
To get the app up and running quick:
Using Docker Compose
- Clone this repo
- Run
docker-compose up
- Visit
http://localhost:3000/posts?apikey=LeCxzc9yRJoBRBgpGjNCsqCaVdNNiQdezNwGcNEp
Running it like a normal NodeJS app
npm install simple-proxy-gateway --save
- Start up a Redis server and point the app to it (see
Env. Vars
section below for more information). Or, for testing real quick:docker run -p 6379:6379 redis
. - Configure the app via env vars or provide a config with your site info (See below for more on the config section).
Sample App:
var Gateway = require('simple-proxy-gateway');
var gateway = new Gateway(require('./config'));
var port = process.env.PORT || 8001;
gateway.start(port, () => {
logger.info('Gateway listening on port ' + port);
});
node .
Configuration
There are two ways to configure the gateway: 1) Via a config file or 2) Via ENV vars.
Config File
sites
: An array of sites the gateway will proxy
Sample site configuration (You can look at the config.js sample file in this repo as well):
{
/**
* Express route to expose on the gateway for this site.
* The example below will allow any endpoint after `/`. e.g. `/posts`
*/
route: '/*',
/**
* The site name. This is checked against the consumer's access to a site.
*/
site: 'jsonplaceholder.typicode.com',
/**
* The target server that the gateway is proxying.
*/
upstreamUrl: 'https://jsonplaceholder.typicode.com',
/**
* The options for the gateway proxy. This app uses the `express-http-proxy` module and will pass these options to this module.
*/
options: {
https: true
},
/**
* The authentication plugin and options to assign to this site.
* Auth plugins can be found in the /auth folder
*/
auth: {
type: 'apikey',
},
/**
* List of middleware plugins to apply to this site. Plugins can be found
* in the /plugins folder
*/
plugins: [
{ type: 'siteAccess' },
{ type: 'rateLimit', lookup: 'user.apikey' }
]
}
consumers
: An array of consumers the gateway will allow through
Sample consumer:
{
/**
* Consumer name
*/
name: 'some-app-from-somebody',
/**
* API key used by this consumer (used by the apikey auth plugin)
*/
apikey: '123456789abcdefghijklmnop',
/**
* Rate limit settings for this consumer (used by the rateLimit plugin)
*/
rateLimit: {
total: 10000000,
expire: 1000 * 600 * 60,
},
/**
* The site name this consumer can access. Used by the siteAccess plugin
* to check against the site name to see if this consumer can hit the site.
*/
site: 'jsonplaceholder.typicode.com'
}
Environment Variables
You can also configure the gateway to proxy / secure one site using env vars:
PORT
: The port on which the gateway runsREDIS_HOST
: Hostname of the redis serverREDIS_PORT
: The redis port the server runs onSITE_NAME
: The site nameSITE_ENDPOINT_PATH
: The express route / path to expose on the gateway. e.g./*
to catch everything.SITE_UPSTREAM_URL
: The upstream site the gateway is proxyingSITE_HTTPS
: Use https for the proxySITE_AUTH_TYPE
: The auth plugin to use. e.g.apikey
SITE_CONSUMER_APIKEY
: The apikey of the consumer (if using the apikey plugin)SITE_IS_RATE_LIMITED
: Enable rate limitingSITE_RATE_LIMIT_TOTAL
: Total number of requests allowedSITE_RATE_LIMIT_EXPIRATION
: Timeframe in which number of requests are allowedSITE_PLUGINS
: Plugins to enable for the site. e.g. rateLimit, siteAccessSITE_PLUGINS_RATE_LIMIT_LOOKUP
: Path in the Express request object to compare to enforce the rate limit. e.g.user.apikey
Using a custom plugin
In a plugin's configuration (inside the site config), you can tell the gateway where your custom module is using the modulePath
property:
// This object also gets passed to your custom plugin, for configuration
{
type: 'mycustomplugin',
modulePath: 'PATH_TO_PLUGIN_FOLDER/mycustomplugin',
someParam: true,
someOtherParam: '123456789'
}
Writing plugins
Authentication plugins are found in the auth
folder, site plugins are found in the plugins
folder.
All plugins must follow the following conventions:
- The file name should be the name of the plugin. e.g.
apikey.js
means the name used in the config to select this plugin isapikey
. - The plugin should export a ES6 class
- The plugin should have a
middleware
method which returns an Express middleware function.
See below for example of the API key auth plugin:
var logger = require('winston');
class APIKeyAuthPlugin {
constructor(config, server) {
this.config = config;
}
middleware (site) {
var consumers = this.config.consumers;
return (req, res, next) => {
var apiKey = req.headers.apikey || req.query.apikey;
var consumer = consumers.filter(consumer => consumer.apikey && consumer.apikey === apiKey);
// Make sure there is a consumer
if (consumer.length < 1) {
res.status(401).send({
message: 'Unauthorized'
});
logger.log('debug', 'Unauthorized', apiKey);
return;
} else {
req.user = consumer[0];
}
return next();
};
}
}
module.exports = APIKeyAuthPlugin;
See below for example of the rate limiting plugin:
var logger = require('winston');
class RateLimitPlugin {
constructor(config, pluginConfig, server) {
this.config = config;
this.pluginConfig = pluginConfig;
this.limiter = require('express-limiter')(server.app, server.db);
}
middleware (site) {
var lookup = process.env.SITE_PLUGINS_RATE_LIMIT_LOOKUP || this.pluginConfig.lookup;
return this.limiter({
method: 'all',
lookup: function (req, res, opts, next) {
var user = req.user;
opts.lookup = lookup || 'user.apikey';
opts.total = user.rateLimit.total;
opts.expire = user.rateLimit.expire;
return next();
},
onRateLimited: function (req, res) {
res.status(429).send({
message: 'Rate limit exceeded'
});
logger.log('debug', 'Rate limit exceeded', req.user);
}
});
}
}
module.exports = RateLimitPlugin;