Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructured to work with specific headers and orders #47

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
131 changes: 56 additions & 75 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
"use strict";

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

var is = require('is_js');

var extend = require('extend');

var DEFAULTS = {
sources: [// Standard headers used by Amazon EC2, Heroku, and others.
'headers.x-client-ip', // Load-balancers (AWS ELB) or proxies.
'headers.x-forwarded-for', // Cloudflare.
// @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
// CF-Connecting-IP - applied to every request to the origin.
'headers.cf-connecting-ip', // Fastly and Firebase hosting header (When forwared to cloud function)
'headers.fastly-client-ip', // Akamai and Cloudflare: True-Client-IP.
'headers.true-client-ip', // Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies.
'headers.x-real-ip', // (Rackspace LB and Riverbed's Stingray)
// http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
// https://splash.riverbed.com/docs/DOC-1926
'headers.x-cluster-client-ip', 'headers.x-forwarded', 'headers.forwarded-for', 'headers.forwarded', 'connection.remoteAddress', 'connection.socket.remoteAddress', 'socket.remoteAddress', 'info.remoteAddress', // AWS Api Gateway + Lambda
'requestContext.identity.sourceIp']
};
/**
* Parse x-forwarded-for headers.
*
* @param {string} value - The value to be parsed.
* @return {string|null} First known IP address, if any.
*/


