From 5bbaf46fc93192cec626e327b808c3a2cda1435f Mon Sep 17 00:00:00 2001 From: Chheung Date: Tue, 20 Dec 2022 17:35:55 +0700 Subject: [PATCH 1/4] Add header pioritizing, update README, example and added test case. --- README.md | 30 ++-- examples/connect-middleware-custom.js | 17 ++- lib/index.js | 188 +++++++++++++++++--------- lib/is.js | 6 - src/index.js | 150 ++++++++++++++------ test/index.js | 24 ++++ 6 files changed, 282 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index bb5e6f1..8b584ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # request-ip -A tiny Node.js module for retrieving a request's IP address. +A tiny Node.js module for retrieving a request's IP address. ![](https://nodei.co/npm/request-ip.png?downloads=true&cacheBust=3) @@ -12,27 +12,29 @@ A tiny Node.js module for retrieving a request's IP address. ## Installation Yarn + ``` yarn add request-ip ``` npm + ```bash npm install request-ip --save ``` - + ## Getting Started ```javascript const requestIp = require('request-ip'); // inside middleware handler -const ipMiddleware = function(req, res, next) { - const clientIp = requestIp.getClientIp(req); - next(); +const ipMiddleware = function (req, res, next) { + const clientIp = requestIp.getClientIp(req); + next(); }; -// on localhost you'll see 127.0.0.1 if you're using IPv4 +// on localhost you'll see 127.0.0.1 if you're using IPv4 // or ::1, ::ffff:127.0.0.1 if you're using IPv6 ``` @@ -40,17 +42,18 @@ const ipMiddleware = function(req, res, next) { ```javascript const requestIp = require('request-ip'); -app.use(requestIp.mw()) +app.use(requestIp.mw()); -app.use(function(req, res) { - const ip = req.clientIp; - res.end(ip); +app.use(function (req, res) { + const ip = req.clientIp; + res.end(ip); }); ``` To see a full working code for the middleware, check out the [examples](https://github.com/pbojinov/request-ip/tree/master/examples) folder. -The connect-middleware also supports retrieving the ip address under a custom attribute name, which also works as a container for any future settings. +The connect-middleware also supports retrieving the ip address under a custom attribute name, which also works as a container for any future settings. +And it also support header check priority. ## How It Works @@ -58,7 +61,7 @@ It looks for specific headers in the request and falls back to some defaults if The user ip is determined by the following order: -1. `X-Client-IP` +1. `X-Client-IP` 2. `X-Forwarded-For` (Header may return multiple IP addresses in the format: "client IP, proxy 1 IP, proxy 2 IP", so we take the first one.) 3. `CF-Connecting-IP` (Cloudflare) 4. `Fastly-Client-Ip` (Fastly CDN and Firebase hosting header when forwared to a cloud function) @@ -78,8 +81,7 @@ If an IP address cannot be found, it will return `null`. ## Samples Use Cases -* Getting a user's IP for geolocation. - +- Getting a user's IP for geolocation. ## Running the Tests diff --git a/examples/connect-middleware-custom.js b/examples/connect-middleware-custom.js index 956a15e..bd18671 100644 --- a/examples/connect-middleware-custom.js +++ b/examples/connect-middleware-custom.js @@ -9,22 +9,29 @@ var requestIp = require('request-ip'); // you can override which attirbute the ip will be set on by // passing in an options object with an attributeName -app.use(requestIp.mw({ attributeName : 'myCustomAttributeName' })) +app.use( + requestIp.mw({attributeName: 'myCustomAttributeName', pr: ['x-real-ip']}), +); // respond to all requests -app.use(function(req, res) { - +app.use(function (req, res) { // use our custom attributeName that we registered in the middleware var ip = req.myCustomAttributeName; console.log(ip); // https://nodejs.org/api/net.html#net_net_isip_input var ipType = net.isIP(ip); // returns 0 for invalid, 4 for IPv4, and 6 for IPv6 - res.end('Hello, your ip address is ' + ip + ' and is of type IPv' + ipType + '\n'); + res.end( + 'Hello, your ip address is ' + + ip + + ' and is of type IPv' + + ipType + + '\n', + ); }); //create node.js http server and listen on port http.createServer(app).listen(3000); // test it locally from the command line -// > curl -X GET localhost:3000 # Hello, your ip address is ::1 and is of type IPv6 \ No newline at end of file +// > curl -X GET localhost:3000 # Hello, your ip address is ::1 and is of type IPv6 diff --git a/lib/index.js b/lib/index.js index 13811b2..86c9d03 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,132 +1,189 @@ "use strict"; +function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } +function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } +function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } +function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } - var is = require('./is'); - +var defaultHeaderPrioritylist = ['x-client-ip', 'x-forwarded-for', 'cf-connecting-ip', 'fastly-client-ip', 'true-client-ip', 'x-real-ip', 'x-cluster-client-ip', 'x-forwarded', 'forwarded-for', 'forwarded', 'x-appengine-user-ip']; +var headerPriorityList = defaultHeaderPrioritylist; function getClientIpFromXForwardedFor(value) { if (!is.existy(value)) { return null; } - if (is.not.string(value)) { throw new TypeError("Expected a string, got \"".concat(_typeof(value), "\"")); } - var forwardedIps = value.split(',').map(function (e) { var ip = e.trim(); - if (ip.includes(':')) { var splitted = ip.split(':'); - if (splitted.length === 2) { return splitted[0]; } } - return ip; }); - for (var i = 0; i < forwardedIps.length; i++) { if (is.ip(forwardedIps[i])) { return forwardedIps[i]; } } - return null; } - +function getClientIpByHeader(req, header) { + switch (header) { + case 'x-client-ip': + { + if (is.ip(req.headers['x-client-ip'])) { + return req.headers['x-client-ip']; + } + } + case 'x-forwarded-for': + { + var xForwardedFor = getClientIpFromXForwardedFor(req.headers['x-forwarded-for']); + if (is.ip(xForwardedFor)) { + return xForwardedFor; + } + } + case 'cf-connecting-ip': + { + if (is.ip(req.headers['cf-connecting-ip'])) { + return req.headers['cf-connecting-ip']; + } + } + case 'fastly-client-ip': + { + if (is.ip(req.headers['fastly-client-ip'])) { + return req.headers['fastly-client-ip']; + } + } + case 'true-client-ip': + { + if (is.ip(req.headers['true-client-ip'])) { + return req.headers['true-client-ip']; + } + } + case 'x-real-ip': + { + if (is.ip(req.headers['x-real-ip'])) { + return req.headers['x-real-ip']; + } + } + case 'x-cluster-client-ip': + { + if (is.ip(req.headers['x-cluster-client-ip'])) { + return req.headers['x-cluster-client-ip']; + } + } + case 'x-forwarded': + { + if (is.ip(req.headers['x-forwarded'])) { + return req.headers['x-forwarded']; + } + } + case 'forwarded-for': + { + if (is.ip(req.headers['forwarded-for'])) { + return req.headers['forwarded-for']; + } + } + case 'forwarded': + { + if (is.ip(req.headers['forwarded'])) { + return req.headers['forwarded']; + } + } + case 'x-appengine-user-ip': + { + if (is.ip(req.headers['x-appengine-user-ip'])) { + return req.headers['x-appengine-user-ip']; + } + } + default: + { + return null; + } + } +} function getClientIp(req) { if (req.headers) { - if (is.ip(req.headers['x-client-ip'])) { - return req.headers['x-client-ip']; - } - - var xForwardedFor = getClientIpFromXForwardedFor(req.headers['x-forwarded-for']); - - if (is.ip(xForwardedFor)) { - return xForwardedFor; - } - - if (is.ip(req.headers['cf-connecting-ip'])) { - return req.headers['cf-connecting-ip']; - } - - if (is.ip(req.headers['fastly-client-ip'])) { - return req.headers['fastly-client-ip']; - } - - if (is.ip(req.headers['true-client-ip'])) { - return req.headers['true-client-ip']; - } - - if (is.ip(req.headers['x-real-ip'])) { - return req.headers['x-real-ip']; - } - - 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; - } - - if (is.ip(req.headers['x-appengine-user-ip'])) { - return req.headers['x-appengine-user-ip']; + var _iterator = _createForOfIteratorHelper(headerPriorityList), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var header = _step.value; + var value = getClientIpByHeader(req, header); + if (value) return value; + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); } } - 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; } } - 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; } - if (is.existy(req.requestContext) && is.existy(req.requestContext.identity) && is.ip(req.requestContext.identity.sourceIp)) { return req.requestContext.identity.sourceIp; } - if (req.headers) { if (is.ip(req.headers['Cf-Pseudo-IPv4'])) { return req.headers['Cf-Pseudo-IPv4']; } } - if (is.existy(req.raw)) { return getClientIp(req.raw); } - return null; } - function mw(options) { + var _configuration$priori; var configuration = is.not.existy(options) ? {} : options; - if (is.not.object(configuration)) { throw new TypeError('Options must be an object!'); } - + if (configuration !== null && configuration !== void 0 && (_configuration$priori = configuration.prioritize) !== null && _configuration$priori !== void 0 && _configuration$priori.length) { + if (configuration !== null && configuration !== void 0 && configuration.prioritize.find(function (item) { + return typeof item !== 'string'; + })) { + throw new TypeError('Prioritize list must be an array of string!'); + } + var _iterator2 = _createForOfIteratorHelper(configuration.prioritize), + _step2; + try { + for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { + var prioritizedHeader = _step2.value; + for (var i = 0; i < headerPriorityList.length; i++) { + if (prioritizedHeader === headerPriorityList[i]) { + headerPriorityList.splice(i, 1); + break; + } + } + } + } catch (err) { + _iterator2.e(err); + } finally { + _iterator2.f(); + } + headerPriorityList.unshift.apply(headerPriorityList, _toConsumableArray(configuration.prioritize)); + } var attributeName = configuration.attributeName || 'clientIp'; return function (req, res, next) { var ip = getClientIp(req); @@ -139,7 +196,6 @@ function mw(options) { next(); }; } - module.exports = { getClientIpFromXForwardedFor: getClientIpFromXForwardedFor, getClientIp: getClientIp, diff --git a/lib/is.js b/lib/is.js index a509378..e2ac1ae 100644 --- a/lib/is.js +++ b/lib/is.js @@ -4,29 +4,23 @@ var regexes = { ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/, ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i }; - function not(func) { return function () { return !func.apply(null, Array.prototype.slice.call(arguments)); }; } - function existy(value) { return value != null; } - function ip(value) { return existy(value) && regexes.ipv4.test(value) || regexes.ipv6.test(value); } - function object(value) { return Object(value) === value; } - function string(value) { return Object.prototype.toString.call(value) === '[object String]'; } - var is = { existy: existy, ip: ip, diff --git a/src/index.js b/src/index.js index c281391..0344fec 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,21 @@ const is = require('./is'); +const defaultHeaderPrioritylist = [ + 'x-client-ip', + 'x-forwarded-for', + 'cf-connecting-ip', + 'fastly-client-ip', + 'true-client-ip', + 'x-real-ip', + 'x-cluster-client-ip', + 'x-forwarded', + 'forwarded-for', + 'forwarded', + 'x-appengine-user-ip', +]; + +let headerPriorityList = defaultHeaderPrioritylist; + /** * Parse x-forwarded-for headers. * @@ -47,73 +63,103 @@ function getClientIpFromXForwardedFor(value) { } /** - * Determine client IP address. - * * @param req - * @returns {string} ip - The IP address if known, defaulting to empty string if unknown. + * @param {string} header - header name + * @returns {string | undefined | null} */ -function getClientIp(req) { - // Server is probably behind a proxy. - if (req.headers) { +function getClientIpByHeader(req, header) { + switch (header) { // Standard headers used by Amazon EC2, Heroku, and others. - if (is.ip(req.headers['x-client-ip'])) { - return req.headers['x-client-ip']; + case 'x-client-ip': { + 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; - } + case 'x-forwarded-for': { + 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']; + case 'cf-connecting-ip': { + 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']; + case 'fastly-client-ip': { + 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']; + case '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']; + case 'x-real-ip': { + 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']; + case 'x-cluster-client-ip': { + 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']; + case 'x-forwarded': { + if (is.ip(req.headers['x-forwarded'])) { + return req.headers['x-forwarded']; + } } - - if (is.ip(req.headers['forwarded-for'])) { - return req.headers['forwarded-for']; + case 'forwarded-for': { + if (is.ip(req.headers['forwarded-for'])) { + return req.headers['forwarded-for']; + } } - - if (is.ip(req.headers.forwarded)) { - return req.headers.forwarded; + case 'forwarded': { + if (is.ip(req.headers['forwarded'])) { + return req.headers['forwarded']; + } } // Google Cloud App Engine // https://cloud.google.com/appengine/docs/standard/go/reference/request-response-headers + case 'x-appengine-user-ip': { + if (is.ip(req.headers['x-appengine-user-ip'])) { + return req.headers['x-appengine-user-ip']; + } + } + default: { + return null; + } + } +} - if (is.ip(req.headers['x-appengine-user-ip'])) { - return req.headers['x-appengine-user-ip']; +/** + * Determine client IP address. + * + * @param req + * @returns {string} ip - The IP address if known, defaulting to empty string if unknown. + */ +function getClientIp(req) { + // Server is probably behind a proxy. + if (req.headers) { + for (const header of headerPriorityList) { + const value = getClientIpByHeader(req, header); + if (value) return value; } } @@ -167,8 +213,9 @@ function getClientIp(req) { /** * Expose request IP as a middleware. * - * @param {object} [options] - Configuration. - * @param {string} [options.attributeName] - Name of attribute to augment request object with. + * @param {object} [options] - Configuration. + * @param {string} [options.attributeName] - Name of attribute to augment request object with. + * @param {string[]} [options.prioritize] - Array of string of prioritized headers to be checked first * @return {*} */ function mw(options) { @@ -180,6 +227,25 @@ function mw(options) { throw new TypeError('Options must be an object!'); } + if (configuration?.prioritize?.length) { + if ( + configuration?.prioritize.find((item) => typeof item !== 'string') + ) { + throw new TypeError('Prioritize list must be an array of string!'); + } + + for (const prioritizedHeader of configuration.prioritize) { + for (let i = 0; i < headerPriorityList.length; i++) { + if (prioritizedHeader === headerPriorityList[i]) { + headerPriorityList.splice(i, 1); + break; + } + } + } + + headerPriorityList.unshift(...configuration.prioritize); + } + const attributeName = configuration.attributeName || 'clientIp'; return (req, res, next) => { const ip = getClientIp(req); diff --git a/test/index.js b/test/index.js index ac6a187..d5ea13f 100644 --- a/test/index.js +++ b/test/index.js @@ -617,3 +617,27 @@ test('Cf-Pseudo-IPv4 is not used when other valid headers exist', (t) => { }); t.equal(found, '129.78.138.66'); }); + +test('Header list priority', (t) => { + t.plan(2); + const realClientIp = '107.77.213.113'; + const injectedXFFHeader = '192.168.0.1'; + const headers = { + 'x-forwarded-for': `${injectedXFFHeader}, ${realClientIp}`, + 'x-real-ip': `${realClientIp}`, + }; + const mw = requestIp.mw({ + prioritize: ['x-real-ip'], // set priority over x-forwarded-for + }); + + t.ok(typeof mw === 'function' && mw.length === 3, 'returns a middleware'); + + const mockReq = {headers}; + mw(mockReq, null, () => { + t.equal( + mockReq.clientIp, + realClientIp, + `Expects returned result to be ${realClientIp} instead of ${injectedXFFHeader}`, + ); + }); +}); From 79d6b1790c902eb800aaeb2a24af081265bb8cb2 Mon Sep 17 00:00:00 2001 From: Chheung Date: Tue, 20 Dec 2022 18:34:20 +0700 Subject: [PATCH 2/4] update test case --- lib/index.js | 19 ++++++++++++++++--- src/index.js | 17 ++++++++++++++++- test/index.js | 17 ++++++++++++++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lib/index.js b/lib/index.js index 86c9d03..83da3e8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -36,12 +36,15 @@ function getClientIpFromXForwardedFor(value) { return null; } function getClientIpByHeader(req, header) { + var _req$headers; + if (!(req !== null && req !== void 0 && (_req$headers = req.headers) !== null && _req$headers !== void 0 && _req$headers[header])) return null; switch (header) { case 'x-client-ip': { if (is.ip(req.headers['x-client-ip'])) { return req.headers['x-client-ip']; } + break; } case 'x-forwarded-for': { @@ -49,66 +52,75 @@ function getClientIpByHeader(req, header) { if (is.ip(xForwardedFor)) { return xForwardedFor; } + break; } case 'cf-connecting-ip': { if (is.ip(req.headers['cf-connecting-ip'])) { return req.headers['cf-connecting-ip']; } + break; } case 'fastly-client-ip': { if (is.ip(req.headers['fastly-client-ip'])) { return req.headers['fastly-client-ip']; } + break; } case 'true-client-ip': { if (is.ip(req.headers['true-client-ip'])) { return req.headers['true-client-ip']; } + break; } case 'x-real-ip': { if (is.ip(req.headers['x-real-ip'])) { return req.headers['x-real-ip']; } + break; } case 'x-cluster-client-ip': { if (is.ip(req.headers['x-cluster-client-ip'])) { return req.headers['x-cluster-client-ip']; } + break; } case 'x-forwarded': { if (is.ip(req.headers['x-forwarded'])) { return req.headers['x-forwarded']; } + break; } case 'forwarded-for': { if (is.ip(req.headers['forwarded-for'])) { return req.headers['forwarded-for']; } + break; } case 'forwarded': { if (is.ip(req.headers['forwarded'])) { return req.headers['forwarded']; } + break; } case 'x-appengine-user-ip': { if (is.ip(req.headers['x-appengine-user-ip'])) { return req.headers['x-appengine-user-ip']; } + break; } default: - { - return null; - } + {} } + return null; } function getClientIp(req) { if (req.headers) { @@ -198,6 +210,7 @@ function mw(options) { } module.exports = { getClientIpFromXForwardedFor: getClientIpFromXForwardedFor, + getClientIpByHeader: getClientIpByHeader, getClientIp: getClientIp, mw: mw }; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 0344fec..1903d8a 100644 --- a/src/index.js +++ b/src/index.js @@ -68,12 +68,15 @@ function getClientIpFromXForwardedFor(value) { * @returns {string | undefined | null} */ function getClientIpByHeader(req, header) { + if (!req?.headers?.[header]) return null; + switch (header) { // Standard headers used by Amazon EC2, Heroku, and others. case 'x-client-ip': { if (is.ip(req.headers['x-client-ip'])) { return req.headers['x-client-ip']; } + break; } // Load-balancers (AWS ELB) or proxies. case 'x-forwarded-for': { @@ -84,6 +87,7 @@ function getClientIpByHeader(req, header) { if (is.ip(xForwardedFor)) { return xForwardedFor; } + break; } // Cloudflare. // @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers- @@ -92,24 +96,28 @@ function getClientIpByHeader(req, header) { if (is.ip(req.headers['cf-connecting-ip'])) { return req.headers['cf-connecting-ip']; } + break; } // Fastly and Firebase hosting header (When forwared to cloud function) case 'fastly-client-ip': { if (is.ip(req.headers['fastly-client-ip'])) { return req.headers['fastly-client-ip']; } + break; } // Akamai and Cloudflare: True-Client-IP. case 'true-client-ip': { if (is.ip(req.headers['true-client-ip'])) { return req.headers['true-client-ip']; } + break; } // Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies. case 'x-real-ip': { if (is.ip(req.headers['x-real-ip'])) { return req.headers['x-real-ip']; } + break; } // (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 @@ -118,21 +126,25 @@ function getClientIpByHeader(req, header) { if (is.ip(req.headers['x-cluster-client-ip'])) { return req.headers['x-cluster-client-ip']; } + break; } case 'x-forwarded': { if (is.ip(req.headers['x-forwarded'])) { return req.headers['x-forwarded']; } + break; } case 'forwarded-for': { if (is.ip(req.headers['forwarded-for'])) { return req.headers['forwarded-for']; } + break; } case 'forwarded': { if (is.ip(req.headers['forwarded'])) { return req.headers['forwarded']; } + break; } // Google Cloud App Engine @@ -141,11 +153,13 @@ function getClientIpByHeader(req, header) { if (is.ip(req.headers['x-appengine-user-ip'])) { return req.headers['x-appengine-user-ip']; } + break; } default: { - return null; } } + + return null; } /** @@ -259,6 +273,7 @@ function mw(options) { module.exports = { getClientIpFromXForwardedFor, + getClientIpByHeader, getClientIp, mw, }; diff --git a/test/index.js b/test/index.js index d5ea13f..22a0ebd 100644 --- a/test/index.js +++ b/test/index.js @@ -55,6 +55,20 @@ test('getClientIpFromXForwardedFor', (t) => { t.throws(() => requestIp.getClientIpFromXForwardedFor({}), TypeError); }); +test('getClientIpByHeader', (t) => { + t.plan(2); + const mockRequest = { + headers: { + 'x-real-ip': '107.77.213.114', + }, + }; + t.equal( + requestIp.getClientIpByHeader(mockRequest, 'x-real-ip'), + '107.77.213.114', + ); + t.equal(requestIp.getClientIpByHeader(mockRequest, 'x-client-ip'), null); +}); + test('x-client-ip', (t) => { t.plan(1); const options = { @@ -453,7 +467,7 @@ test('getClientIp - default', (t) => { }); test('request-ip.mw', (t) => { - t.plan(3); + t.plan(4); t.equal( typeof requestIp.mw, 'function', @@ -465,6 +479,7 @@ test('request-ip.mw', (t) => { 'requestIp.mw expects 1 argument - options', ); t.throws(() => requestIp.mw('fail'), TypeError); + t.throws(() => requestIp.mw({prioritize: 'string'}), TypeError); }); test('request-ip.mw - used with no arguments', (t) => { From f777380935eab93fbd6b981154db8f3e5fff66a2 Mon Sep 17 00:00:00 2001 From: Chheung Date: Tue, 20 Dec 2022 18:44:35 +0700 Subject: [PATCH 3/4] update switch case and undefined value --- lib/index.js | 36 ++++++++++++++++++------------------ src/index.js | 33 ++++++++++++++++----------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/lib/index.js b/lib/index.js index 83da3e8..1ea6bd5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -36,19 +36,17 @@ function getClientIpFromXForwardedFor(value) { return null; } function getClientIpByHeader(req, header) { - var _req$headers; - if (!(req !== null && req !== void 0 && (_req$headers = req.headers) !== null && _req$headers !== void 0 && _req$headers[header])) return null; switch (header) { case 'x-client-ip': { - if (is.ip(req.headers['x-client-ip'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['x-client-ip'])) { return req.headers['x-client-ip']; } break; } case 'x-forwarded-for': { - var xForwardedFor = getClientIpFromXForwardedFor(req.headers['x-forwarded-for']); + var xForwardedFor = getClientIpFromXForwardedFor(req === null || req === void 0 ? void 0 : req.headers['x-forwarded-for']); if (is.ip(xForwardedFor)) { return xForwardedFor; } @@ -56,74 +54,76 @@ function getClientIpByHeader(req, header) { } case 'cf-connecting-ip': { - if (is.ip(req.headers['cf-connecting-ip'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['cf-connecting-ip'])) { return req.headers['cf-connecting-ip']; } break; } case 'fastly-client-ip': { - if (is.ip(req.headers['fastly-client-ip'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['fastly-client-ip'])) { return req.headers['fastly-client-ip']; } break; } case 'true-client-ip': { - if (is.ip(req.headers['true-client-ip'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['true-client-ip'])) { return req.headers['true-client-ip']; } break; } case 'x-real-ip': { - if (is.ip(req.headers['x-real-ip'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['x-real-ip'])) { return req.headers['x-real-ip']; } break; } case 'x-cluster-client-ip': { - if (is.ip(req.headers['x-cluster-client-ip'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['x-cluster-client-ip'])) { return req.headers['x-cluster-client-ip']; } break; } case 'x-forwarded': { - if (is.ip(req.headers['x-forwarded'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['x-forwarded'])) { return req.headers['x-forwarded']; } break; } case 'forwarded-for': { - if (is.ip(req.headers['forwarded-for'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['forwarded-for'])) { return req.headers['forwarded-for']; } break; } case 'forwarded': { - if (is.ip(req.headers['forwarded'])) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['forwarded'])) { return req.headers['forwarded']; } break; } case 'x-appengine-user-ip': { - if (is.ip(req.headers['x-appengine-user-ip'])) { - return req.headers['x-appengine-user-ip']; + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['x-appengine-user-ip'])) { + return req === null || req === void 0 ? void 0 : req.headers['x-appengine-user-ip']; } break; } default: - {} + { + break; + } } return null; } function getClientIp(req) { - if (req.headers) { + if (req !== null && req !== void 0 && req.headers) { var _iterator = _createForOfIteratorHelper(headerPriorityList), _step; try { @@ -155,8 +155,8 @@ function getClientIp(req) { if (is.existy(req.requestContext) && is.existy(req.requestContext.identity) && is.ip(req.requestContext.identity.sourceIp)) { return req.requestContext.identity.sourceIp; } - if (req.headers) { - if (is.ip(req.headers['Cf-Pseudo-IPv4'])) { + if (req !== null && req !== void 0 && req.headers) { + if (is.ip(req === null || req === void 0 ? void 0 : req.headers['Cf-Pseudo-IPv4'])) { return req.headers['Cf-Pseudo-IPv4']; } } diff --git a/src/index.js b/src/index.js index 1903d8a..1f2618d 100644 --- a/src/index.js +++ b/src/index.js @@ -68,12 +68,10 @@ function getClientIpFromXForwardedFor(value) { * @returns {string | undefined | null} */ function getClientIpByHeader(req, header) { - if (!req?.headers?.[header]) return null; - switch (header) { // Standard headers used by Amazon EC2, Heroku, and others. case 'x-client-ip': { - if (is.ip(req.headers['x-client-ip'])) { + if (is.ip(req?.headers['x-client-ip'])) { return req.headers['x-client-ip']; } break; @@ -81,7 +79,7 @@ function getClientIpByHeader(req, header) { // Load-balancers (AWS ELB) or proxies. case 'x-forwarded-for': { const xForwardedFor = getClientIpFromXForwardedFor( - req.headers['x-forwarded-for'], + req?.headers['x-forwarded-for'], ); if (is.ip(xForwardedFor)) { @@ -93,28 +91,28 @@ function getClientIpByHeader(req, header) { // @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. case 'cf-connecting-ip': { - if (is.ip(req.headers['cf-connecting-ip'])) { + if (is.ip(req?.headers['cf-connecting-ip'])) { return req.headers['cf-connecting-ip']; } break; } // Fastly and Firebase hosting header (When forwared to cloud function) case 'fastly-client-ip': { - if (is.ip(req.headers['fastly-client-ip'])) { + if (is.ip(req?.headers['fastly-client-ip'])) { return req.headers['fastly-client-ip']; } break; } // Akamai and Cloudflare: True-Client-IP. case 'true-client-ip': { - if (is.ip(req.headers['true-client-ip'])) { + if (is.ip(req?.headers['true-client-ip'])) { return req.headers['true-client-ip']; } break; } // Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies. case 'x-real-ip': { - if (is.ip(req.headers['x-real-ip'])) { + if (is.ip(req?.headers['x-real-ip'])) { return req.headers['x-real-ip']; } break; @@ -123,25 +121,25 @@ function getClientIpByHeader(req, header) { // 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 case 'x-cluster-client-ip': { - if (is.ip(req.headers['x-cluster-client-ip'])) { + if (is.ip(req?.headers['x-cluster-client-ip'])) { return req.headers['x-cluster-client-ip']; } break; } case 'x-forwarded': { - if (is.ip(req.headers['x-forwarded'])) { + if (is.ip(req?.headers['x-forwarded'])) { return req.headers['x-forwarded']; } break; } case 'forwarded-for': { - if (is.ip(req.headers['forwarded-for'])) { + if (is.ip(req?.headers['forwarded-for'])) { return req.headers['forwarded-for']; } break; } case 'forwarded': { - if (is.ip(req.headers['forwarded'])) { + if (is.ip(req?.headers['forwarded'])) { return req.headers['forwarded']; } break; @@ -150,12 +148,13 @@ function getClientIpByHeader(req, header) { // Google Cloud App Engine // https://cloud.google.com/appengine/docs/standard/go/reference/request-response-headers case 'x-appengine-user-ip': { - if (is.ip(req.headers['x-appengine-user-ip'])) { - return req.headers['x-appengine-user-ip']; + if (is.ip(req?.headers['x-appengine-user-ip'])) { + return req?.headers['x-appengine-user-ip']; } break; } default: { + break; } } @@ -170,7 +169,7 @@ function getClientIpByHeader(req, header) { */ function getClientIp(req) { // Server is probably behind a proxy. - if (req.headers) { + if (req?.headers) { for (const header of headerPriorityList) { const value = getClientIpByHeader(req, header); if (value) return value; @@ -210,8 +209,8 @@ function getClientIp(req) { // Cloudflare fallback // https://blog.cloudflare.com/eliminating-the-last-reasons-to-not-enable-ipv6/#introducingpseudoipv4 - if (req.headers) { - if (is.ip(req.headers['Cf-Pseudo-IPv4'])) { + if (req?.headers) { + if (is.ip(req?.headers['Cf-Pseudo-IPv4'])) { return req.headers['Cf-Pseudo-IPv4']; } } From 29ade20c20523932640d49d03488e81142754cfa Mon Sep 17 00:00:00 2001 From: Chheung Date: Thu, 29 Dec 2022 15:05:03 +0700 Subject: [PATCH 4/4] Update example --- examples/connect-middleware-custom.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/connect-middleware-custom.js b/examples/connect-middleware-custom.js index bd18671..86f015c 100644 --- a/examples/connect-middleware-custom.js +++ b/examples/connect-middleware-custom.js @@ -10,7 +10,10 @@ var requestIp = require('request-ip'); // you can override which attirbute the ip will be set on by // passing in an options object with an attributeName app.use( - requestIp.mw({attributeName: 'myCustomAttributeName', pr: ['x-real-ip']}), + requestIp.mw({ + attributeName: 'myCustomAttributeName', + prioritize: ['x-real-ip'], + }), ); // respond to all requests