diff --git a/lib/middleware/access-control.js b/lib/middleware/access-control.js index c2374340ef6d27..27736268918d50 100644 --- a/lib/middleware/access-control.js +++ b/lib/middleware/access-control.js @@ -8,6 +8,27 @@ const reject = (ctx) => { throw Error('Authentication failed. Access denied.'); }; +const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/; +const cidrPattern = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})/; + +const ipInCidr = (cidr, ip) => { + const cidrMatch = cidr.match(cidrPattern); + const ipMatch = ip.match(ipv4Pattern); + if (!cidrMatch || !ipMatch) { + return false; + } + const subnetMask = parseInt(cidrMatch[2]); + const cidrIpBits = ipv4ToBitsring(cidrMatch[1]).substring(0, subnetMask); + const ipBits = ipv4ToBitsring(ip).substring(0, subnetMask); + return cidrIpBits === ipBits; +}; + +const ipv4ToBitsring = (ip) => + ip + .split('.') + .map((part) => ('00000000' + parseInt(part).toString(2)).slice(-8)) + .join(''); + module.exports = async (ctx, next) => { const ip = ctx.ips[0] || ctx.ip; const requestPath = ctx.request.path; @@ -39,13 +60,13 @@ module.exports = async (ctx, next) => { } if (config.allowlist) { - if (config.allowlist.find((item) => ip.includes(item) || requestPath.includes(item) || requestUA.includes(item))) { + if (config.allowlist.find((item) => ip.includes(item) || ipInCidr(item, ip) || requestPath.includes(item) || requestUA.includes(item))) { return grant(); } } if (config.denylist) { - if (!config.denylist.find((item) => ip.includes(item) || requestPath.includes(item) || requestUA.includes(item))) { + if (!config.denylist.find((item) => ip.includes(item) || ipInCidr(item, ip) || requestPath.includes(item) || requestUA.includes(item))) { return grant(); } } diff --git a/test/middleware/access-control.js b/test/middleware/access-control.js index 430a38c610fca1..fff9c1f1ba4d4c 100644 --- a/test/middleware/access-control.js +++ b/test/middleware/access-control.js @@ -68,7 +68,7 @@ describe('access-control', () => { it(`allowlist`, async () => { const key = '1L0veRSSHub'; const code = md5('/test/2' + key); - process.env.ALLOWLIST = 'est/1,233.233.233.,white'; + process.env.ALLOWLIST = 'est/1,233.233.233.,103.31.4.0/22,white'; process.env.ACCESS_KEY = key; server = require('../../lib/index'); const request = supertest(server); @@ -94,6 +94,15 @@ describe('access-control', () => { const response22 = await request.get('/test/2').set('X-Forwarded-For', '233.233.233.233'); expect(response22.status).toBe(200); + const response221 = await request.get('/test/2').set('X-Forwarded-For', '103.31.4.0'); + expect(response221.status).toBe(200); + + const response222 = await request.get('/test/2').set('X-Forwarded-For', '103.31.7.255'); + expect(response222.status).toBe(200); + + const response223 = await request.get('/test/2').set('X-Forwarded-For', '103.31.8.0'); + checkBlock(response223); + const response23 = await request.get('/test/2').set('user-agent', 'whiteua'); expect(response23.status).toBe(200);