@push.rocks/smartproxy 21.1.7 → 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.
- package/changelog.md +81 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
- package/dist_ts/core/utils/shared-security-manager.js +66 -1
- package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
- package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
- package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
- package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
- package/dist_ts/proxies/http-proxy/index.js +6 -2
- package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
- package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
- package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
- package/dist_ts/proxies/nftables-proxy/index.js +2 -1
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
- package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
- package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
- package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +0 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
- package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +1 -2
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
- package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
- package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
- package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
- package/dist_ts/proxies/smart-proxy/utils/route-validator.js +251 -3
- package/npmextra.json +12 -6
- package/package.json +34 -24
- package/readme.hints.md +184 -1
- package/readme.md +235 -172
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/utils/shared-security-manager.ts +98 -13
- package/ts/proxies/http-proxy/default-certificates.ts +150 -0
- package/ts/proxies/http-proxy/http-proxy.ts +9 -15
- package/ts/proxies/http-proxy/index.ts +6 -1
- package/ts/proxies/http-proxy/security-manager.ts +141 -161
- package/ts/proxies/nftables-proxy/index.ts +1 -0
- package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
- package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
- package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
- package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
- package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
- package/ts/proxies/smart-proxy/certificate-manager.ts +3 -2
- package/ts/proxies/smart-proxy/connection-manager.ts +21 -8
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +39 -13
- package/ts/proxies/smart-proxy/models/interfaces.ts +0 -1
- package/ts/proxies/smart-proxy/route-connection-handler.ts +88 -16
- package/ts/proxies/smart-proxy/security-manager.ts +98 -86
- package/ts/proxies/smart-proxy/smart-proxy.ts +0 -2
- package/ts/proxies/smart-proxy/tls-manager.ts +1 -37
- package/ts/proxies/smart-proxy/utils/index.ts +3 -5
- package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
- package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
- package/ts/proxies/smart-proxy/utils/route-validator.ts +274 -4
- package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
- 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
|
|
8
|
-
* Implements
|
|
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,
|
|
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(
|
|
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 ||
|
|
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 ||
|
|
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.
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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.
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
322
|
-
|
|
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(
|
|
289
|
+
const timestamps = this.connectionRateByIP.get(key)!.filter((time) => now - time < minute);
|
|
328
290
|
timestamps.push(now);
|
|
329
|
-
this.connectionRateByIP.set(
|
|
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
|
|
340
|
-
|
|
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(
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
}
|