-
-
Notifications
You must be signed in to change notification settings - Fork 31
/
ip.ts
81 lines (63 loc) · 2.51 KB
/
ip.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import type { CustomHelpers, ErrorReport } from 'joi';
import got from 'got';
import validator from 'validator';
import ipaddr from 'ipaddr.js';
export const sourceList = [
'https://osint.digitalside.it/Threat-Intel/lists/latestips.txt',
'https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level2.netset',
'https://raw.githubusercontent.com/stamparm/ipsum/master/levels/2.txt',
'https://www.spamhaus.org/drop/dropv6.txt',
'https://lists.blocklist.de/lists/all.txt',
];
export const ipListPath = path.join(path.resolve(), 'data/IP_BLACKLIST.json');
let ipList = new Set<string>();
let ipListCIDR = new Set<ReturnType<typeof ipaddr.parseCIDR>>();
const isFulfilled = <T>(input: PromiseSettledResult<T>): input is PromiseFulfilledResult<T> => input.status === 'fulfilled';
export const query = async (url: string): Promise<string[]> => {
const { body } = await got(url, {
timeout: { request: 5000 },
});
return body
.split(/\r?\n/)
.map(l => l.split(' ')[0] ?? '')
.filter(l => validator.isIP(l.split('/')[0] ?? ''));
};
export const populateMemList = async (): Promise<void> => {
const data = JSON.parse(await readFile(ipListPath, 'utf8')) as string[];
ipList = new Set(data.filter(address => ipaddr.isValid(address)));
ipListCIDR = new Set(data.filter(address => ipaddr.isValidCIDR(address)).map(address => ipaddr.parseCIDR(address)));
};
export const updateList = async (): Promise<void> => {
const result = await Promise.allSettled(sourceList.map(source => query(source)));
const ipList = [ ...new Set(result.flatMap(r => isFulfilled(r) ? r.value : [])) ];
await writeFile(ipListPath, JSON.stringify(ipList), { encoding: 'utf8' });
};
function isContainedWithinSubnet (target: string, ipList: Set<ReturnType<typeof ipaddr.parseCIDR>>): boolean {
const targetAddr = ipaddr.parse(target);
for (const listAddr of ipList) {
if (targetAddr.kind() === listAddr[0].kind()) {
if (targetAddr.match(listAddr)) {
return true;
}
}
}
return false;
}
export const validate = (target: string): boolean => {
if (!validator.isIP(target)) {
return true;
}
return !ipList.has(target) && !isContainedWithinSubnet(target, ipListCIDR);
};
export const joiValidate = (value: string, helpers?: CustomHelpers): string | ErrorReport | Error => {
const isValid = validate(value);
if (!isValid) {
if (helpers) {
return helpers.error('ip.blacklisted');
}
throw new Error('ip.blacklisted');
}
return String(value);
};