Skip to content

Commit

Permalink
feat: remove allowlist and denylist
Browse files Browse the repository at this point in the history
  • Loading branch information
DIYgod committed Mar 2, 2024
1 parent c91c46d commit 0ab8996
Show file tree
Hide file tree
Showing 9 changed files with 11 additions and 308 deletions.
6 changes: 0 additions & 6 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ export type Config = {
proxyStrategy: string;
pacUri?: string;
pacScript?: string;
denylist?: string[];
allowlist?: string[];
allowLocalhost: boolean;
accessKey?: string;
debugInfo: string;
loggerLevel: string;
Expand Down Expand Up @@ -373,9 +370,6 @@ const calculateValue = () => {
pacUri: envs.PAC_URI,
pacScript: envs.PAC_SCRIPT,
// access control
denylist: envs.DENYLIST ? envs.DENYLIST.split(',') : undefined,
allowlist: envs.ALLOWLIST ? envs.ALLOWLIST.split(',') : undefined,
allowLocalhost: toBoolean(envs.ALLOW_LOCALHOST, false),
accessKey: envs.ACCESS_KEY,
// logging
// 是否显示 Debug 信息,取值 'true' 'false' 'some_string' ,取值为 'true' 时永久显示,取值为 'false' 时永远隐藏,取值为 'some_string' 时请求带上 '?debug=some_string' 显示
Expand Down
185 changes: 2 additions & 183 deletions lib/middleware/access-control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,190 +10,11 @@ async function checkBlock(response) {

afterEach(() => {
delete process.env.ACCESS_KEY;
delete process.env.DENYLIST;
delete process.env.ALLOWLIST;
jest.resetModules();
});

describe('access-control', () => {
it(`denylist`, async () => {
const key = '1L0veRSSHub';
const code = md5('/test/2' + key);
process.env.DENYLIST = 'est/1,233.233.233.,black';
process.env.ACCESS_KEY = key;
const app = (await import('@/app')).default;

const response11 = await app.request('/test/1');
await checkBlock(response11);

const response12 = await app.request('/test/1', {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
await checkBlock(response12);

const response13 = await app.request('/test/1', {
headers: {
'user-agent': 'blackua',
},
});
await checkBlock(response13);

const response21 = await app.request('/test/2');
expect(response21.status).toBe(200);

const response22 = await app.request('/test/2', {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
await checkBlock(response22);

const response23 = await app.request('/test/2', {
headers: {
'user-agent': 'blackua',
},
});
await checkBlock(response23);

// wrong key/code, not on denylist
const response311 = await app.request(`/test/2?key=wrong+${key}`);
expect(response311.status).toBe(200);

const response312 = await app.request(`/test/2?code=wrong+${code}`);
expect(response312.status).toBe(200);

// wrong key/code, on denylist
const response321 = await app.request(`/test/2?key=wrong+${key}`, {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
await checkBlock(response321);

const response322 = await app.request(`/test/2?code=wrong+${code}`, {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
await checkBlock(response322);

// right key/code, on denylist
const response331 = await app.request(`/test/2?key=${key}`, {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
expect(response331.status).toBe(200);

const response332 = await app.request(`/test/2?code=${code}`, {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
expect(response332.status).toBe(200);
});

it(`allowlist`, async () => {
const key = '1L0veRSSHub';
const code = md5('/test/2' + key);
process.env.ALLOWLIST = 'est/1,233.233.233.,103.31.4.0/22,white';
process.env.ACCESS_KEY = key;
const app = (await import('@/app')).default;

const response01 = await app.request('/');
expect(response01.status).toBe(200);

const response02 = await app.request('/robots.txt');
expect(response02.status).toBe(404);

const response11 = await app.request('/test/1');
expect(response11.status).toBe(200);

const response12 = await app.request('/test/1', {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
expect(response12.status).toBe(200);

const response13 = await app.request('/test/1', {
headers: {
'user-agent': 'whiteua',
},
});
expect(response13.status).toBe(200);

const response21 = await app.request('/test/2');
await checkBlock(response21);

const response22 = await app.request('/test/2', {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
expect(response22.status).toBe(200);

const response221 = await app.request('/test/2', {
headers: {
'X-Mock-IP': '103.31.4.0',
},
});
expect(response221.status).toBe(200);

const response222 = await app.request('/test/2', {
headers: {
'X-Mock-IP': '103.31.7.255',
},
});
expect(response222.status).toBe(200);

const response223 = await app.request('/test/2', {
headers: {
'X-Mock-IP': '103.31.8.0',
},
});
await checkBlock(response223);

const response23 = await app.request('/test/2', {
headers: {
'user-agent': 'whiteua',
},
});
expect(response23.status).toBe(200);

// wrong key/code, not on allowlist
const response311 = await app.request(`/test/2?code=wrong+${code}`);
await checkBlock(response311);

const response312 = await app.request(`/test/2?key=wrong+${key}`);
await checkBlock(response312);

// wrong key/code, on allowlist
const response321 = await app.request(`/test/2?code=wrong+${code}`, {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
expect(response321.status).toBe(200);

const response322 = await app.request(`/test/2?key=wrong+${key}`, {
headers: {
'X-Mock-IP': '233.233.233.233',
},
});
expect(response322.status).toBe(200);

// right key/code
const response331 = await app.request(`/test/2?code=${code}`);
expect(response331.status).toBe(200);

const response332 = await app.request(`/test/2?key=${key}`);
expect(response332.status).toBe(200);
});

it(`no list`, async () => {
it(`access key`, async () => {
const key = '1L0veRSSHub';
const code = md5('/test/2' + key);
process.env.ACCESS_KEY = key;
Expand All @@ -205,9 +26,7 @@ describe('access-control', () => {
const response02 = await app.request('/robots.txt');
expect(response02.status).toBe(404);

const response11 = await app.request('/test/1');
await checkBlock(response11);

// no key/code
const response21 = await app.request('/test/2');
await checkBlock(response21);

Expand Down
54 changes: 3 additions & 51 deletions lib/middleware/access-control.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,24 @@
import type { MiddlewareHandler } from 'hono';
import { config } from '@/config';
import md5 from '@/utils/md5';
import ipUtils from 'ip';
import { getIp } from '@/utils/helpers';
import RejectError from '@/errors/reject';

const reject = () => {
throw new RejectError('Authentication failed. Access denied.');
};

const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
const cidrPattern = /((?:\d{1,3}\.){3}\d{1,3})\/(\d{1,2})/;

const ipInCidr = (cidr: string, ip: string) => {
const cidrMatch = cidr.match(cidrPattern);
const ipMatch = ip.match(ipv4Pattern);
if (!cidrMatch || !ipMatch) {
return false;
}
const subnetMask = Number.parseInt(cidrMatch[2]);
const cidrIpBits = ipv4ToBitsring(cidrMatch[1]).substring(0, subnetMask);
const ipBits = ipv4ToBitsring(ip).substring(0, subnetMask);
return cidrIpBits === ipBits;
};

const ipv4ToBitsring = (ip: string) =>
ip
.split('.')
.map((part) => ('00000000' + Number.parseInt(part).toString(2)).slice(-8))
.join('');

const middleware: MiddlewareHandler = async (ctx, next) => {
const ip = getIp(ctx);
const requestPath = ctx.req.path;
const requestUA = ctx.req.header('user-agent');
const accessKey = ctx.req.query('key');
const accessCode = ctx.req.query('code');

const isControlled = config.accessKey || config.allowlist || config.denylist;

const allowLocalhost = config.allowLocalhost && ip && ipUtils.isPrivate(ip);

const grant = async () => {
if (ctx.res.status !== 403) {
await next();
}
};

if (requestPath === '/' || requestPath === '/robots.txt') {
await next();
} else {
if (!isControlled || allowLocalhost) {
return grant();
if (config.accessKey && !(config.accessKey === accessKey || accessCode === md5(requestPath + config.accessKey))) {
return reject();
}

if (config.accessKey && (config.accessKey === accessKey || accessCode === md5(requestPath + config.accessKey))) {
return grant();
}

if (config.allowlist && config.allowlist.some((item) => ip?.includes(item) || (ip && ipInCidr(item, ip)) || requestPath.includes(item) || requestUA?.includes(item))) {
return grant();
}

if (config.denylist && !config.denylist.some((item) => ip?.includes(item) || (ip && ipInCidr(item, ip)) || requestPath.includes(item) || requestUA?.includes(item))) {
return grant();
}

reject();
await next();
}
};

Expand Down
4 changes: 2 additions & 2 deletions lib/middleware/logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MiddlewareHandler } from 'hono';
import logger from '@/utils/logger';
import { getIp, getPath, time } from '@/utils/helpers';
import { getPath, time } from '@/utils/helpers';

enum LogPrefix {
Outgoing = '-->',
Expand Down Expand Up @@ -28,7 +28,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
const { method, raw } = ctx.req;
const path = getPath(raw);

logger.info(`${LogPrefix.Incoming} ${method} ${path} from ${getIp(ctx)}`);
logger.info(`${LogPrefix.Incoming} ${method} ${path}`);

const start = Date.now();

Expand Down
8 changes: 0 additions & 8 deletions lib/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
import { Context } from 'hono';
import { Ipware } from '@fullerstack/nax-ipware';
import { config } from '@/config';

const ipware = new Ipware();

export const getRouteNameFromPath = (path: string) => {
const p = path.split('/').filter(Boolean);
if (p.length > 0) {
Expand All @@ -12,8 +6,6 @@ export const getRouteNameFromPath = (path: string) => {
return null;
};

export const getIp = (ctx: Context) => (config.nodeName === 'mock' && ctx.req.header('X-Mock-IP') ? ctx.req.header('X-Mock-IP') : ipware.getClientIP(ctx.req.raw)?.ip);

export const getPath = (request: Request): string => {
// Optimized: RegExp is faster than indexOf() + slice()
const match = request.url.match(/^https?:\/\/[^/]+(\/[^?]*)/);
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@
"outputDirectory": "coverage"
},
"dependencies": {
"@fullerstack/nax-ipware": "0.10.0",
"@hono/node-server": "1.8.2",
"@koa/router": "12.0.1",
"@notionhq/client": "2.2.14",
Expand Down Expand Up @@ -108,7 +107,6 @@
"imapflow": "1.0.153",
"instagram-private-api": "1.45.3",
"ioredis": "5.3.2",
"ip": "2.0.1",
"ip-regex": "5.0.0",
"jsdom": "24.0.0",
"json-bigint": "1.0.0",
Expand Down
Loading

0 comments on commit 0ab8996

Please sign in to comment.