diff --git a/lib/utils.js b/lib/utils.js index aae7c9c..5452816 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,9 @@ 'use strict'; var crypto = require('crypto'); +var parseForwarded = require('forwarded-parse'); +var ipaddr = require('ipaddr.js'); +var _ = require('underscore'); exports.hookNameMapping = { beforeSave: '__before_save_for_', @@ -70,9 +73,37 @@ exports.prepareResponseObject = function(res, callback) { }; var getRemoteAddress = exports.getRemoteAddress = function(req) { - return req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress + var forwardedClient = exports.getForwardedClient(req) + + if (forwardedClient) { + return forwardedClient.for + } else { + return req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress + } }; exports.endsWith = function(str, suffix) { return str.indexOf(suffix, str.length - suffix.length) !== -1; }; + +exports.getForwardedClient = function getForwardedClient(req) { + if (req.headers['forwarded']) { + try { + const forwards = parseForwarded(req.headers['forwarded']).reverse() + + for (var i = 0; i < forwards.length; i++) { + if (!forwards[i].for) { + return + } + + var range = ipaddr.parse(forwards[i].for).range() + + if (!_.include(['loopback', 'private'], range) || i === forwards.length - 1) { + return _.extend(forwards[i], {range: range}) + } + } + } catch (err) { + console.error('LeanEngine: parse Forwarded header failed', req.headers['forwarded'], err.stack) + } + } +} diff --git a/package-lock.json b/package-lock.json index 20ee68c..7f3ce0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -757,6 +757,11 @@ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", "dev": true }, + "forwarded-parse": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.0.tgz", + "integrity": "sha512-as9a7Xelt0CvdUy7/qxrY73dZq2vMx49F556fwjjFrUyzq5uHHfeLgD2cCq/6P4ZvusGZzjD6aL2NdgGdS5Cew==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -884,8 +889,7 @@ "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, "is-arrayish": { "version": "0.2.1", diff --git a/package.json b/package.json index d60e747..9081512 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "connect-timeout": "^1.8.0", "cookies": "^0.6.2", "debug": "^2.6.0", + "forwarded-parse": "^2.1.0", + "ipaddr.js": "^1.9.1", "leancloud-cors-headers": "^0.1.0", "on-headers": "^1.0.1", "underscore": "^1.8.3" diff --git a/test/fixtures/functions.js b/test/fixtures/functions.js index 0266c5a..2a6b52e 100644 --- a/test/fixtures/functions.js +++ b/test/fixtures/functions.js @@ -231,6 +231,10 @@ AV.Cloud.define('testTimeout', function(req, res) { }, req.params.delay); }); +AV.Cloud.define('remoteAddress', function(request) { + return request.meta.remoteAddress +}) + AV.Cloud.onIMMessageReceived(function(request, response) { response.success('ok'); }); diff --git a/test/function-test.js b/test/function-test.js index 7636ee1..037ff9c 100644 --- a/test/function-test.js +++ b/test/function-test.js @@ -452,6 +452,31 @@ describe('functions', function() { }); }); + it('remoteAddress', function(done) { + request(app) + .post('/1.1/functions/remoteAddress') + .set('X-AVOSCloud-Application-Id', appId) + .set('X-AVOSCloud-Application-Key', appKey) + .set('Forwarded', 'for=1.2.3.4; proto=https, for=10.0.0.1') + .expect(200, (err, res) => { + res.body.result.should.equal('1.2.3.4') + done(err) + }); + }) + + it('remoteAddress invalid Forwarded header', function(done) { + request(app) + .post('/1.1/functions/remoteAddress') + .set('X-AVOSCloud-Application-Id', appId) + .set('X-AVOSCloud-Application-Key', appKey) + .set('Forwarded', 'for=1.2.3.456; proto=https, for=10.0.0.1') + .set('X-Real-IP', '5.6.7.8') + .expect(200, (err, res) => { + res.body.result.should.equal('5.6.7.8') + done(err) + }); + }) + it('_metadatas', function(done) { request(app) .get('/1/functions/_ops/metadatas')