1.0.1 • Published 8 years ago

simple-proxy-gateway v1.0.1

Weekly downloads
1
License
Apache 2.0
Repository
-
Last release
8 years ago

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

  1. Clone this repo
  2. Run docker-compose up
  3. Visit http://localhost:3000/posts?apikey=LeCxzc9yRJoBRBgpGjNCsqCaVdNNiQdezNwGcNEp

Running it like a normal NodeJS app

  1. npm install simple-proxy-gateway --save
  2. 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.
  3. 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);
});
  1. 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 runs
  • REDIS_HOST: Hostname of the redis server
  • REDIS_PORT: The redis port the server runs on
  • SITE_NAME: The site name
  • SITE_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 proxying
  • SITE_HTTPS: Use https for the proxy
  • SITE_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 limiting
  • SITE_RATE_LIMIT_TOTAL: Total number of requests allowed
  • SITE_RATE_LIMIT_EXPIRATION: Timeframe in which number of requests are allowed
  • SITE_PLUGINS: Plugins to enable for the site. e.g. rateLimit, siteAccess
  • SITE_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 is apikey.
  • 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;