@push.rocks/smartproxy 16.0.2 → 16.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/models/index.d.ts +2 -0
- package/dist_ts/core/models/index.js +3 -1
- package/dist_ts/core/models/route-context.d.ts +62 -0
- package/dist_ts/core/models/route-context.js +43 -0
- package/dist_ts/core/models/socket-augmentation.d.ts +12 -0
- package/dist_ts/core/models/socket-augmentation.js +18 -0
- package/dist_ts/core/utils/event-system.d.ts +200 -0
- package/dist_ts/core/utils/event-system.js +224 -0
- package/dist_ts/core/utils/index.d.ts +7 -0
- package/dist_ts/core/utils/index.js +8 -1
- package/dist_ts/core/utils/route-manager.d.ts +118 -0
- package/dist_ts/core/utils/route-manager.js +383 -0
- package/dist_ts/core/utils/route-utils.d.ts +94 -0
- package/dist_ts/core/utils/route-utils.js +264 -0
- package/dist_ts/core/utils/security-utils.d.ts +111 -0
- package/dist_ts/core/utils/security-utils.js +212 -0
- package/dist_ts/core/utils/shared-security-manager.d.ts +110 -0
- package/dist_ts/core/utils/shared-security-manager.js +252 -0
- package/dist_ts/core/utils/template-utils.d.ts +37 -0
- package/dist_ts/core/utils/template-utils.js +104 -0
- package/dist_ts/core/utils/websocket-utils.d.ts +23 -0
- package/dist_ts/core/utils/websocket-utils.js +86 -0
- package/dist_ts/http/router/index.d.ts +5 -1
- package/dist_ts/http/router/index.js +4 -2
- package/dist_ts/http/router/route-router.d.ts +108 -0
- package/dist_ts/http/router/route-router.js +393 -0
- package/dist_ts/index.d.ts +8 -2
- package/dist_ts/index.js +10 -3
- package/dist_ts/proxies/index.d.ts +7 -2
- package/dist_ts/proxies/index.js +10 -4
- package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +21 -0
- package/dist_ts/proxies/network-proxy/certificate-manager.js +92 -1
- package/dist_ts/proxies/network-proxy/context-creator.d.ts +34 -0
- package/dist_ts/proxies/network-proxy/context-creator.js +108 -0
- package/dist_ts/proxies/network-proxy/function-cache.d.ts +90 -0
- package/dist_ts/proxies/network-proxy/function-cache.js +198 -0
- package/dist_ts/proxies/network-proxy/http-request-handler.d.ts +40 -0
- package/dist_ts/proxies/network-proxy/http-request-handler.js +256 -0
- package/dist_ts/proxies/network-proxy/http2-request-handler.d.ts +24 -0
- package/dist_ts/proxies/network-proxy/http2-request-handler.js +201 -0
- package/dist_ts/proxies/network-proxy/models/types.d.ts +73 -1
- package/dist_ts/proxies/network-proxy/models/types.js +242 -1
- package/dist_ts/proxies/network-proxy/network-proxy.d.ts +23 -20
- package/dist_ts/proxies/network-proxy/network-proxy.js +147 -60
- package/dist_ts/proxies/network-proxy/request-handler.d.ts +38 -5
- package/dist_ts/proxies/network-proxy/request-handler.js +584 -198
- package/dist_ts/proxies/network-proxy/security-manager.d.ts +65 -0
- package/dist_ts/proxies/network-proxy/security-manager.js +255 -0
- package/dist_ts/proxies/network-proxy/websocket-handler.d.ts +13 -2
- package/dist_ts/proxies/network-proxy/websocket-handler.js +238 -20
- package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/index.js +3 -3
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +3 -5
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +56 -3
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +4 -57
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +19 -228
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +81 -0
- package/dist_ts/proxies/smart-proxy/port-manager.js +166 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +5 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +131 -15
- package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +3 -1
- package/dist_ts/proxies/smart-proxy/route-helpers/index.js +5 -3
- package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +5 -178
- package/dist_ts/proxies/smart-proxy/route-helpers.js +8 -296
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +11 -2
- package/dist_ts/proxies/smart-proxy/route-manager.js +79 -10
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +29 -2
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +48 -43
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +67 -1
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +120 -1
- package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +3 -3
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +27 -5
- package/package.json +1 -1
- package/readme.md +102 -14
- package/readme.plan.md +103 -168
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/models/index.ts +2 -0
- package/ts/core/models/route-context.ts +113 -0
- package/ts/core/models/socket-augmentation.ts +33 -0
- package/ts/core/utils/event-system.ts +376 -0
- package/ts/core/utils/index.ts +7 -0
- package/ts/core/utils/route-manager.ts +489 -0
- package/ts/core/utils/route-utils.ts +312 -0
- package/ts/core/utils/security-utils.ts +309 -0
- package/ts/core/utils/shared-security-manager.ts +333 -0
- package/ts/core/utils/template-utils.ts +124 -0
- package/ts/core/utils/websocket-utils.ts +81 -0
- package/ts/http/router/index.ts +8 -1
- package/ts/http/router/route-router.ts +482 -0
- package/ts/index.ts +14 -2
- package/ts/proxies/index.ts +12 -3
- package/ts/proxies/network-proxy/certificate-manager.ts +114 -10
- package/ts/proxies/network-proxy/context-creator.ts +145 -0
- package/ts/proxies/network-proxy/function-cache.ts +259 -0
- package/ts/proxies/network-proxy/http-request-handler.ts +330 -0
- package/ts/proxies/network-proxy/http2-request-handler.ts +255 -0
- package/ts/proxies/network-proxy/models/types.ts +312 -1
- package/ts/proxies/network-proxy/network-proxy.ts +195 -86
- package/ts/proxies/network-proxy/request-handler.ts +698 -246
- package/ts/proxies/network-proxy/security-manager.ts +298 -0
- package/ts/proxies/network-proxy/websocket-handler.ts +276 -33
- package/ts/proxies/smart-proxy/index.ts +2 -12
- package/ts/proxies/smart-proxy/models/interfaces.ts +7 -4
- package/ts/proxies/smart-proxy/models/route-types.ts +78 -10
- package/ts/proxies/smart-proxy/network-proxy-bridge.ts +20 -257
- package/ts/proxies/smart-proxy/port-manager.ts +195 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +156 -21
- package/ts/proxies/smart-proxy/route-manager.ts +98 -14
- package/ts/proxies/smart-proxy/smart-proxy.ts +56 -55
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +167 -1
- package/ts/proxies/smart-proxy/utils/route-validators.ts +24 -5
- package/ts/proxies/smart-proxy/domain-config-manager.ts.bak +0 -441
- package/ts/proxies/smart-proxy/route-helpers/index.ts +0 -9
- package/ts/proxies/smart-proxy/route-helpers.ts +0 -498
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route matching utilities for SmartProxy components
|
|
3
|
+
*
|
|
4
|
+
* Contains shared logic for domain matching, path matching, and IP matching
|
|
5
|
+
* to be used by different proxy components throughout the system.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Match a domain pattern against a domain
|
|
9
|
+
*
|
|
10
|
+
* @param pattern Domain pattern with optional wildcards (e.g., "*.example.com")
|
|
11
|
+
* @param domain Domain to match against the pattern
|
|
12
|
+
* @returns Whether the domain matches the pattern
|
|
13
|
+
*/
|
|
14
|
+
export function matchDomain(pattern, domain) {
|
|
15
|
+
// Handle exact match (case-insensitive)
|
|
16
|
+
if (pattern.toLowerCase() === domain.toLowerCase()) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
// Handle wildcard pattern
|
|
20
|
+
if (pattern.includes('*')) {
|
|
21
|
+
const regexPattern = pattern
|
|
22
|
+
.replace(/\./g, '\\.') // Escape dots
|
|
23
|
+
.replace(/\*/g, '.*'); // Convert * to .*
|
|
24
|
+
const regex = new RegExp(`^${regexPattern}$`, 'i');
|
|
25
|
+
return regex.test(domain);
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Match domains from a route against a given domain
|
|
31
|
+
*
|
|
32
|
+
* @param domains Array or single domain pattern to match against
|
|
33
|
+
* @param domain Domain to match
|
|
34
|
+
* @returns Whether the domain matches any of the patterns
|
|
35
|
+
*/
|
|
36
|
+
export function matchRouteDomain(domains, domain) {
|
|
37
|
+
// If no domains specified in the route, match all domains
|
|
38
|
+
if (!domains) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// If no domain in the request, can't match domain-specific routes
|
|
42
|
+
if (!domain) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const patterns = Array.isArray(domains) ? domains : [domains];
|
|
46
|
+
return patterns.some(pattern => matchDomain(pattern, domain));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Match a path pattern against a path
|
|
50
|
+
*
|
|
51
|
+
* @param pattern Path pattern with optional wildcards
|
|
52
|
+
* @param path Path to match against the pattern
|
|
53
|
+
* @returns Whether the path matches the pattern
|
|
54
|
+
*/
|
|
55
|
+
export function matchPath(pattern, path) {
|
|
56
|
+
// Handle exact match
|
|
57
|
+
if (pattern === path) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// Handle simple wildcard at the end (like /api/*)
|
|
61
|
+
if (pattern.endsWith('*')) {
|
|
62
|
+
const prefix = pattern.slice(0, -1);
|
|
63
|
+
return path.startsWith(prefix);
|
|
64
|
+
}
|
|
65
|
+
// Handle more complex wildcard patterns
|
|
66
|
+
if (pattern.includes('*')) {
|
|
67
|
+
const regexPattern = pattern
|
|
68
|
+
.replace(/\./g, '\\.') // Escape dots
|
|
69
|
+
.replace(/\*/g, '.*') // Convert * to .*
|
|
70
|
+
.replace(/\//g, '\\/'); // Escape slashes
|
|
71
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
72
|
+
return regex.test(path);
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse CIDR notation into subnet and mask bits
|
|
78
|
+
*
|
|
79
|
+
* @param cidr CIDR string (e.g., "192.168.1.0/24")
|
|
80
|
+
* @returns Object with subnet and bits, or null if invalid
|
|
81
|
+
*/
|
|
82
|
+
export function parseCidr(cidr) {
|
|
83
|
+
try {
|
|
84
|
+
const [subnet, bitsStr] = cidr.split('/');
|
|
85
|
+
const bits = parseInt(bitsStr, 10);
|
|
86
|
+
if (isNaN(bits) || bits < 0 || bits > 32) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return { subnet, bits };
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Convert an IP address to a numeric value
|
|
97
|
+
*
|
|
98
|
+
* @param ip IPv4 address string (e.g., "192.168.1.1")
|
|
99
|
+
* @returns Numeric representation of the IP
|
|
100
|
+
*/
|
|
101
|
+
export function ipToNumber(ip) {
|
|
102
|
+
// Handle IPv6-mapped IPv4 addresses (::ffff:192.168.1.1)
|
|
103
|
+
if (ip.startsWith('::ffff:')) {
|
|
104
|
+
ip = ip.slice(7);
|
|
105
|
+
}
|
|
106
|
+
const parts = ip.split('.').map(part => parseInt(part, 10));
|
|
107
|
+
return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Match an IP against a CIDR pattern
|
|
111
|
+
*
|
|
112
|
+
* @param cidr CIDR pattern (e.g., "192.168.1.0/24")
|
|
113
|
+
* @param ip IP to match against the pattern
|
|
114
|
+
* @returns Whether the IP is in the CIDR range
|
|
115
|
+
*/
|
|
116
|
+
export function matchIpCidr(cidr, ip) {
|
|
117
|
+
const parsed = parseCidr(cidr);
|
|
118
|
+
if (!parsed) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const { subnet, bits } = parsed;
|
|
123
|
+
// Normalize IPv6-mapped IPv4 addresses
|
|
124
|
+
const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
|
|
125
|
+
const normalizedSubnet = subnet.startsWith('::ffff:') ? subnet.substring(7) : subnet;
|
|
126
|
+
// Convert IP addresses to numeric values
|
|
127
|
+
const ipNum = ipToNumber(normalizedIp);
|
|
128
|
+
const subnetNum = ipToNumber(normalizedSubnet);
|
|
129
|
+
// Calculate subnet mask
|
|
130
|
+
const maskNum = ~(2 ** (32 - bits) - 1);
|
|
131
|
+
// Check if IP is in subnet
|
|
132
|
+
return (ipNum & maskNum) === (subnetNum & maskNum);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Match an IP pattern against an IP
|
|
140
|
+
*
|
|
141
|
+
* @param pattern IP pattern (exact, CIDR, or with wildcards)
|
|
142
|
+
* @param ip IP to match against the pattern
|
|
143
|
+
* @returns Whether the IP matches the pattern
|
|
144
|
+
*/
|
|
145
|
+
export function matchIpPattern(pattern, ip) {
|
|
146
|
+
// Normalize IPv6-mapped IPv4 addresses
|
|
147
|
+
const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
|
|
148
|
+
const normalizedPattern = pattern.startsWith('::ffff:') ? pattern.substring(7) : pattern;
|
|
149
|
+
// Handle exact match with all variations
|
|
150
|
+
if (pattern === ip || normalizedPattern === normalizedIp ||
|
|
151
|
+
pattern === normalizedIp || normalizedPattern === ip) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
// Handle "all" wildcard
|
|
155
|
+
if (pattern === '*' || normalizedPattern === '*') {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
// Handle CIDR notation (e.g., 192.168.1.0/24)
|
|
159
|
+
if (pattern.includes('/')) {
|
|
160
|
+
return matchIpCidr(pattern, normalizedIp) ||
|
|
161
|
+
(normalizedPattern !== pattern && matchIpCidr(normalizedPattern, normalizedIp));
|
|
162
|
+
}
|
|
163
|
+
// Handle glob pattern (e.g., 192.168.1.*)
|
|
164
|
+
if (pattern.includes('*')) {
|
|
165
|
+
const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
|
|
166
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
167
|
+
if (regex.test(ip) || regex.test(normalizedIp)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
// If pattern was normalized, also test with normalized pattern
|
|
171
|
+
if (normalizedPattern !== pattern) {
|
|
172
|
+
const normalizedRegexPattern = normalizedPattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
|
|
173
|
+
const normalizedRegex = new RegExp(`^${normalizedRegexPattern}$`);
|
|
174
|
+
return normalizedRegex.test(ip) || normalizedRegex.test(normalizedIp);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Match an IP against allowed and blocked IP patterns
|
|
181
|
+
*
|
|
182
|
+
* @param ip IP to check
|
|
183
|
+
* @param allowedIps Array of allowed IP patterns
|
|
184
|
+
* @param blockedIps Array of blocked IP patterns
|
|
185
|
+
* @returns Whether the IP is allowed
|
|
186
|
+
*/
|
|
187
|
+
export function isIpAuthorized(ip, allowedIps = ['*'], blockedIps = []) {
|
|
188
|
+
// Check blocked IPs first
|
|
189
|
+
if (blockedIps.length > 0) {
|
|
190
|
+
for (const pattern of blockedIps) {
|
|
191
|
+
if (matchIpPattern(pattern, ip)) {
|
|
192
|
+
return false; // IP is blocked
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// If there are allowed IPs, check them
|
|
197
|
+
if (allowedIps.length > 0) {
|
|
198
|
+
// Special case: if '*' is in allowed IPs, all non-blocked IPs are allowed
|
|
199
|
+
if (allowedIps.includes('*')) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
for (const pattern of allowedIps) {
|
|
203
|
+
if (matchIpPattern(pattern, ip)) {
|
|
204
|
+
return true; // IP is allowed
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return false; // IP not in allowed list
|
|
208
|
+
}
|
|
209
|
+
// No allowed IPs specified, so IP is allowed by default
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Match an HTTP header pattern against a header value
|
|
214
|
+
*
|
|
215
|
+
* @param pattern Expected header value (string or RegExp)
|
|
216
|
+
* @param value Actual header value
|
|
217
|
+
* @returns Whether the header matches the pattern
|
|
218
|
+
*/
|
|
219
|
+
export function matchHeader(pattern, value) {
|
|
220
|
+
if (typeof pattern === 'string') {
|
|
221
|
+
return pattern === value;
|
|
222
|
+
}
|
|
223
|
+
else if (pattern instanceof RegExp) {
|
|
224
|
+
return pattern.test(value);
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Calculate route specificity score
|
|
230
|
+
* Higher score means more specific matching criteria
|
|
231
|
+
*
|
|
232
|
+
* @param match Match criteria to evaluate
|
|
233
|
+
* @returns Numeric specificity score
|
|
234
|
+
*/
|
|
235
|
+
export function calculateRouteSpecificity(match) {
|
|
236
|
+
let score = 0;
|
|
237
|
+
// Path is very specific
|
|
238
|
+
if (match.path) {
|
|
239
|
+
// More specific if it doesn't use wildcards
|
|
240
|
+
score += match.path.includes('*') ? 3 : 4;
|
|
241
|
+
}
|
|
242
|
+
// Domain is next most specific
|
|
243
|
+
if (match.domains) {
|
|
244
|
+
const domains = Array.isArray(match.domains) ? match.domains : [match.domains];
|
|
245
|
+
// More domains or more specific domains (without wildcards) increase specificity
|
|
246
|
+
score += domains.length;
|
|
247
|
+
// Add bonus for exact domains (without wildcards)
|
|
248
|
+
score += domains.some(d => !d.includes('*')) ? 1 : 0;
|
|
249
|
+
}
|
|
250
|
+
// Headers are quite specific
|
|
251
|
+
if (match.headers) {
|
|
252
|
+
score += Object.keys(match.headers).length * 2;
|
|
253
|
+
}
|
|
254
|
+
// Client IP adds some specificity
|
|
255
|
+
if (match.clientIp && match.clientIp.length > 0) {
|
|
256
|
+
score += 1;
|
|
257
|
+
}
|
|
258
|
+
// TLS version adds minimal specificity
|
|
259
|
+
if (match.tlsVersion && match.tlsVersion.length > 0) {
|
|
260
|
+
score += 1;
|
|
261
|
+
}
|
|
262
|
+
return score;
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtdXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9jb3JlL3V0aWxzL3JvdXRlLXV0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7OztHQUtHO0FBRUg7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxPQUFlLEVBQUUsTUFBYztJQUN6RCx3Q0FBd0M7SUFDeEMsSUFBSSxPQUFPLENBQUMsV0FBVyxFQUFFLEtBQUssTUFBTSxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUM7UUFDbkQsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsMEJBQTBCO0lBQzFCLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzFCLE1BQU0sWUFBWSxHQUFHLE9BQU87YUFDekIsT0FBTyxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBRSxjQUFjO2FBQ3JDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBRSxrQkFBa0I7UUFFNUMsTUFBTSxLQUFLLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSxZQUFZLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQztRQUNuRCxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDNUIsQ0FBQztJQUVELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxPQUFzQyxFQUFFLE1BQTBCO0lBQ2pHLDBEQUEwRDtJQUMxRCxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDYixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCxrRUFBa0U7SUFDbEUsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsTUFBTSxRQUFRLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxP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
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities for IP validation, rate limiting,
|
|
3
|
+
* authentication, and other security features
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Result of IP validation
|
|
7
|
+
*/
|
|
8
|
+
export interface IIpValidationResult {
|
|
9
|
+
allowed: boolean;
|
|
10
|
+
reason?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* IP connection tracking information
|
|
14
|
+
*/
|
|
15
|
+
export interface IIpConnectionInfo {
|
|
16
|
+
connections: Set<string>;
|
|
17
|
+
timestamps: number[];
|
|
18
|
+
ipVariants: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Rate limit tracking
|
|
22
|
+
*/
|
|
23
|
+
export interface IRateLimitInfo {
|
|
24
|
+
count: number;
|
|
25
|
+
expiry: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Logger interface for security utilities
|
|
29
|
+
*/
|
|
30
|
+
export interface ISecurityLogger {
|
|
31
|
+
info: (message: string, ...args: any[]) => void;
|
|
32
|
+
warn: (message: string, ...args: any[]) => void;
|
|
33
|
+
error: (message: string, ...args: any[]) => void;
|
|
34
|
+
debug?: (message: string, ...args: any[]) => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalize IP addresses for comparison
|
|
38
|
+
* Handles IPv4-mapped IPv6 addresses (::ffff:127.0.0.1)
|
|
39
|
+
*
|
|
40
|
+
* @param ip IP address to normalize
|
|
41
|
+
* @returns Array of equivalent IP representations
|
|
42
|
+
*/
|
|
43
|
+
export declare function normalizeIP(ip: string): string[];
|
|
44
|
+
/**
|
|
45
|
+
* Check if an IP is authorized based on allow and block lists
|
|
46
|
+
*
|
|
47
|
+
* @param ip - The IP address to check
|
|
48
|
+
* @param allowedIPs - Array of allowed IP patterns
|
|
49
|
+
* @param blockedIPs - Array of blocked IP patterns
|
|
50
|
+
* @returns Whether the IP is authorized
|
|
51
|
+
*/
|
|
52
|
+
export declare function isIPAuthorized(ip: string, allowedIPs?: string[], blockedIPs?: string[]): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Check if an IP exceeds maximum connections
|
|
55
|
+
*
|
|
56
|
+
* @param ip - The IP address to check
|
|
57
|
+
* @param ipConnectionsMap - Map of IPs to connection info
|
|
58
|
+
* @param maxConnectionsPerIP - Maximum allowed connections per IP
|
|
59
|
+
* @returns Result with allowed status and reason if blocked
|
|
60
|
+
*/
|
|
61
|
+
export declare function checkMaxConnections(ip: string, ipConnectionsMap: Map<string, IIpConnectionInfo>, maxConnectionsPerIP: number): IIpValidationResult;
|
|
62
|
+
/**
|
|
63
|
+
* Check if an IP exceeds connection rate limit
|
|
64
|
+
*
|
|
65
|
+
* @param ip - The IP address to check
|
|
66
|
+
* @param ipConnectionsMap - Map of IPs to connection info
|
|
67
|
+
* @param rateLimit - Maximum connections per minute
|
|
68
|
+
* @returns Result with allowed status and reason if blocked
|
|
69
|
+
*/
|
|
70
|
+
export declare function checkConnectionRate(ip: string, ipConnectionsMap: Map<string, IIpConnectionInfo>, rateLimit: number): IIpValidationResult;
|
|
71
|
+
/**
|
|
72
|
+
* Track a connection for an IP
|
|
73
|
+
*
|
|
74
|
+
* @param ip - The IP address
|
|
75
|
+
* @param connectionId - The connection ID to track
|
|
76
|
+
* @param ipConnectionsMap - Map of IPs to connection info
|
|
77
|
+
*/
|
|
78
|
+
export declare function trackConnection(ip: string, connectionId: string, ipConnectionsMap: Map<string, IIpConnectionInfo>): void;
|
|
79
|
+
/**
|
|
80
|
+
* Remove connection tracking for an IP
|
|
81
|
+
*
|
|
82
|
+
* @param ip - The IP address
|
|
83
|
+
* @param connectionId - The connection ID to remove
|
|
84
|
+
* @param ipConnectionsMap - Map of IPs to connection info
|
|
85
|
+
*/
|
|
86
|
+
export declare function removeConnection(ip: string, connectionId: string, ipConnectionsMap: Map<string, IIpConnectionInfo>): void;
|
|
87
|
+
/**
|
|
88
|
+
* Clean up expired rate limits
|
|
89
|
+
*
|
|
90
|
+
* @param rateLimits - Map of rate limits to clean up
|
|
91
|
+
* @param logger - Logger for debug messages
|
|
92
|
+
*/
|
|
93
|
+
export declare function cleanupExpiredRateLimits(rateLimits: Map<string, Map<string, IRateLimitInfo>>, logger?: ISecurityLogger): void;
|
|
94
|
+
/**
|
|
95
|
+
* Generate basic auth header value from username and password
|
|
96
|
+
*
|
|
97
|
+
* @param username - The username
|
|
98
|
+
* @param password - The password
|
|
99
|
+
* @returns Base64 encoded basic auth string
|
|
100
|
+
*/
|
|
101
|
+
export declare function generateBasicAuthHeader(username: string, password: string): string;
|
|
102
|
+
/**
|
|
103
|
+
* Parse basic auth header
|
|
104
|
+
*
|
|
105
|
+
* @param authHeader - The Authorization header value
|
|
106
|
+
* @returns Username and password, or null if invalid
|
|
107
|
+
*/
|
|
108
|
+
export declare function parseBasicAuthHeader(authHeader: string): {
|
|
109
|
+
username: string;
|
|
110
|
+
password: string;
|
|
111
|
+
} | null;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { matchIpPattern, ipToNumber, matchIpCidr } from './route-utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* Normalize IP addresses for comparison
|
|
5
|
+
* Handles IPv4-mapped IPv6 addresses (::ffff:127.0.0.1)
|
|
6
|
+
*
|
|
7
|
+
* @param ip IP address to normalize
|
|
8
|
+
* @returns Array of equivalent IP representations
|
|
9
|
+
*/
|
|
10
|
+
export function normalizeIP(ip) {
|
|
11
|
+
if (!ip)
|
|
12
|
+
return [];
|
|
13
|
+
// Handle IPv4-mapped IPv6 addresses (::ffff:127.0.0.1)
|
|
14
|
+
if (ip.startsWith('::ffff:')) {
|
|
15
|
+
const ipv4 = ip.slice(7);
|
|
16
|
+
return [ip, ipv4];
|
|
17
|
+
}
|
|
18
|
+
// Handle IPv4 addresses by also checking IPv4-mapped form
|
|
19
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
20
|
+
return [ip, `::ffff:${ip}`];
|
|
21
|
+
}
|
|
22
|
+
return [ip];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if an IP is authorized based on allow and block lists
|
|
26
|
+
*
|
|
27
|
+
* @param ip - The IP address to check
|
|
28
|
+
* @param allowedIPs - Array of allowed IP patterns
|
|
29
|
+
* @param blockedIPs - Array of blocked IP patterns
|
|
30
|
+
* @returns Whether the IP is authorized
|
|
31
|
+
*/
|
|
32
|
+
export function isIPAuthorized(ip, allowedIPs = ['*'], blockedIPs = []) {
|
|
33
|
+
// Skip IP validation if no rules
|
|
34
|
+
if (!ip || (allowedIPs.length === 0 && blockedIPs.length === 0)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// First check if IP is blocked - blocked IPs take precedence
|
|
38
|
+
if (blockedIPs.length > 0) {
|
|
39
|
+
for (const pattern of blockedIPs) {
|
|
40
|
+
if (matchIpPattern(pattern, ip)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// If allowed IPs list has wildcard, all non-blocked IPs are allowed
|
|
46
|
+
if (allowedIPs.includes('*')) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// Then check if IP is allowed in the explicit allow list
|
|
50
|
+
if (allowedIPs.length > 0) {
|
|
51
|
+
for (const pattern of allowedIPs) {
|
|
52
|
+
if (matchIpPattern(pattern, ip)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// If allowedIPs is specified but no match, deny access
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// Default allow if no explicit allow list
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if an IP exceeds maximum connections
|
|
64
|
+
*
|
|
65
|
+
* @param ip - The IP address to check
|
|
66
|
+
* @param ipConnectionsMap - Map of IPs to connection info
|
|
67
|
+
* @param maxConnectionsPerIP - Maximum allowed connections per IP
|
|
68
|
+
* @returns Result with allowed status and reason if blocked
|
|
69
|
+
*/
|
|
70
|
+
export function checkMaxConnections(ip, ipConnectionsMap, maxConnectionsPerIP) {
|
|
71
|
+
if (!ipConnectionsMap.has(ip)) {
|
|
72
|
+
return { allowed: true };
|
|
73
|
+
}
|
|
74
|
+
const connectionCount = ipConnectionsMap.get(ip).connections.size;
|
|
75
|
+
if (connectionCount >= maxConnectionsPerIP) {
|
|
76
|
+
return {
|
|
77
|
+
allowed: false,
|
|
78
|
+
reason: `Maximum connections per IP (${maxConnectionsPerIP}) exceeded`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { allowed: true };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if an IP exceeds connection rate limit
|
|
85
|
+
*
|
|
86
|
+
* @param ip - The IP address to check
|
|
87
|
+
* @param ipConnectionsMap - Map of IPs to connection info
|
|
88
|
+
* @param rateLimit - Maximum connections per minute
|
|
89
|
+
* @returns Result with allowed status and reason if blocked
|
|
90
|
+
*/
|
|
91
|
+
export function checkConnectionRate(ip, ipConnectionsMap, rateLimit) {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
const minute = 60 * 1000;
|
|
94
|
+
// Get or create connection info
|
|
95
|
+
if (!ipConnectionsMap.has(ip)) {
|
|
96
|
+
const info = {
|
|
97
|
+
connections: new Set(),
|
|
98
|
+
timestamps: [now],
|
|
99
|
+
ipVariants: normalizeIP(ip)
|
|
100
|
+
};
|
|
101
|
+
ipConnectionsMap.set(ip, info);
|
|
102
|
+
return { allowed: true };
|
|
103
|
+
}
|
|
104
|
+
// Get timestamps and filter out entries older than 1 minute
|
|
105
|
+
const info = ipConnectionsMap.get(ip);
|
|
106
|
+
const timestamps = info.timestamps.filter(time => now - time < minute);
|
|
107
|
+
timestamps.push(now);
|
|
108
|
+
info.timestamps = timestamps;
|
|
109
|
+
// Check if rate exceeds limit
|
|
110
|
+
if (timestamps.length > rateLimit) {
|
|
111
|
+
return {
|
|
112
|
+
allowed: false,
|
|
113
|
+
reason: `Connection rate limit (${rateLimit}/min) exceeded`
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return { allowed: true };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Track a connection for an IP
|
|
120
|
+
*
|
|
121
|
+
* @param ip - The IP address
|
|
122
|
+
* @param connectionId - The connection ID to track
|
|
123
|
+
* @param ipConnectionsMap - Map of IPs to connection info
|
|
124
|
+
*/
|
|
125
|
+
export function trackConnection(ip, connectionId, ipConnectionsMap) {
|
|
126
|
+
if (!ipConnectionsMap.has(ip)) {
|
|
127
|
+
ipConnectionsMap.set(ip, {
|
|
128
|
+
connections: new Set([connectionId]),
|
|
129
|
+
timestamps: [Date.now()],
|
|
130
|
+
ipVariants: normalizeIP(ip)
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const info = ipConnectionsMap.get(ip);
|
|
135
|
+
info.connections.add(connectionId);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Remove connection tracking for an IP
|
|
139
|
+
*
|
|
140
|
+
* @param ip - The IP address
|
|
141
|
+
* @param connectionId - The connection ID to remove
|
|
142
|
+
* @param ipConnectionsMap - Map of IPs to connection info
|
|
143
|
+
*/
|
|
144
|
+
export function removeConnection(ip, connectionId, ipConnectionsMap) {
|
|
145
|
+
if (!ipConnectionsMap.has(ip))
|
|
146
|
+
return;
|
|
147
|
+
const info = ipConnectionsMap.get(ip);
|
|
148
|
+
info.connections.delete(connectionId);
|
|
149
|
+
if (info.connections.size === 0) {
|
|
150
|
+
ipConnectionsMap.delete(ip);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Clean up expired rate limits
|
|
155
|
+
*
|
|
156
|
+
* @param rateLimits - Map of rate limits to clean up
|
|
157
|
+
* @param logger - Logger for debug messages
|
|
158
|
+
*/
|
|
159
|
+
export function cleanupExpiredRateLimits(rateLimits, logger) {
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
let totalRemoved = 0;
|
|
162
|
+
for (const [routeId, routeLimits] of rateLimits.entries()) {
|
|
163
|
+
let removed = 0;
|
|
164
|
+
for (const [key, limit] of routeLimits.entries()) {
|
|
165
|
+
if (limit.expiry < now) {
|
|
166
|
+
routeLimits.delete(key);
|
|
167
|
+
removed++;
|
|
168
|
+
totalRemoved++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (removed > 0 && logger?.debug) {
|
|
172
|
+
logger.debug(`Cleaned up ${removed} expired rate limits for route ${routeId}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (totalRemoved > 0 && logger?.info) {
|
|
176
|
+
logger.info(`Cleaned up ${totalRemoved} expired rate limits total`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Generate basic auth header value from username and password
|
|
181
|
+
*
|
|
182
|
+
* @param username - The username
|
|
183
|
+
* @param password - The password
|
|
184
|
+
* @returns Base64 encoded basic auth string
|
|
185
|
+
*/
|
|
186
|
+
export function generateBasicAuthHeader(username, password) {
|
|
187
|
+
return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Parse basic auth header
|
|
191
|
+
*
|
|
192
|
+
* @param authHeader - The Authorization header value
|
|
193
|
+
* @returns Username and password, or null if invalid
|
|
194
|
+
*/
|
|
195
|
+
export function parseBasicAuthHeader(authHeader) {
|
|
196
|
+
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const base64 = authHeader.slice(6); // Remove 'Basic '
|
|
201
|
+
const decoded = Buffer.from(base64, 'base64').toString();
|
|
202
|
+
const [username, password] = decoded.split(':');
|
|
203
|
+
if (!username || !password) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return { username, password };
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdXJpdHktdXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9jb3JlL3V0aWxzL3NlY3VyaXR5LXV0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUNMLGNBQWMsRUFDZCxVQUFVLEVBQ1YsV0FBVyxFQUNaLE1BQU0sa0JBQWtCLENBQUM7QUEwQzFCOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxXQUFXLENBQUMsRUFBVTtJQUNwQyxJQUFJLENBQUMsRUFBRTtRQUFFLE9BQU8sRUFBRSxDQUFDO0lBRW5CLHVEQUF1RDtJQUN2RCxJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztRQUM3QixNQUFNLElBQUksR0FBRyxFQUFFLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3pCLE9BQU8sQ0FBQyxFQUFFLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDcEIsQ0FBQztJQUVELDBEQUEwRDtJQUMxRCxJQUFJLHlCQUF5QixDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDO1FBQ3ZDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsVUFBVSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQzlCLENBQUM7SUFFRCxPQUFPLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDZCxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0sVUFBVSxjQUFjLENBQzVCLEVBQVUsRUFDVixhQUF1QixDQUFDLEdBQUcsQ0FBQyxFQUM1QixhQUF1QixFQUFFO0lBRXpCLGlDQUFpQztJQUNqQyxJQUFJLENBQUMsRUFBRSxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksVUFBVSxDQUFDLE1BQU0sS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ2hFLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVE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
|