@push.rocks/smartproxy 21.1.6 → 22.4.2

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.
Files changed (103) hide show
  1. package/changelog.md +89 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
  4. package/dist_ts/core/utils/shared-security-manager.js +66 -1
  5. package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
  6. package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
  7. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
  8. package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
  9. package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
  10. package/dist_ts/proxies/http-proxy/index.js +6 -2
  11. package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
  12. package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
  13. package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
  14. package/dist_ts/proxies/nftables-proxy/index.js +2 -1
  15. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
  16. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
  17. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
  18. package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
  19. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
  20. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
  21. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
  22. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
  23. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
  24. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
  25. package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
  26. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
  27. package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
  28. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
  29. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +0 -1
  30. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
  31. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
  32. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
  33. package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
  34. package/dist_ts/proxies/smart-proxy/smart-proxy.js +1 -2
  35. package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
  36. package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
  37. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
  38. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
  39. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
  40. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
  41. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
  42. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
  43. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
  44. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
  45. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
  46. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
  47. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
  48. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
  49. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
  50. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
  51. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
  52. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
  53. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
  54. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
  55. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
  56. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
  57. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
  58. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
  59. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
  60. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
  61. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
  62. package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
  63. package/dist_ts/proxies/smart-proxy/utils/route-validator.js +266 -6
  64. package/npmextra.json +12 -6
  65. package/package.json +34 -24
  66. package/readme.hints.md +184 -1
  67. package/readme.md +235 -172
  68. package/ts/00_commitinfo_data.ts +1 -1
  69. package/ts/core/utils/shared-security-manager.ts +98 -13
  70. package/ts/proxies/http-proxy/default-certificates.ts +150 -0
  71. package/ts/proxies/http-proxy/http-proxy.ts +9 -15
  72. package/ts/proxies/http-proxy/index.ts +6 -1
  73. package/ts/proxies/http-proxy/security-manager.ts +141 -161
  74. package/ts/proxies/nftables-proxy/index.ts +1 -0
  75. package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
  76. package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
  77. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
  78. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
  79. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
  80. package/ts/proxies/smart-proxy/certificate-manager.ts +3 -2
  81. package/ts/proxies/smart-proxy/connection-manager.ts +21 -8
  82. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +39 -13
  83. package/ts/proxies/smart-proxy/models/interfaces.ts +0 -1
  84. package/ts/proxies/smart-proxy/route-connection-handler.ts +88 -16
  85. package/ts/proxies/smart-proxy/security-manager.ts +98 -86
  86. package/ts/proxies/smart-proxy/smart-proxy.ts +0 -2
  87. package/ts/proxies/smart-proxy/tls-manager.ts +1 -37
  88. package/ts/proxies/smart-proxy/utils/index.ts +3 -5
  89. package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
  90. package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
  91. package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
  92. package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
  93. package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
  94. package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
  95. package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
  96. package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
  97. package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
  98. package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
  99. package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
  100. package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
  101. package/ts/proxies/smart-proxy/utils/route-validator.ts +289 -7
  102. package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
  103. package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
@@ -1,28 +1,40 @@
1
- import * as plugins from '../../plugins.js';
2
1
  import type { ILogger } from './models/types.js';
3
2
  import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
4
3
  import type { IRouteContext } from '../../core/models/route-context.js';
4
+ import {
5
+ isIPAuthorized,
6
+ normalizeIP,
7
+ parseBasicAuthHeader,
8
+ cleanupExpiredRateLimits,
9
+ type IRateLimitInfo
10
+ } from '../../core/utils/security-utils.js';
5
11
 
6
12
  /**
7
- * Manages security features for the NetworkProxy
8
- * Implements Phase 5.4: Security features like IP filtering and rate limiting
13
+ * Manages security features for the HttpProxy
14
+ * Implements IP filtering, rate limiting, and authentication.
15
+ * Uses shared utilities from security-utils.ts.
9
16
  */
