-
Notifications
You must be signed in to change notification settings - Fork 50
/
Copy pathproxy.js
119 lines (102 loc) · 3.14 KB
/
proxy.js
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import rateLimit from 'express-rate-limit';
import { request as httpsRequest } from 'https';
import { URL } from 'url';
import net from 'net';
import dns from 'dns/promises';
function isLocalDomain(hostname) {
const localDomains = ['localhost', '127.0.0.1', '::1'];
const localSuffixes = ['.localhost', '.local', '.internal'];
if (localDomains.includes(hostname.toLowerCase())) {
return true;
}
return localSuffixes.some(suffix => hostname.endsWith(suffix));
}
function isValidHost(hostname) {
return !isLocalDomain(hostname) && net.isIP(hostname) === 0;
}
function isPrivateIp(ip) {
if (net.isIPv4(ip)) {
const parts = ip.split('.').map(Number);
// 10.0.0.0/8
if (parts[0] === 10) return true;
// 172.16.0.0/12
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
// 192.168.0.0/16
if (parts[0] === 192 && parts[1] === 168) return true;
// 127.0.0.0/8 (localhost)
if (parts[0] === 127) return true;
}
if (net.isIPv6(ip)) {
const lowerIp = ip.toLowerCase();
// fc00::/7 (unique local addresses)
if (lowerIp.startsWith('fc') || lowerIp.startsWith('fd')) return true;
// fe80::/10 (link-local addresses)
if (lowerIp.startsWith('fe80:')) return true;
// ::1/128 (localhost)
if (lowerIp === '::1') return true;
}
return false;
}
// Perform DNS resolution to check for private IPs
async function isPrivateAddress(hostname) {
try {
const addresses = await dns.lookup(hostname, { all: true });
for (const addr of addresses) {
if (isPrivateIp(addr.address)) {
return true;
}
}
return false;
} catch (err) {
return true;
}
}
// Rate limiter: Max 100 requests per 1 minutes per IP
const proxyRateLimiter = rateLimit({
windowMs: 1 * 60 * 1000,
max: 100,
message: 'Too many requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
async function proxyHandler(req, res) {
const targetUrl = req.query.url;
if (!targetUrl) {
return res.status(400).send('Bad Request');
}
try {
const parsedUrl = new URL(targetUrl);
if (parsedUrl.protocol !== 'https:') {
return res.status(403).send('Request Forbidden');
}
if (isValidHost(parsedUrl.hostname)) {
return res.status(403).send('Request Forbidden');
}
if (await isPrivateAddress(parsedUrl.hostname)) {
return res.status(403).send('Request Forbidden');
}
const options = {
hostname: parsedUrl.hostname,
port: parsedUrl.port || 443,
path: parsedUrl.pathname + parsedUrl.search,
method: req.method,
headers: { ...req.headers, host: parsedUrl.host },
timeout: 30000,
};
const proxyReq = httpsRequest(options, proxyRes => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
proxyReq.on('error', () => {
res.status(502).send('An error occurred while making the proxy request.');
});
if (Buffer.isBuffer(req.body)) {
proxyReq.end(req.body);
} else {
req.pipe(proxyReq);
}
} catch (error) {
res.status(400).send('Bad Request');
}
}
export { proxyHandler, proxyRateLimiter };