@push.rocks/smartproxy 21.1.3 → 21.1.5
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 +23 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/core/models/socket-augmentation.d.ts +3 -0
- package/dist_ts/core/models/socket-augmentation.js +1 -1
- package/dist_ts/core/utils/socket-tracker.d.ts +16 -0
- package/dist_ts/core/utils/socket-tracker.js +49 -0
- package/dist_ts/detection/detectors/http-detector.js +14 -2
- package/dist_ts/detection/protocol-detector.js +2 -1
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +5 -1
- package/dist_ts/proxies/http-proxy/http-proxy.js +63 -39
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +5 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +20 -6
- package/dist_ts/proxies/smart-proxy/index.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/index.js +2 -1
- package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +4 -0
- package/dist_ts/proxies/smart-proxy/metrics-collector.js +52 -7
- package/dist_ts/proxies/smart-proxy/route-orchestrator.d.ts +56 -0
- package/dist_ts/proxies/smart-proxy/route-orchestrator.js +204 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -11
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +48 -237
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +42 -7
- package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +58 -0
- package/dist_ts/proxies/smart-proxy/utils/route-validator.js +405 -0
- package/package.json +3 -2
- package/readme.md +321 -828
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/models/socket-augmentation.ts +5 -0
- package/ts/core/utils/socket-tracker.ts +63 -0
- package/ts/detection/detectors/http-detector.ts +14 -1
- package/ts/detection/protocol-detector.ts +1 -0
- package/ts/proxies/http-proxy/http-proxy.ts +73 -48
- package/ts/proxies/smart-proxy/certificate-manager.ts +21 -5
- package/ts/proxies/smart-proxy/index.ts +1 -0
- package/ts/proxies/smart-proxy/metrics-collector.ts +58 -6
- package/ts/proxies/smart-proxy/route-orchestrator.ts +297 -0
- package/ts/proxies/smart-proxy/smart-proxy.ts +66 -270
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +45 -6
- package/ts/proxies/smart-proxy/utils/route-validator.ts +453 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { logger } from '../../../core/utils/logger.js';
|
|
2
|
+
import type { IRouteConfig } from '../models/route-types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validates route configurations for correctness and safety
|
|
6
|
+
*/
|
|
7
|
+
export class RouteValidator {
|
|
8
|
+
private static readonly VALID_TLS_MODES = ['terminate', 'passthrough', 'terminate-and-reencrypt'];
|
|
9
|
+
private static readonly VALID_ACTION_TYPES = ['forward', 'socket-handler'];
|
|
10
|
+
private static readonly VALID_PROTOCOLS = ['tcp', 'http', 'https', 'ws', 'wss'];
|
|
11
|
+
private static readonly MAX_PORTS = 100;
|
|
12
|
+
private static readonly MAX_DOMAINS = 1000;
|
|
13
|
+
private static readonly MAX_HEADER_SIZE = 8192;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validate a single route configuration
|
|
17
|
+
*/
|
|
18
|
+
public static validateRoute(route: IRouteConfig): { valid: boolean; errors: string[] } {
|
|
19
|
+
const errors: string[] = [];
|
|
20
|
+
|
|
21
|
+
// Validate route has a name
|
|
22
|
+
if (!route.name || typeof route.name !== 'string') {
|
|
23
|
+
errors.push('Route must have a valid name');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Validate match criteria
|
|
27
|
+
if (!route.match) {
|
|
28
|
+
errors.push('Route must have match criteria');
|
|
29
|
+
} else {
|
|
30
|
+
// Validate ports
|
|
31
|
+
if (route.match.ports) {
|
|
32
|
+
const ports = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports];
|
|
33
|
+
|
|
34
|
+
if (ports.length > this.MAX_PORTS) {
|
|
35
|
+
errors.push(`Too many ports specified (max ${this.MAX_PORTS})`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const port of ports) {
|
|
39
|
+
if (typeof port === 'number') {
|
|
40
|
+
if (!this.isValidPort(port)) {
|
|
41
|
+
errors.push(`Invalid port: ${port}. Must be between 1 and 65535`);
|
|
42
|
+
}
|
|
43
|
+
} else if (typeof port === 'object' && 'from' in port && 'to' in port) {
|
|
44
|
+
if (!this.isValidPort(port.from)) {
|
|
45
|
+
errors.push(`Invalid port range start: ${port.from}. Must be between 1 and 65535`);
|
|
46
|
+
}
|
|
47
|
+
if (!this.isValidPort(port.to)) {
|
|
48
|
+
errors.push(`Invalid port range end: ${port.to}. Must be between 1 and 65535`);
|
|
49
|
+
}
|
|
50
|
+
if (port.from > port.to) {
|
|
51
|
+
errors.push(`Invalid port range: ${port.from}-${port.to} (start > end)`);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
errors.push(`Invalid port configuration: ${JSON.stringify(port)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Validate domains
|
|
60
|
+
if (route.match.domains) {
|
|
61
|
+
const domains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains];
|
|
62
|
+
|
|
63
|
+
if (domains.length > this.MAX_DOMAINS) {
|
|
64
|
+
errors.push(`Too many domains specified (max ${this.MAX_DOMAINS})`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const domain of domains) {
|
|
68
|
+
if (!this.isValidDomain(domain)) {
|
|
69
|
+
errors.push(`Invalid domain pattern: ${domain}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Validate paths
|
|
75
|
+
if (route.match.path) {
|
|
76
|
+
const paths = Array.isArray(route.match.path) ? route.match.path : [route.match.path];
|
|
77
|
+
|
|
78
|
+
for (const path of paths) {
|
|
79
|
+
if (!this.isValidPath(path)) {
|
|
80
|
+
errors.push(`Invalid path pattern: ${path}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Validate client IPs
|
|
86
|
+
if (route.match.clientIp) {
|
|
87
|
+
const ips = Array.isArray(route.match.clientIp) ? route.match.clientIp : [route.match.clientIp];
|
|
88
|
+
|
|
89
|
+
for (const ip of ips) {
|
|
90
|
+
if (!this.isValidIPPattern(ip)) {
|
|
91
|
+
errors.push(`Invalid IP pattern: ${ip}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate headers
|
|
97
|
+
if (route.match.headers) {
|
|
98
|
+
for (const [key, value] of Object.entries(route.match.headers)) {
|
|
99
|
+
if (key.length > 256) {
|
|
100
|
+
errors.push(`Header name too long: ${key}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const headerValue = String(value);
|
|
104
|
+
if (headerValue.length > this.MAX_HEADER_SIZE) {
|
|
105
|
+
errors.push(`Header value too long for ${key} (max ${this.MAX_HEADER_SIZE} bytes)`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!/^[\x20-\x7E]+$/.test(key)) {
|
|
109
|
+
errors.push(`Invalid header name: ${key} (must be printable ASCII)`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Protocol validation removed - not part of IRouteMatch interface
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Validate action
|
|
118
|
+
if (!route.action) {
|
|
119
|
+
errors.push('Route must have an action');
|
|
120
|
+
} else {
|
|
121
|
+
// Validate action type
|
|
122
|
+
if (!route.action.type || !this.VALID_ACTION_TYPES.includes(route.action.type)) {
|
|
123
|
+
errors.push(`Invalid action type: ${route.action.type}. Must be one of: ${this.VALID_ACTION_TYPES.join(', ')}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate socket-handler
|
|
127
|
+
if (route.action.type === 'socket-handler') {
|
|
128
|
+
if (typeof route.action.socketHandler !== 'function') {
|
|
129
|
+
errors.push('socket-handler action requires a socketHandler function');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Validate forward target
|
|
134
|
+
if (route.action.type === 'forward') {
|
|
135
|
+
if (!route.action.targets || route.action.targets.length === 0) {
|
|
136
|
+
errors.push('Forward action must have at least one target');
|
|
137
|
+
} else {
|
|
138
|
+
for (const target of route.action.targets) {
|
|
139
|
+
if (!target.host) {
|
|
140
|
+
errors.push('Target must have a host');
|
|
141
|
+
} else if (typeof target.host !== 'string' && !Array.isArray(target.host) && typeof target.host !== 'function') {
|
|
142
|
+
errors.push('Target host must be a string, array of strings, or function');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (target.port) {
|
|
146
|
+
if (typeof target.port === 'number' && !this.isValidPort(target.port)) {
|
|
147
|
+
errors.push(`Invalid target port: ${target.port}`);
|
|
148
|
+
} else if (target.port !== 'preserve' && typeof target.port !== 'function' && typeof target.port !== 'number') {
|
|
149
|
+
errors.push(`Invalid target port configuration: ${target.port}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Validate TLS settings
|
|
157
|
+
if (route.action.tls) {
|
|
158
|
+
if (route.action.tls.mode && !this.VALID_TLS_MODES.includes(route.action.tls.mode)) {
|
|
159
|
+
errors.push(`Invalid TLS mode: ${route.action.tls.mode}. Must be one of: ${this.VALID_TLS_MODES.join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (route.action.tls.certificate) {
|
|
163
|
+
if (route.action.tls.certificate !== 'auto' && typeof route.action.tls.certificate !== 'object') {
|
|
164
|
+
errors.push('TLS certificate must be "auto" or a certificate configuration object');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (route.action.tls.versions) {
|
|
169
|
+
for (const version of route.action.tls.versions) {
|
|
170
|
+
if (!['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'].includes(version)) {
|
|
171
|
+
errors.push(`Invalid TLS version: ${version}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Validate security settings
|
|
179
|
+
if (route.security) {
|
|
180
|
+
// Validate IP allow/block lists
|
|
181
|
+
if (route.security.ipAllowList) {
|
|
182
|
+
const allowList = Array.isArray(route.security.ipAllowList) ? route.security.ipAllowList : [route.security.ipAllowList];
|
|
183
|
+
|
|
184
|
+
for (const ip of allowList) {
|
|
185
|
+
if (!this.isValidIPPattern(ip)) {
|
|
186
|
+
errors.push(`Invalid IP pattern in allow list: ${ip}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (route.security.ipBlockList) {
|
|
192
|
+
const blockList = Array.isArray(route.security.ipBlockList) ? route.security.ipBlockList : [route.security.ipBlockList];
|
|
193
|
+
|
|
194
|
+
for (const ip of blockList) {
|
|
195
|
+
if (!this.isValidIPPattern(ip)) {
|
|
196
|
+
errors.push(`Invalid IP pattern in block list: ${ip}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Validate rate limits
|
|
202
|
+
if (route.security.rateLimit) {
|
|
203
|
+
if (route.security.rateLimit.maxRequests && route.security.rateLimit.maxRequests < 0) {
|
|
204
|
+
errors.push('Rate limit maxRequests must be positive');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (route.security.rateLimit.window && route.security.rateLimit.window < 0) {
|
|
208
|
+
errors.push('Rate limit window must be positive');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Validate connection limits
|
|
213
|
+
if (route.security.maxConnections && route.security.maxConnections < 0) {
|
|
214
|
+
errors.push('Max connections must be positive');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Validate priority
|
|
219
|
+
if (route.priority !== undefined && (route.priority < 0 || route.priority > 10000)) {
|
|
220
|
+
errors.push('Priority must be between 0 and 10000');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
valid: errors.length === 0,
|
|
225
|
+
errors
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Validate multiple route configurations
|
|
231
|
+
*/
|
|
232
|
+
public static validateRoutes(routes: IRouteConfig[]): { valid: boolean; errors: Map<string, string[]> } {
|
|
233
|
+
const errorMap = new Map<string, string[]>();
|
|
234
|
+
let valid = true;
|
|
235
|
+
|
|
236
|
+
// Check for duplicate route names
|
|
237
|
+
const routeNames = new Set<string>();
|
|
238
|
+
for (const route of routes) {
|
|
239
|
+
if (route.name && routeNames.has(route.name)) {
|
|
240
|
+
const existingErrors = errorMap.get(route.name) || [];
|
|
241
|
+
existingErrors.push('Duplicate route name');
|
|
242
|
+
errorMap.set(route.name, existingErrors);
|
|
243
|
+
valid = false;
|
|
244
|
+
}
|
|
245
|
+
routeNames.add(route.name);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Validate each route
|
|
249
|
+
for (const route of routes) {
|
|
250
|
+
const result = this.validateRoute(route);
|
|
251
|
+
if (!result.valid) {
|
|
252
|
+
errorMap.set(route.name || 'unnamed', result.errors);
|
|
253
|
+
valid = false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check for conflicting routes
|
|
258
|
+
const conflicts = this.findRouteConflicts(routes);
|
|
259
|
+
if (conflicts.length > 0) {
|
|
260
|
+
for (const conflict of conflicts) {
|
|
261
|
+
const existingErrors = errorMap.get(conflict.route) || [];
|
|
262
|
+
existingErrors.push(conflict.message);
|
|
263
|
+
errorMap.set(conflict.route, existingErrors);
|
|
264
|
+
}
|
|
265
|
+
valid = false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { valid, errors: errorMap };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Find potential conflicts between routes
|
|
273
|
+
*/
|
|
274
|
+
private static findRouteConflicts(routes: IRouteConfig[]): Array<{ route: string; message: string }> {
|
|
275
|
+
const conflicts: Array<{ route: string; message: string }> = [];
|
|
276
|
+
|
|
277
|
+
// Group routes by port
|
|
278
|
+
const portMap = new Map<number, IRouteConfig[]>();
|
|
279
|
+
|
|
280
|
+
for (const route of routes) {
|
|
281
|
+
if (route.match?.ports) {
|
|
282
|
+
const ports = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports];
|
|
283
|
+
|
|
284
|
+
// Expand port ranges to individual ports
|
|
285
|
+
const expandedPorts: number[] = [];
|
|
286
|
+
for (const port of ports) {
|
|
287
|
+
if (typeof port === 'number') {
|
|
288
|
+
expandedPorts.push(port);
|
|
289
|
+
} else if (typeof port === 'object' && 'from' in port && 'to' in port) {
|
|
290
|
+
for (let p = port.from; p <= port.to; p++) {
|
|
291
|
+
expandedPorts.push(p);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
for (const port of expandedPorts) {
|
|
297
|
+
const routesOnPort = portMap.get(port) || [];
|
|
298
|
+
routesOnPort.push(route);
|
|
299
|
+
portMap.set(port, routesOnPort);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Check for conflicting catch-all routes on the same port
|
|
305
|
+
for (const [port, routesOnPort] of portMap) {
|
|
306
|
+
const catchAllRoutes = routesOnPort.filter(r =>
|
|
307
|
+
!r.match.domains ||
|
|
308
|
+
(Array.isArray(r.match.domains) && r.match.domains.includes('*')) ||
|
|
309
|
+
r.match.domains === '*'
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
if (catchAllRoutes.length > 1) {
|
|
313
|
+
for (const route of catchAllRoutes) {
|
|
314
|
+
conflicts.push({
|
|
315
|
+
route: route.name,
|
|
316
|
+
message: `Multiple catch-all routes on port ${port}`
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return conflicts;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Validate port number
|
|
327
|
+
*/
|
|
328
|
+
private static isValidPort(port: number): boolean {
|
|
329
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Validate domain pattern
|
|
334
|
+
*/
|
|
335
|
+
private static isValidDomain(domain: string): boolean {
|
|
336
|
+
if (!domain || typeof domain !== 'string') return false;
|
|
337
|
+
if (domain === '*') return true;
|
|
338
|
+
|
|
339
|
+
// Basic domain pattern validation
|
|
340
|
+
const domainPattern = /^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
|
|
341
|
+
return domainPattern.test(domain) || domain === 'localhost';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Validate path pattern
|
|
346
|
+
*/
|
|
347
|
+
private static isValidPath(path: string): boolean {
|
|
348
|
+
if (!path || typeof path !== 'string') return false;
|
|
349
|
+
if (!path.startsWith('/')) return false;
|
|
350
|
+
|
|
351
|
+
// Check for invalid characters
|
|
352
|
+
if (!/^[a-zA-Z0-9/_*:{}.-]+$/.test(path)) return false;
|
|
353
|
+
|
|
354
|
+
// Validate parameter syntax
|
|
355
|
+
const paramPattern = /\{[a-zA-Z_][a-zA-Z0-9_]*\}/g;
|
|
356
|
+
const params = path.match(paramPattern) || [];
|
|
357
|
+
|
|
358
|
+
for (const param of params) {
|
|
359
|
+
if (param.length > 32) return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Validate IP pattern
|
|
367
|
+
*/
|
|
368
|
+
private static isValidIPPattern(ip: string): boolean {
|
|
369
|
+
if (!ip || typeof ip !== 'string') return false;
|
|
370
|
+
if (ip === '*') return true;
|
|
371
|
+
|
|
372
|
+
// Check for CIDR notation
|
|
373
|
+
if (ip.includes('/')) {
|
|
374
|
+
const [addr, prefix] = ip.split('/');
|
|
375
|
+
const prefixNum = parseInt(prefix, 10);
|
|
376
|
+
|
|
377
|
+
if (addr.includes(':')) {
|
|
378
|
+
// IPv6 CIDR
|
|
379
|
+
return this.isValidIPv6(addr) && prefixNum >= 0 && prefixNum <= 128;
|
|
380
|
+
} else {
|
|
381
|
+
// IPv4 CIDR
|
|
382
|
+
return this.isValidIPv4(addr) && prefixNum >= 0 && prefixNum <= 32;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check for range
|
|
387
|
+
if (ip.includes('-')) {
|
|
388
|
+
const [start, end] = ip.split('-');
|
|
389
|
+
return (this.isValidIPv4(start) && this.isValidIPv4(end)) ||
|
|
390
|
+
(this.isValidIPv6(start) && this.isValidIPv6(end));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Check for wildcards in IPv4
|
|
394
|
+
if (ip.includes('*') && !ip.includes(':')) {
|
|
395
|
+
const parts = ip.split('.');
|
|
396
|
+
if (parts.length !== 4) return false;
|
|
397
|
+
|
|
398
|
+
for (const part of parts) {
|
|
399
|
+
if (part !== '*' && !/^\d{1,3}$/.test(part)) return false;
|
|
400
|
+
if (part !== '*' && parseInt(part, 10) > 255) return false;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Regular IP address
|
|
407
|
+
return this.isValidIPv4(ip) || this.isValidIPv6(ip);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Validate IPv4 address
|
|
412
|
+
*/
|
|
413
|
+
private static isValidIPv4(ip: string): boolean {
|
|
414
|
+
const parts = ip.split('.');
|
|
415
|
+
if (parts.length !== 4) return false;
|
|
416
|
+
|
|
417
|
+
for (const part of parts) {
|
|
418
|
+
const num = parseInt(part, 10);
|
|
419
|
+
if (isNaN(num) || num < 0 || num > 255) return false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Validate IPv6 address
|
|
427
|
+
*/
|
|
428
|
+
private static isValidIPv6(ip: string): boolean {
|
|
429
|
+
// Simple IPv6 validation
|
|
430
|
+
const ipv6Pattern = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|::[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{1,4}){0,6}|::1|::)$/;
|
|
431
|
+
return ipv6Pattern.test(ip);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Log validation errors
|
|
436
|
+
*/
|
|
437
|
+
public static logValidationErrors(errors: Map<string, string[]>): void {
|
|
438
|
+
for (const [routeName, routeErrors] of errors) {
|
|
439
|
+
logger.log('error', `Route validation failed for ${routeName}:`, {
|
|
440
|
+
route: routeName,
|
|
441
|
+
errors: routeErrors,
|
|
442
|
+
component: 'route-validator'
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
for (const error of routeErrors) {
|
|
446
|
+
logger.log('error', ` - ${error}`, {
|
|
447
|
+
route: routeName,
|
|
448
|
+
component: 'route-validator'
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|