function getClientIpFromXForwardedFor(value) {
if (!is.existy(value)) {
return null;
}

if (is.not.string(value)) {
throw new TypeError("Expected a string, got \"".concat(_typeof(value), "\""));
} // x-forwarded-for may return multiple IP addresses in the format:
Expand Down Expand Up @@ -45,92 +58,60 @@ function getClientIpFromXForwardedFor(value) {
return forwardedIps.find(is.ip);
}
/**
* Determine client IP address.
* Parse object tree and fetch relevant key.
*
* @param req
* @returns {string} ip - The IP address if known, defaulting to empty string if unknown.
* @param keys
* @returns {string|null|*} - The value from the object tree.
*/


function getClientIp(req) {
// Server is probably behind a proxy.
if (req.headers) {
// Standard headers used by Amazon EC2, Heroku, and others.
if (is.ip(req.headers['x-client-ip'])) {
return req.headers['x-client-ip'];
} // Load-balancers (AWS ELB) or proxies.


var xForwardedFor = getClientIpFromXForwardedFor(req.headers['x-forwarded-for']);

if (is.ip(xForwardedFor)) {
return xForwardedFor;
} // Cloudflare.
// @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
// CF-Connecting-IP - applied to every request to the origin.


if (is.ip(req.headers['cf-connecting-ip'])) {
return req.headers['cf-connecting-ip'];
} // Fastly and Firebase hosting header (When forwared to cloud function)

function getIpFromSource(req, keys) {
var key = keys.shift();

if (is.ip(req.headers['fastly-client-ip'])) {
return req.headers['fastly-client-ip'];
} // Akamai and Cloudflare: True-Client-IP.


if (is.ip(req.headers['true-client-ip'])) {
return req.headers['true-client-ip'];
} // Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies.
if (!is.existy(req[key])) {
return null;
}

if (keys.length !== 0) {
return getIpFromSource(req[key], keys);
}

if (is.ip(req.headers['x-real-ip'])) {
return req.headers['x-real-ip'];
} // (Rackspace LB and Riverbed's Stingray)
// http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
// https://splash.riverbed.com/docs/DOC-1926
if (key === 'x-forwarded-for') {
return getClientIpFromXForwardedFor(req[key]);
}

return req[key];
}
/**
* Determine client IP address.
*
* @param req
* @param _options
* @returns {string} ip - The IP address if known, defaulting to empty string if unknown.
*/

if (is.ip(req.headers['x-cluster-client-ip'])) {
return req.headers['x-cluster-client-ip'];
}

if (is.ip(req.headers['x-forwarded'])) {
return req.headers['x-forwarded'];
}
function getClientIp(req) {
var _options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

if (is.ip(req.headers['forwarded-for'])) {
return req.headers['forwarded-for'];
}
var options = extend(false, {}, DEFAULTS, _options);
var sources = options.sources; // eslint-disable-next-line no-restricted-syntax

if (is.ip(req.headers.forwarded)) {
return req.headers.forwarded;
for (var key in sources) {
// eslint-disable-next-line no-prototype-builtins
if (Object.prototype.hasOwnProperty(sources, key)) {
// eslint-disable-next-line no-continue
continue;
}
} // Remote address checks.

var source = sources[key];
var keys = source.split('.');
var ip = getIpFromSource(req, keys);

if (is.existy(req.connection)) {
if (is.ip(req.connection.remoteAddress)) {
return req.connection.remoteAddress;
if (is.ip(ip)) {
return ip;
}

if (is.existy(req.connection.socket) && is.ip(req.connection.socket.remoteAddress)) {
return req.connection.socket.remoteAddress;
}
}

if (is.existy(req.socket) && is.ip(req.socket.remoteAddress)) {
return req.socket.remoteAddress;
}

if (is.existy(req.info) && is.ip(req.info.remoteAddress)) {
return req.info.remoteAddress;
} // AWS Api Gateway + Lambda


if (is.existy(req.requestContext) && is.existy(req.requestContext.identity) && is.ip(req.requestContext.identity.sourceIp)) {
return req.requestContext.identity.sourceIp;
}

return null;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"test": "nyc --reporter=html --reporter=text --check-coverage --lines=100 --statements=100 tape ./test/index.js"
},
"dependencies": {
"extend": "^3.0.2",
"is_js": "^0.9.0"
},
"devDependencies": {
Expand Down
153 changes: 71 additions & 82 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,37 @@
const is = require('is_js');
const extend = require('extend');

const DEFAULTS = {
sources: [
// Standard headers used by Amazon EC2, Heroku, and others.
'headers.x-client-ip',
// Load-balancers (AWS ELB) or proxies.
'headers.x-forwarded-for',
// Cloudflare.
// @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
// CF-Connecting-IP - applied to every request to the origin.
'headers.cf-connecting-ip',
// Fastly and Firebase hosting header (When forwared to cloud function)
'headers.fastly-client-ip',
// Akamai and Cloudflare: True-Client-IP.
'headers.true-client-ip',
// Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies.
'headers.x-real-ip',
// (Rackspace LB and Riverbed's Stingray)
// http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
// https://splash.riverbed.com/docs/DOC-1926
'headers.x-cluster-client-ip',
'headers.x-forwarded',
'headers.forwarded-for',
'headers.forwarded',
'connection.remoteAddress',
'connection.socket.remoteAddress',
'socket.remoteAddress',
'info.remoteAddress',
// AWS Api Gateway + Lambda
'requestContext.identity.sourceIp',
],
};

/**
* Parse x-forwarded-for headers.
Expand All @@ -7,10 +40,6 @@ const is = require('is_js');
* @return {string|null} First known IP address, if any.
*/
function getClientIpFromXForwardedFor(value) {
if (!is.existy(value)) {
return null;
}

if (is.not.string(value)) {
throw new TypeError(`Expected a string, got "${typeof value}"`);
}
Expand Down Expand Up @@ -40,90 +69,50 @@ function getClientIpFromXForwardedFor(value) {
}

/**
* Determine client IP address.
* Parse object tree and fetch relevant key.
*
* @param req
* @returns {string} ip - The IP address if known, defaulting to empty string if unknown.
* @param keys
* @returns {string|null|*} - The value from the object tree.
*/
function getClientIp(req) {

// Server is probably behind a proxy.
if (req.headers) {

// Standard headers used by Amazon EC2, Heroku, and others.
if (is.ip(req.headers['x-client-ip'])) {
return req.headers['x-client-ip'];
}

// Load-balancers (AWS ELB) or proxies.
const xForwardedFor = getClientIpFromXForwardedFor(req.headers['x-forwarded-for']);
if (is.ip(xForwardedFor)) {
return xForwardedFor;
}

// Cloudflare.
// @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
// CF-Connecting-IP - applied to every request to the origin.
if (is.ip(req.headers['cf-connecting-ip'])) {
return req.headers['cf-connecting-ip'];
}

// Fastly and Firebase hosting header (When forwared to cloud function)
if (is.ip(req.headers['fastly-client-ip'])) {
return req.headers['fastly-client-ip'];
}

// Akamai and Cloudflare: True-Client-IP.
if (is.ip(req.headers['true-client-ip'])) {
return req.headers['true-client-ip'];
}

// Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies.
if (is.ip(req.headers['x-real-ip'])) {
return req.headers['x-real-ip'];
}

// (Rackspace LB and Riverbed's Stingray)
// http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
// https://splash.riverbed.com/docs/DOC-1926
if (is.ip(req.headers['x-cluster-client-ip'])) {
return req.headers['x-cluster-client-ip'];
}

if (is.ip(req.headers['x-forwarded'])) {
return req.headers['x-forwarded'];
}

if (is.ip(req.headers['forwarded-for'])) {
return req.headers['forwarded-for'];
}

if (is.ip(req.headers.forwarded)) {
return req.headers.forwarded;
}
}

// Remote address checks.
if (is.existy(req.connection)) {
if (is.ip(req.connection.remoteAddress)) {
return req.connection.remoteAddress;
}
if (is.existy(req.connection.socket) && is.ip(req.connection.socket.remoteAddress)) {
return req.connection.socket.remoteAddress;
}
function getIpFromSource(req, keys) {
const key = keys.shift();
if (!is.existy(req[key])) {
return null;
}

if (is.existy(req.socket) && is.ip(req.socket.remoteAddress)) {
return req.socket.remoteAddress;
if (keys.length !== 0) {
return getIpFromSource(req[key], keys);
}

if (is.existy(req.info) && is.ip(req.info.remoteAddress)) {
return req.info.remoteAddress;
if (key === 'x-forwarded-for') {
return getClientIpFromXForwardedFor(req[key]);
}
return req[key];
}

// AWS Api Gateway + Lambda
if (is.existy(req.requestContext) && is.existy(req.requestContext.identity) && is.ip(req.requestContext.identity.sourceIp)) {
return req.requestContext.identity.sourceIp;
/**
* Determine client IP address.
*
* @param req
* @param _options
* @returns {string} ip - The IP address if known, defaulting to empty string if unknown.
*/
function getClientIp(req, _options = {}) {
const options = extend(false, {}, DEFAULTS, _options);
const { sources } = options;

// eslint-disable-next-line no-restricted-syntax
for (const key in sources) {
// eslint-disable-next-line no-prototype-builtins
if (Object.prototype.hasOwnProperty(sources, key)) {
// eslint-disable-next-line no-continue
continue;
}
const source = sources[key];
const keys = source.split('.');
const ip = getIpFromSource(req, keys);
if (is.ip(ip)) {
return ip;
}
}

return null;
Expand All @@ -150,7 +139,7 @@ function mw(options) {
const ip = getClientIp(req);
Object.defineProperty(req, attributeName, {
get: () => ip,
configurable: true
configurable: true,
});
next();
};
Expand Down