10
17
  export class SecurityManager {
11
18
  // Cache IP filtering results to avoid constant regex matching
12
19
  private ipFilterCache: Map<string, Map<string, boolean>> = new Map();
13
-
20
+
14
21
  // Store rate limits per route and key
15
- private rateLimits: Map<string, Map<string, { count: number, expiry: number }>> = new Map();
16
-
22
+ private rateLimits: Map<string, Map<string, IRateLimitInfo>> = new Map();
23
+
17
24
  // Connection tracking by IP
18
25
  private connectionsByIP: Map<string, Set<string>> = new Map();
19
26
  private connectionRateByIP: Map<string, number[]> = new Map();
20
-
21
- constructor(private logger: ILogger, private routes: IRouteConfig[] = [], private maxConnectionsPerIP: number = 100, private connectionRateLimitPerMinute: number = 300) {
27
+
28
+ constructor(
29
+ private logger: ILogger,
30
+ private routes: IRouteConfig[] = [],
31
+ private maxConnectionsPerIP: number = 100,
32
+ private connectionRateLimitPerMinute: number = 300
33
+ ) {
22
34
  // Start periodic cleanup for connection tracking
23
35
  this.startPeriodicIpCleanup();
24
36
  }
25
-
37
+
26
38
  /**
27
39
  * Update the routes configuration
28
40
  */
@@ -31,10 +43,10 @@ export class SecurityManager {
31
43
  // Reset caches when routes change
32
44
  this.ipFilterCache.clear();
33
45
  }
34
-
46
+
35
47
  /**
36
48
  * Check if a client is allowed to access a specific route
37
- *
49
+ *
38
50
  * @param route The route to check access for
39
51
  * @param context The route context with client information
40
52
  * @returns True if access is allowed, false otherwise
@@ -43,26 +55,26 @@ export class SecurityManager {
43
55
  if (!route.security) {
44
56
  return true; // No security restrictions
45
57
  }
46
-
58
+
47
59
  // --- IP filtering ---
48
60
  if (!this.isIpAllowed(route, context.clientIp)) {
49
- this.logger.debug(`IP ${context.clientIp} is blocked for route ${route.name || route.id || 'unnamed'}`);
61
+ this.logger.debug(`IP ${context.clientIp} is blocked for route ${route.name || 'unnamed'}`);
50
62
  return false;
51
63
  }
52
-
64
+
53
65
  // --- Rate limiting ---
54
66
  if (route.security.rateLimit?.enabled && !this.isWithinRateLimit(route, context)) {
55
- this.logger.debug(`Rate limit exceeded for route ${route.name || route.id || 'unnamed'}`);
67
+ this.logger.debug(`Rate limit exceeded for route ${route.name || 'unnamed'}`);
56
68
  return false;
57
69
  }
58
-
70
+
59
71
  // --- Basic Auth (handled at HTTP level) ---
60
72
  // Basic auth is not checked here as it requires HTTP headers
61
73
  // and is handled in the RequestHandler
62
-
74
+
63
75
  return true;
64
76
  }
65
-
77
+
66
78
  /**
67
79
  * Check if an IP is allowed based on route security settings
68
80
  */
@@ -70,94 +82,32 @@ export class SecurityManager {
70
82
  if (!route.security) {
71
83
  return true; // No security restrictions
72
84
  }
73
-
74
- const routeId = route.id || route.name || 'unnamed';
75
-
85
+
86
+ const routeId = route.name || 'unnamed';
87
+
76
88
  // Check cache first
77
89
  if (!this.ipFilterCache.has(routeId)) {
78
90
  this.ipFilterCache.set(routeId, new Map());
79
91
  }
80
-
92
+
81
93
  const routeCache = this.ipFilterCache.get(routeId)!;
82
94
  if (routeCache.has(clientIp)) {
83
95
  return routeCache.get(clientIp)!;
84
96
  }
85
-
86
- let allowed = true;
87
-
88
- // Check block list first (deny has priority over allow)
89
- if (route.security.ipBlockList && route.security.ipBlockList.length > 0) {
90
- if (this.ipMatchesPattern(clientIp, route.security.ipBlockList)) {
91
- allowed = false;
92
- }
93
- }
94
-
95
- // Then check allow list (overrides block list if specified)
96
- if (route.security.ipAllowList && route.security.ipAllowList.length > 0) {
97
- // If allow list is specified, IP must match an entry to be allowed
98
- allowed = this.ipMatchesPattern(clientIp, route.security.ipAllowList);
99
- }
100
-
97
+
98
+ // Use shared utility for IP authorization
99
+ const allowed = isIPAuthorized(
100
+ clientIp,
101
+ route.security.ipAllowList,
102
+ route.security.ipBlockList
103
+ );
104
+
101
105
  // Cache the result
102
106
  routeCache.set(clientIp, allowed);
103
-
107
+
104
108
  return allowed;
105
109
  }
106
-
107
- /**
108
- * Check if IP matches any pattern in the list
109
- */
110
- private ipMatchesPattern(ip: string, patterns: string[]): boolean {
111
- for (const pattern of patterns) {
112
- // CIDR notation
113
- if (pattern.includes('/')) {
114
- if (this.ipMatchesCidr(ip, pattern)) {
115
- return true;
116
- }
117
- }
118
- // Wildcard notation
119
- else if (pattern.includes('*')) {
120
- const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
121
- if (regex.test(ip)) {
122
- return true;
123
- }
124
- }
125
- // Exact match
126
- else if (pattern === ip) {
127
- return true;
128
- }
129
- }
130
- return false;
131
- }
132
-
133
- /**
134
- * Check if IP matches CIDR notation
135
- * Very basic implementation - for production use, consider a dedicated IP library
136
- */
137
- private ipMatchesCidr(ip: string, cidr: string): boolean {
138
- try {
139
- const [subnet, bits] = cidr.split('/');
140
- const mask = parseInt(bits, 10);
141
-
142
- // Convert IP to numeric format
143
- const ipParts = ip.split('.').map(part => parseInt(part, 10));
144
- const subnetParts = subnet.split('.').map(part => parseInt(part, 10));
145
-
146
- // Calculate the numeric IP and subnet
147
- const ipNum = (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3];
148
- const subnetNum = (subnetParts[0] << 24) | (subnetParts[1] << 16) | (subnetParts[2] << 8) | subnetParts[3];
149
-
150
- // Calculate the mask
151
- const maskNum = ~((1 << (32 - mask)) - 1);
152
-
153
- // Check if IP is in subnet
154
- return (ipNum & maskNum) === (subnetNum & maskNum);
155
- } catch (e) {
156
- this.logger.error(`Invalid CIDR notation: ${cidr}`);
157
- return false;
158
- }
159
- }
160
-
110
+
161
111
  /**
162
112
  * Check if request is within rate limit
163
113
  */
@@ -165,13 +115,13 @@ export class SecurityManager {
165
115
  if (!route.security?.rateLimit?.enabled) {
166
116
  return true;
167
117
  }
168
-
118
+
169
119
  const rateLimit = route.security.rateLimit;
170
- const routeId = route.id || route.name || 'unnamed';
171
-
120
+ const routeId = route.name || 'unnamed';
121
+
172
122
  // Determine rate limit key (by IP, path, or header)
173
123
  let key = context.clientIp; // Default to IP
174
-
124
+
175
125
  if (rateLimit.keyBy === 'path' && context.path) {
176
126
  key = `${context.clientIp}:${context.path}`;
177
127
  } else if (rateLimit.keyBy === 'header' && rateLimit.headerName && context.headers) {
@@ -180,15 +130,15 @@ export class SecurityManager {
180
130
  key = `${context.clientIp}:${headerValue}`;
181
131
  }
182
132
  }
183
-
133
+
184
134
  // Get or create rate limit tracking for this route
185
135
  if (!this.rateLimits.has(routeId)) {
186
136
  this.rateLimits.set(routeId, new Map());
187
137
  }
188
-
138
+
189
139
  const routeLimits = this.rateLimits.get(routeId)!;
190
140
  const now = Date.now();
191
-
141
+
192
142
  // Get or create rate limit tracking for this key
193
143
  let limit = routeLimits.get(key);
194
144
  if (!limit || limit.expiry < now) {
@@ -200,37 +150,30 @@ export class SecurityManager {
200
150
  routeLimits.set(key, limit);
201
151
  return true;
202
152
  }
203
-
153
+
204
154
  // Increment the counter
205
155
  limit.count++;
206
-
156
+
207
157
  // Check if rate limit is exceeded
208
158
  return limit.count <= rateLimit.maxRequests;
209
159
  }
210
-
160
+
211
161
  /**
212
162
  * Clean up expired rate limits
213
163
  * Should be called periodically to prevent memory leaks
214
164
  */
215
165
  public cleanupExpiredRateLimits(): void {
216
- const now = Date.now();
217
- for (const [routeId, routeLimits] of this.rateLimits.entries()) {
218
- let removed = 0;
219
- for (const [key, limit] of routeLimits.entries()) {
220
- if (limit.expiry < now) {
221
- routeLimits.delete(key);
222
- removed++;
223
- }
224
- }
225
- if (removed > 0) {
226
- this.logger.debug(`Cleaned up ${removed} expired rate limits for route ${routeId}`);
227
- }
228
- }
166
+ cleanupExpiredRateLimits(this.rateLimits, {
167
+ info: this.logger.info.bind(this.logger),
168
+ warn: this.logger.warn.bind(this.logger),
169
+ error: this.logger.error.bind(this.logger),
170
+ debug: this.logger.debug?.bind(this.logger)
171
+ });
229
172
  }
230
-
173
+
231
174
  /**
232
175
  * Check basic auth credentials
233
- *
176
+ *
234
177
  * @param route The route to check auth for
235
178
  * @param username The provided username
236
179
  * @param password The provided password
@@ -240,22 +183,22 @@ export class SecurityManager {
240
183
  if (!route.security?.basicAuth?.enabled) {
241
184
  return true;
242
185
  }
243
-
186
+
244
187
  const basicAuth = route.security.basicAuth;
245
-
188
+
246
189
  // Check credentials against configured users
247
190
  for (const user of basicAuth.users) {
248
191
  if (user.username === username && user.password === password) {
249
192
  return true;
250
193
  }
251
194
  }
252
-
195
+
253
196
  return false;
254
197
  }
255
-
198
+
256
199
  /**
257
200
  * Verify a JWT token
258
- *
201
+ *
259
202
  * @param route The route to verify the token for
260
203
  * @param token The JWT token to verify
261
204
  * @returns True if the token is valid, false otherwise
@@ -264,38 +207,37 @@ export class SecurityManager {
264
207
  if (!route.security?.jwtAuth?.enabled) {
265
208
  return true;
266
209
  }
267
-
210
+
268
211
  try {
269
- // This is a simplified version - in production you'd use a proper JWT library
270
212
  const jwtAuth = route.security.jwtAuth;
271
-
213
+
272
214
  // Verify structure
273
215
  const parts = token.split('.');
274
216
  if (parts.length !== 3) {
275
217
  return false;
276
218
  }
277
-
219
+
278
220
  // Decode payload
279
221
  const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
280
-
222
+
281
223
  // Check expiration
282
224
  if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
283
225
  return false;
284
226
  }
285
-
227
+
286
228
  // Check issuer
287
229
  if (jwtAuth.issuer && payload.iss !== jwtAuth.issuer) {
288
230
  return false;
289
231
  }
290
-
232
+
291
233
  // Check audience
292
234
  if (jwtAuth.audience && payload.aud !== jwtAuth.audience) {
293
235
  return false;
294
236
  }
295
-
296
- // In a real implementation, you'd also verify the signature
237
+
238
+ // Note: In a real implementation, you'd also verify the signature
297
239
  // using the secret and algorithm specified in jwtAuth
298
-
240
+
299
241
  return true;
300
242
  } catch (err) {
301
243
  this.logger.error(`Error verifying JWT: ${err}`);
@@ -304,12 +246,20 @@ export class SecurityManager {
304
246
  }
305
247
 
306
248
  /**
307
- * Get connections count by IP
249
+ * Get connections count by IP (checks normalized variants)
308
250
  */
309
251
  public getConnectionCountByIP(ip: string): number {
310
- return this.connectionsByIP.get(ip)?.size || 0;
252
+ // Check all normalized variants of the IP
253
+ const variants = normalizeIP(ip);
254
+ for (const variant of variants) {
255
+ const connections = this.connectionsByIP.get(variant);
256
+ if (connections) {
257
+ return connections.size;
258
+ }
259
+ }
260
+ return 0;
311
261
  }
312
-
262
+
313
263
  /**
314
264
  * Check and update connection rate for an IP
315
265
  * @returns true if within rate limit, false if exceeding limit
@@ -318,43 +268,73 @@ export class SecurityManager {
318
268
  const now = Date.now();
319
269
  const minute = 60 * 1000;
320
270
 
321
- if (!this.connectionRateByIP.has(ip)) {
322
- this.connectionRateByIP.set(ip, [now]);
271
+ // Find existing rate tracking (check normalized variants)
272
+ const variants = normalizeIP(ip);
273
+ let existingKey: string | null = null;
274
+ for (const variant of variants) {
275
+ if (this.connectionRateByIP.has(variant)) {
276
+ existingKey = variant;
277
+ break;
278
+ }
279
+ }
280
+
281
+ const key = existingKey || ip;
282
+
283
+ if (!this.connectionRateByIP.has(key)) {
284
+ this.connectionRateByIP.set(key, [now]);
323
285
  return true;
324
286
  }
325
287
 
326
288
  // Get timestamps and filter out entries older than 1 minute
327
- const timestamps = this.connectionRateByIP.get(ip)!.filter((time) => now - time < minute);
289
+ const timestamps = this.connectionRateByIP.get(key)!.filter((time) => now - time < minute);
328
290
  timestamps.push(now);
329
- this.connectionRateByIP.set(ip, timestamps);
291
+ this.connectionRateByIP.set(key, timestamps);
330
292
 
331
293
  // Check if rate exceeds limit
332
294
  return timestamps.length <= this.connectionRateLimitPerMinute;
333
295
  }
334
-
296
+
335
297
  /**
336
298
  * Track connection by IP
337
299
  */
338
300
  public trackConnectionByIP(ip: string, connectionId: string): void {
339
- if (!this.connectionsByIP.has(ip)) {
340
- this.connectionsByIP.set(ip, new Set());
301
+ // Check if any variant already exists
302
+ const variants = normalizeIP(ip);
303
+ let existingKey: string | null = null;
304
+
305
+ for (const variant of variants) {
306
+ if (this.connectionsByIP.has(variant)) {
307
+ existingKey = variant;
308
+ break;
309
+ }
310
+ }
311
+
312
+ const key = existingKey || ip;
313
+ if (!this.connectionsByIP.has(key)) {
314
+ this.connectionsByIP.set(key, new Set());
341
315
  }
342
- this.connectionsByIP.get(ip)!.add(connectionId);
316
+ this.connectionsByIP.get(key)!.add(connectionId);
343
317
  }
344
-
318
+
345
319
  /**
346
320
  * Remove connection tracking for an IP
347
321
  */
348
322
  public removeConnectionByIP(ip: string, connectionId: string): void {
349
- if (this.connectionsByIP.has(ip)) {
350
- const connections = this.connectionsByIP.get(ip)!;
351
- connections.delete(connectionId);
352
- if (connections.size === 0) {
353
- this.connectionsByIP.delete(ip);
323
+ // Check all variants to find where the connection is tracked
324
+ const variants = normalizeIP(ip);
325
+
326
+ for (const variant of variants) {
327
+ if (this.connectionsByIP.has(variant)) {
328
+ const connections = this.connectionsByIP.get(variant)!;
329
+ connections.delete(connectionId);
330
+ if (connections.size === 0) {
331
+ this.connectionsByIP.delete(variant);
332
+ }
333
+ break;
354
334
  }
355
335
  }
356
336
  }
357
-
337
+
358
338
  /**
359
339
  * Check if IP should be allowed considering connection rate and max connections
360
340
  * @returns Object with result and reason
@@ -375,10 +355,10 @@ export class SecurityManager {
375
355
  reason: `Connection rate limit (${this.connectionRateLimitPerMinute}/min) exceeded`
376
356
  };
377
357
  }
378
-
358
+
379
359
  return { allowed: true };
380
360
  }
381
-
361
+
382
362
  /**
383
363
  * Clears all IP tracking data (for shutdown)
384
364
  */
@@ -386,7 +366,7 @@ export class SecurityManager {
386
366
  this.connectionsByIP.clear();
387
367
  this.connectionRateByIP.clear();
388
368
  }
389
-
369
+
390
370
  /**
391
371
  * Start periodic cleanup of IP tracking data
392
372
  */
@@ -396,7 +376,7 @@ export class SecurityManager {
396
376
  this.performIpCleanup();
397
377
  }, 60000).unref();
398
378
  }
399
-
379
+
400
380
  /**
401
381
  * Perform cleanup of expired IP data
402
382
  */
@@ -405,11 +385,11 @@ export class SecurityManager {
405
385
  const minute = 60 * 1000;
406
386
  let cleanedRateLimits = 0;
407
387
  let cleanedIPs = 0;
408
-
388
+
409
389
  // Clean up expired rate limit timestamps
410
390
  for (const [ip, timestamps] of this.connectionRateByIP.entries()) {
411
- const validTimestamps = timestamps.filter(time => now - time < minute);
412
-
391
+ const validTimestamps = timestamps.filter((time) => now - time < minute);
392
+
413
393
  if (validTimestamps.length === 0) {
414
394
  this.connectionRateByIP.delete(ip);
415
395
  cleanedRateLimits++;
@@ -417,7 +397,7 @@ export class SecurityManager {
417
397
  this.connectionRateByIP.set(ip, validTimestamps);
418
398
  }
419
399
  }
420
-
400
+
421
401
  // Clean up IPs with no active connections
422
402
  for (const [ip, connections] of this.connectionsByIP.entries()) {
423
403
  if (connections.size === 0) {
@@ -425,7 +405,7 @@ export class SecurityManager {
425
405
  cleanedIPs++;
426
406
  }
427
407
  }
428
-
408
+
429
409
  if (cleanedRateLimits > 0 || cleanedIPs > 0) {
430
410
  this.logger.debug(`IP cleanup: removed ${cleanedIPs} IPs and ${cleanedRateLimits} rate limits`);
431
411
  }
@@ -3,3 +3,4 @@
3
3
  */
4
4
  export * from './nftables-proxy.js';
5
5
  export * from './models/index.js';
6
+ export * from './utils/index.js';