diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..cce0279 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +package.json +package-lock.json diff --git a/README.md b/README.md index 0a55494..3793f31 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,47 @@ # cloudflare-middleware -Restores request origin ip to `req.origin_ip`. Denies other requests. +Allows requests which originate from Cloudflare. Restores request origin IP +to `req.origin_ip`. Denies other requests. ## Usage -```nodejs -let app = express(); -app.use(require("cloudflare-middleware")()); +Using [Express trust proxy][trust proxy] (preferred): + +```js +const { trustProxy } = require('cloudflare-middleware') + +const app = express() +app.set('trust proxy', trustProxy) +``` + +Using Express middleware: + +```js +const { middleware } = require('cloudflare-middleware') + +const app = express() +app.use(middleware()) ``` -This is a safer alternative to naively using `app.set("trust proxy");`, as this checks CloudFlare IP address ranges. +Using with another framework: + +```js +const http = require('http') +const { isCloudflare } = require('cloudflare-middleware') + +http.createServer((req, res) => { + if (!isCloudflare(req.socket.remoteAddress)) { + response.end() + } + + // Proceed with the response. + response.write('oh, you good') + response.end() +}) +``` -This, however, has been deprecated in favor of the following: +[trust proxy]: https://expressjs.com/en/guide/behind-proxies.html -```nodejs -const cloudflareIp = require('cloudflare-ip'); +## License -app.set('trust proxy', ip => { - if(ip.startsWith('::ffff:')) - ip = ip.substr(7); - return ip == '127.0.0.1' || cloudflareIp(ip); -}); +This project is licensed under the ISC license. diff --git a/index.js b/index.js index a15d4a9..3034691 100644 --- a/index.js +++ b/index.js @@ -1,16 +1,32 @@ -const rangeCheck = require("range_check"); -const ranges = require("./ranges"); +const rangeCheck = require('range_check') +const ranges = require('./ranges') -module.exports = function(options){ - return function(req, res, next) { - ip = req.ip; - if(ip.startsWith("::ffff:")) - ip = ip.substr(7); - if(rangeCheck.inRange(ip, ranges)){ - req.origin_ip = req.headers['cf-connecting-ip']; - next(); - }else{ - res.end(); +function trimIp(ip) { + return ip.startsWith('::ffff:') ? ip.substr(7) : ip +} + +function isCloudflare(ip) { + return rangeCheck.inRange(ip, ranges) +} + +function trustProxy(ip) { + const trimmed = trimIp(ip) + return trimmed == '127.0.0.1' || isCloudflare(trimmed) +} + +function middleware(options) { + return function (req, res, next) { + if (isCloudflare(trimIp(req.ip))) { + req.origin_ip = req.headers['cf-connecting-ip'] + next() + } else { + res.end() } - }; -}; + } +} + +module.exports = { + isCloudflare, + middleware, + trustProxy, +} diff --git a/package.json b/package.json index b8013e3..f8bd0ad 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,20 @@ "devDependencies": { "chai": "^4.2.0", "got": "^11.7.0", - "mocha": "^8.1.3" + "mocha": "^8.1.3", + "prettier": "^2.1.2" }, "scripts": { - "test": "mocha ranges.integration.js" + "format": "prettier --check \"**/*.@(js|md|json)\"", + "format:fix": "prettier --write \"**/*.@(js|md|json)\"", + "mocha": "mocha ranges.integration.js", + "test": "npm run mocha && npm run format" + }, + "prettier": { + "semi": false, + "singleQuote": true, + "bracketSpacing": true, + "arrowParens": "avoid" }, "repository": { "type": "git", diff --git a/ranges.integration.js b/ranges.integration.js index e20c160..68550e8 100644 --- a/ranges.integration.js +++ b/ranges.integration.js @@ -1,25 +1,25 @@ -const got = require("got"); -const { expect } = require("chai"); -const ranges = require("./ranges"); +const got = require('got') +const { expect } = require('chai') +const ranges = require('./ranges') async function collectAdvertisedRanges() { - let result = []; + let result = [] for (const url of [ - "https://www.cloudflare.com/ips-v4", - "https://www.cloudflare.com/ips-v6", + 'https://www.cloudflare.com/ips-v4', + 'https://www.cloudflare.com/ips-v6', ]) { - const { body } = await got(url); - const theseRanges = body.split("\n").filter(Boolean); - result = result.concat(theseRanges); + const { body } = await got(url) + const theseRanges = body.split('\n').filter(Boolean) + result = result.concat(theseRanges) } - return result; + return result } -describe("Ranges", function () { +describe('Ranges', function () { it("match Cloudflare's advertised ranges", async function () { - const advertisedRanges = await collectAdvertisedRanges(); + const advertisedRanges = await collectAdvertisedRanges() - expect(ranges).to.have.members(advertisedRanges); - expect(advertisedRanges).to.have.members(ranges); - }); -}); + expect(ranges).to.have.members(advertisedRanges) + expect(advertisedRanges).to.have.members(ranges) + }) +}) diff --git a/ranges.js b/ranges.js index 27059cd..1cadbb1 100644 --- a/ranges.js +++ b/ranges.js @@ -1,25 +1,25 @@ module.exports = [ -// From https://www.cloudflare.com/ips-v4 - "103.21.244.0/22", - "103.22.200.0/22", - "103.31.4.0/22", - "104.16.0.0/12", - "108.162.192.0/18", - "131.0.72.0/22", - "141.101.64.0/18", - "162.158.0.0/15", - "172.64.0.0/13", - "173.245.48.0/20", - "188.114.96.0/20", - "190.93.240.0/20", - "197.234.240.0/22", - "198.41.128.0/17", -// From https://www.cloudflare.com/ips-v6 - "2400:cb00::/32", - "2405:8100::/32", - "2405:b500::/32", - "2606:4700::/32", - "2803:f800::/32", - "2c0f:f248::/32", - "2a06:98c0::/29", -]; + // From https://www.cloudflare.com/ips-v4 + '103.21.244.0/22', + '103.22.200.0/22', + '103.31.4.0/22', + '104.16.0.0/12', + '108.162.192.0/18', + '131.0.72.0/22', + '141.101.64.0/18', + '162.158.0.0/15', + '172.64.0.0/13', + '173.245.48.0/20', + '188.114.96.0/20', + '190.93.240.0/20', + '197.234.240.0/22', + '198.41.128.0/17', + // From https://www.cloudflare.com/ips-v6 + '2400:cb00::/32', + '2405:8100::/32', + '2405:b500::/32', + '2606:4700::/32', + '2803:f800::/32', + '2c0f:f248::/32', + '2a06:98c0::/29', +] diff --git a/yarn.lock b/yarn.lock index 5a18aa2..a88b091 100644 --- a/yarn.lock +++ b/yarn.lock @@ -851,6 +851,11 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +prettier@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" + integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== + promise.allsettled@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.2.tgz#d66f78fbb600e83e863d893e98b3d4376a9c47c9"