@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.
- package/changelog.md +89 -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 +266 -6
- 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 +289 -7
- package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
- package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from '../../../core/utils/logger.js';
|
|
2
|
-
import type { IRouteConfig } from '../models/route-types.js';
|
|
2
|
+
import type { IRouteConfig, IRouteMatch, IRouteAction, TPortRange } from '../models/route-types.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Validates route configurations for correctness and safety
|
|
@@ -335,10 +335,22 @@ export class RouteValidator {
|
|
|
335
335
|
private static isValidDomain(domain: string): boolean {
|
|
336
336
|
if (!domain || typeof domain !== 'string') return false;
|
|
337
337
|
if (domain === '*') return true;
|
|
338
|
+
if (domain === 'localhost') return true;
|
|
338
339
|
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
340
|
+
// Allow both *.domain and *domain patterns
|
|
341
|
+
// Also allow regular domains and subdomains
|
|
342
|
+
const domainPatterns = [
|
|
343
|
+
// Standard domain with optional wildcard subdomain (*.example.com)
|
|
344
|
+
/^(\*\.)?([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])?$/,
|
|
345
|
+
// Wildcard prefix without dot (*example.com)
|
|
346
|
+
/^\*[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])?))*$/,
|
|
347
|
+
// IP address
|
|
348
|
+
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
|
|
349
|
+
// IPv6 address
|
|
350
|
+
/^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
return domainPatterns.some(pattern => pattern.test(domain));
|
|
342
354
|
}
|
|
343
355
|
|
|
344
356
|
/**
|
|
@@ -427,8 +439,8 @@ export class RouteValidator {
|
|
|
427
439
|
* Validate IPv6 address
|
|
428
440
|
*/
|
|
429
441
|
private static isValidIPv6(ip: string): boolean {
|
|
430
|
-
//
|
|
431
|
-
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
|
|
442
|
+
// IPv6 validation including IPv6-mapped IPv4 addresses (::ffff:x.x.x.x)
|
|
443
|
+
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|::|::ffff:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i;
|
|
432
444
|
return ipv6Pattern.test(ip);
|
|
433
445
|
}
|
|
434
446
|
|
|
@@ -442,7 +454,7 @@ export class RouteValidator {
|
|
|
442
454
|
errors: routeErrors,
|
|
443
455
|
component: 'route-validator'
|
|
444
456
|
});
|
|
445
|
-
|
|
457
|
+
|
|
446
458
|
for (const error of routeErrors) {
|
|
447
459
|
logger.log('error', ` - ${error}`, {
|
|
448
460
|
route: routeName,
|
|
@@ -451,4 +463,274 @@ export class RouteValidator {
|
|
|
451
463
|
}
|
|
452
464
|
}
|
|
453
465
|
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ============================================================================
|
|
469
|
+
// Functional API (for backwards compatibility with route-validators.ts)
|
|
470
|
+
// ============================================================================
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Validates a port range or port number
|
|
474
|
+
* @param port Port number, port range, or port function
|
|
475
|
+
* @returns True if valid, false otherwise
|
|
476
|
+
*/
|
|
477
|
+
export function isValidPort(port: any): boolean {
|
|
478
|
+
if (typeof port === 'number') {
|
|
479
|
+
return port > 0 && port < 65536;
|
|
480
|
+
} else if (Array.isArray(port)) {
|
|
481
|
+
return port.every(p =>
|
|
482
|
+
(typeof p === 'number' && p > 0 && p < 65536) ||
|
|
483
|
+
(typeof p === 'object' && 'from' in p && 'to' in p &&
|
|
484
|
+
p.from > 0 && p.from < 65536 && p.to > 0 && p.to < 65536)
|
|
485
|
+
);
|
|
486
|
+
} else if (typeof port === 'function') {
|
|
487
|
+
return true;
|
|
488
|
+
} else if (typeof port === 'object' && 'from' in port && 'to' in port) {
|
|
489
|
+
return port.from > 0 && port.from < 65536 && port.to > 0 && port.to < 65536;
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Validates a domain string - supports wildcards, localhost, and IP addresses
|
|
496
|
+
* @param domain Domain string to validate
|
|
497
|
+
* @returns True if valid, false otherwise
|
|
498
|
+
*/
|
|
499
|
+
export function isValidDomain(domain: string): boolean {
|
|
500
|
+
if (!domain || typeof domain !== 'string') return false;
|
|
501
|
+
if (domain === '*') return true;
|
|
502
|
+
if (domain === 'localhost') return true;
|
|
503
|
+
|
|
504
|
+
const domainPatterns = [
|
|
505
|
+
// Standard domain with optional wildcard subdomain (*.example.com)
|
|
506
|
+
/^(\*\.)?([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])?$/,
|
|
507
|
+
// Wildcard prefix without dot (*example.com)
|
|
508
|
+
/^\*[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])?))*$/,
|
|
509
|
+
// IP address
|
|
510
|
+
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
|
|
511
|
+
// IPv6 address
|
|
512
|
+
/^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/
|
|
513
|
+
];
|
|
514
|
+
|
|
515
|
+
return domainPatterns.some(pattern => pattern.test(domain));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Validates a route match configuration
|
|
520
|
+
* @param match Route match configuration to validate
|
|
521
|
+
* @returns { valid: boolean, errors: string[] } Validation result
|
|
522
|
+
*/
|
|
523
|
+
export function validateRouteMatch(match: IRouteMatch): { valid: boolean; errors: string[] } {
|
|
524
|
+
const errors: string[] = [];
|
|
525
|
+
|
|
526
|
+
if (match.ports !== undefined) {
|
|
527
|
+
if (!isValidPort(match.ports)) {
|
|
528
|
+
errors.push('Invalid port number or port range in match.ports');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (match.domains !== undefined) {
|
|
533
|
+
if (typeof match.domains === 'string') {
|
|
534
|
+
if (!isValidDomain(match.domains)) {
|
|
535
|
+
errors.push(`Invalid domain format: ${match.domains}`);
|
|
536
|
+
}
|
|
537
|
+
} else if (Array.isArray(match.domains)) {
|
|
538
|
+
for (const domain of match.domains) {
|
|
539
|
+
if (!isValidDomain(domain)) {
|
|
540
|
+
errors.push(`Invalid domain format: ${domain}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
errors.push('Domains must be a string or an array of strings');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (match.path !== undefined) {
|
|
549
|
+
if (typeof match.path !== 'string' || !match.path.startsWith('/')) {
|
|
550
|
+
errors.push('Path must be a string starting with /');
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
valid: errors.length === 0,
|
|
556
|
+
errors
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Validates a route action configuration
|
|
562
|
+
* @param action Route action configuration to validate
|
|
563
|
+
* @returns { valid: boolean, errors: string[] } Validation result
|
|
564
|
+
*/
|
|
565
|
+
export function validateRouteAction(action: IRouteAction): { valid: boolean; errors: string[] } {
|
|
566
|
+
const errors: string[] = [];
|
|
567
|
+
|
|
568
|
+
if (!action.type) {
|
|
569
|
+
errors.push('Action type is required');
|
|
570
|
+
} else if (!['forward', 'socket-handler'].includes(action.type)) {
|
|
571
|
+
errors.push(`Invalid action type: ${action.type}`);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (action.type === 'forward') {
|
|
575
|
+
if (!action.targets || !Array.isArray(action.targets) || action.targets.length === 0) {
|
|
576
|
+
errors.push('Targets array is required for forward action');
|
|
577
|
+
} else {
|
|
578
|
+
action.targets.forEach((target, index) => {
|
|
579
|
+
if (!target.host) {
|
|
580
|
+
errors.push(`Target[${index}] host is required`);
|
|
581
|
+
} else if (typeof target.host !== 'string' &&
|
|
582
|
+
!Array.isArray(target.host) &&
|
|
583
|
+
typeof target.host !== 'function') {
|
|
584
|
+
errors.push(`Target[${index}] host must be a string, array of strings, or function`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (target.port === undefined) {
|
|
588
|
+
errors.push(`Target[${index}] port is required`);
|
|
589
|
+
} else if (typeof target.port !== 'number' &&
|
|
590
|
+
typeof target.port !== 'function' &&
|
|
591
|
+
target.port !== 'preserve') {
|
|
592
|
+
errors.push(`Target[${index}] port must be a number, 'preserve', or a function`);
|
|
593
|
+
} else if (typeof target.port === 'number' && !isValidPort(target.port)) {
|
|
594
|
+
errors.push(`Target[${index}] port must be between 1 and 65535`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (target.match) {
|
|
598
|
+
if (target.match.ports && !Array.isArray(target.match.ports)) {
|
|
599
|
+
errors.push(`Target[${index}] match.ports must be an array`);
|
|
600
|
+
}
|
|
601
|
+
if (target.match.method && !Array.isArray(target.match.method)) {
|
|
602
|
+
errors.push(`Target[${index}] match.method must be an array`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (action.tls) {
|
|
609
|
+
if (!['passthrough', 'terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) {
|
|
610
|
+
errors.push(`Invalid TLS mode: ${action.tls.mode}`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (['terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) {
|
|
614
|
+
if (action.tls.certificate !== 'auto' &&
|
|
615
|
+
(!action.tls.certificate || !action.tls.certificate.key || !action.tls.certificate.cert)) {
|
|
616
|
+
errors.push('Certificate must be "auto" or an object with key and cert properties');
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (action.type === 'socket-handler') {
|
|
623
|
+
if (!action.socketHandler) {
|
|
624
|
+
errors.push('Socket handler function is required for socket-handler action');
|
|
625
|
+
} else if (typeof action.socketHandler !== 'function') {
|
|
626
|
+
errors.push('Socket handler must be a function');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
valid: errors.length === 0,
|
|
632
|
+
errors
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Validates a complete route configuration
|
|
638
|
+
* @param route Route configuration to validate
|
|
639
|
+
* @returns { valid: boolean, errors: string[] } Validation result
|
|
640
|
+
*/
|
|
641
|
+
export function validateRouteConfig(route: IRouteConfig): { valid: boolean; errors: string[] } {
|
|
642
|
+
const errors: string[] = [];
|
|
643
|
+
|
|
644
|
+
if (!route.match) {
|
|
645
|
+
errors.push('Route match configuration is required');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (!route.action) {
|
|
649
|
+
errors.push('Route action configuration is required');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (route.match) {
|
|
653
|
+
const matchValidation = validateRouteMatch(route.match);
|
|
654
|
+
if (!matchValidation.valid) {
|
|
655
|
+
errors.push(...matchValidation.errors.map(err => `Match: ${err}`));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (route.action) {
|
|
660
|
+
const actionValidation = validateRouteAction(route.action);
|
|
661
|
+
if (!actionValidation.valid) {
|
|
662
|
+
errors.push(...actionValidation.errors.map(err => `Action: ${err}`));
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return {
|
|
667
|
+
valid: errors.length === 0,
|
|
668
|
+
errors
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Validate an array of route configurations
|
|
674
|
+
* @param routes Array of route configurations to validate
|
|
675
|
+
* @returns { valid: boolean, errors: { index: number, errors: string[] }[] } Validation result
|
|
676
|
+
*/
|
|
677
|
+
export function validateRoutes(routes: IRouteConfig[]): {
|
|
678
|
+
valid: boolean;
|
|
679
|
+
errors: { index: number; errors: string[] }[]
|
|
680
|
+
} {
|
|
681
|
+
const results: { index: number; errors: string[] }[] = [];
|
|
682
|
+
|
|
683
|
+
routes.forEach((route, index) => {
|
|
684
|
+
const validation = validateRouteConfig(route);
|
|
685
|
+
if (!validation.valid) {
|
|
686
|
+
results.push({
|
|
687
|
+
index,
|
|
688
|
+
errors: validation.errors
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
valid: results.length === 0,
|
|
695
|
+
errors: results
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Check if a route configuration has the required properties for a specific action type
|
|
701
|
+
* @param route Route configuration to check
|
|
702
|
+
* @param actionType Expected action type
|
|
703
|
+
* @returns True if the route has the necessary properties, false otherwise
|
|
704
|
+
*/
|
|
705
|
+
export function hasRequiredPropertiesForAction(route: IRouteConfig, actionType: string): boolean {
|
|
706
|
+
if (!route.action || route.action.type !== actionType) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
switch (actionType) {
|
|
711
|
+
case 'forward':
|
|
712
|
+
return !!route.action.targets &&
|
|
713
|
+
Array.isArray(route.action.targets) &&
|
|
714
|
+
route.action.targets.length > 0 &&
|
|
715
|
+
route.action.targets.every(t => t.host && t.port !== undefined);
|
|
716
|
+
case 'socket-handler':
|
|
717
|
+
return !!route.action.socketHandler && typeof route.action.socketHandler === 'function';
|
|
718
|
+
default:
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Throws an error if the route config is invalid, returns the config if valid
|
|
725
|
+
* Useful for immediate validation when creating routes
|
|
726
|
+
* @param route Route configuration to validate
|
|
727
|
+
* @returns The validated route configuration
|
|
728
|
+
* @throws Error if the route configuration is invalid
|
|
729
|
+
*/
|
|
730
|
+
export function assertValidRoute(route: IRouteConfig): IRouteConfig {
|
|
731
|
+
const validation = validateRouteConfig(route);
|
|
732
|
+
if (!validation.valid) {
|
|
733
|
+
throw new Error(`Invalid route configuration: ${validation.errors.join(', ')}`);
|
|
734
|
+
}
|
|
735
|
+
return route;
|
|
454
736
|
}
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../../plugins.js';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import { AsyncFileSystem } from '../../core/utils/fs-utils.js';
|
|
6
|
-
import { type IHttpProxyOptions, type ICertificateEntry, type ILogger, createLogger } from './models/types.js';
|
|
7
|
-
import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @deprecated This class is deprecated. Use SmartCertManager instead.
|
|
11
|
-
*
|
|
12
|
-
* This is a stub implementation that maintains backward compatibility
|
|
13
|
-
* while the functionality has been moved to SmartCertManager.
|
|
14
|
-
*/
|
|
15
|
-
export class CertificateManager {
|
|
16
|
-
private defaultCertificates: { key: string; cert: string };
|
|
17
|
-
private certificateCache: Map<string, ICertificateEntry> = new Map();
|
|
18
|
-
private certificateStoreDir: string;
|
|
19
|
-
private logger: ILogger;
|
|
20
|
-
private httpsServer: plugins.https.Server | null = null;
|
|
21
|
-
private initialized = false;
|
|
22
|
-
|
|
23
|
-
constructor(private options: IHttpProxyOptions) {
|
|
24
|
-
this.certificateStoreDir = path.resolve(options.acme?.certificateStore || './certs');
|
|
25
|
-
this.logger = createLogger(options.logLevel || 'info');
|
|
26
|
-
|
|
27
|
-
this.logger.warn('CertificateManager is deprecated - use SmartCertManager instead');
|
|
28
|
-
|
|
29
|
-
// Initialize synchronously for backward compatibility but log warning
|
|
30
|
-
this.initializeSync();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Synchronous initialization for backward compatibility
|
|
35
|
-
* @deprecated This uses sync filesystem operations which block the event loop
|
|
36
|
-
*/
|
|
37
|
-
private initializeSync(): void {
|
|
38
|
-
// Ensure certificate store directory exists
|
|
39
|
-
try {
|
|
40
|
-
if (!fs.existsSync(this.certificateStoreDir)) {
|
|
41
|
-
fs.mkdirSync(this.certificateStoreDir, { recursive: true });
|
|
42
|
-
this.logger.info(`Created certificate store directory: ${this.certificateStoreDir}`);
|
|
43
|
-
}
|
|
44
|
-
} catch (error) {
|
|
45
|
-
this.logger.warn(`Failed to create certificate store directory: ${error}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
this.loadDefaultCertificates();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Async initialization - preferred method
|
|
53
|
-
*/
|
|
54
|
-
public async initialize(): Promise<void> {
|
|
55
|
-
if (this.initialized) return;
|
|
56
|
-
|
|
57
|
-
// Ensure certificate store directory exists
|
|
58
|
-
try {
|
|
59
|
-
await AsyncFileSystem.ensureDir(this.certificateStoreDir);
|
|
60
|
-
this.logger.info(`Ensured certificate store directory: ${this.certificateStoreDir}`);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
this.logger.warn(`Failed to create certificate store directory: ${error}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
await this.loadDefaultCertificatesAsync();
|
|
66
|
-
this.initialized = true;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Loads default certificates from the filesystem
|
|
71
|
-
* @deprecated This uses sync filesystem operations which block the event loop
|
|
72
|
-
*/
|
|
73
|
-
public loadDefaultCertificates(): void {
|
|
74
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
75
|
-
const certPath = path.join(__dirname, '..', '..', '..', 'assets', 'certs');
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
this.defaultCertificates = {
|
|
79
|
-
key: fs.readFileSync(path.join(certPath, 'key.pem'), 'utf8'),
|
|
80
|
-
cert: fs.readFileSync(path.join(certPath, 'cert.pem'), 'utf8')
|
|
81
|
-
};
|
|
82
|
-
this.logger.info('Loaded default certificates from filesystem (sync - deprecated)');
|
|
83
|
-
} catch (error) {
|
|
84
|
-
this.logger.error(`Failed to load default certificates: ${error}`);
|
|
85
|
-
this.generateSelfSignedCertificate();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Loads default certificates from the filesystem asynchronously
|
|
91
|
-
*/
|
|
92
|
-
public async loadDefaultCertificatesAsync(): Promise<void> {
|
|
93
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
94
|
-
const certPath = path.join(__dirname, '..', '..', '..', 'assets', 'certs');
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const [key, cert] = await Promise.all([
|
|
98
|
-
AsyncFileSystem.readFile(path.join(certPath, 'key.pem')),
|
|
99
|
-
AsyncFileSystem.readFile(path.join(certPath, 'cert.pem'))
|
|
100
|
-
]);
|
|
101
|
-
|
|
102
|
-
this.defaultCertificates = { key, cert };
|
|
103
|
-
this.logger.info('Loaded default certificates from filesystem (async)');
|
|
104
|
-
} catch (error) {
|
|
105
|
-
this.logger.error(`Failed to load default certificates: ${error}`);
|
|
106
|
-
this.generateSelfSignedCertificate();
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Generates self-signed certificates as fallback
|
|
112
|
-
*/
|
|
113
|
-
private generateSelfSignedCertificate(): void {
|
|
114
|
-
// Generate a self-signed certificate using forge or similar
|
|
115
|
-
// For now, just use a placeholder
|
|
116
|
-
const selfSignedCert = `-----BEGIN CERTIFICATE-----
|
|
117
|
-
MIIBkTCB+wIJAKHHIgIIA0/cMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNVBAYTAlVT
|
|
118
|
-
MB4XDTE0MDEwMTAwMDAwMFoXDTI0MDEwMTAwMDAwMFowDTELMAkGA1UEBhMCVVMw
|
|
119
|
-
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMRiH0VwnOH3jCV7c6JFZWYrvuqy
|
|
120
|
-
-----END CERTIFICATE-----`;
|
|
121
|
-
|
|
122
|
-
const selfSignedKey = `-----BEGIN PRIVATE KEY-----
|
|
123
|
-
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMRiH0VwnOH3jCV7
|
|
124
|
-
c6JFZWYrvuqyALCLXj0pcr1iqNdHjegNXnkl5zjdaUjq4edNOKl7M1AlFiYjG2xk
|
|
125
|
-
-----END PRIVATE KEY-----`;
|
|
126
|
-
|
|
127
|
-
this.defaultCertificates = {
|
|
128
|
-
key: selfSignedKey,
|
|
129
|
-
cert: selfSignedCert
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
this.logger.warn('Using self-signed certificate as fallback');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Gets the default certificates
|
|
137
|
-
*/
|
|
138
|
-
public getDefaultCertificates(): { key: string; cert: string } {
|
|
139
|
-
return this.defaultCertificates;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* @deprecated Use SmartCertManager instead
|
|
144
|
-
*/
|
|
145
|
-
public setExternalPort80Handler(handler: any): void {
|
|
146
|
-
this.logger.warn('setExternalPort80Handler is deprecated - use SmartCertManager instead');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* @deprecated Use SmartCertManager instead
|
|
151
|
-
*/
|
|
152
|
-
public async updateRoutes(routes: IRouteConfig[]): Promise<void> {
|
|
153
|
-
this.logger.warn('updateRoutes is deprecated - use SmartCertManager instead');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Handles SNI callback to provide appropriate certificate
|
|
158
|
-
*/
|
|
159
|
-
public handleSNI(domain: string, cb: (err: Error | null, ctx: plugins.tls.SecureContext) => void): void {
|
|
160
|
-
const certificate = this.getCachedCertificate(domain);
|
|
161
|
-
|
|
162
|
-
if (certificate) {
|
|
163
|
-
const context = plugins.tls.createSecureContext({
|
|
164
|
-
key: certificate.key,
|
|
165
|
-
cert: certificate.cert
|
|
166
|
-
});
|
|
167
|
-
cb(null, context);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Use default certificate if no domain-specific certificate found
|
|
172
|
-
const defaultContext = plugins.tls.createSecureContext({
|
|
173
|
-
key: this.defaultCertificates.key,
|
|
174
|
-
cert: this.defaultCertificates.cert
|
|
175
|
-
});
|
|
176
|
-
cb(null, defaultContext);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Updates a certificate in the cache
|
|
181
|
-
*/
|
|
182
|
-
public updateCertificate(domain: string, cert: string, key: string): void {
|
|
183
|
-
this.certificateCache.set(domain, {
|
|
184
|
-
cert,
|
|
185
|
-
key,
|
|
186
|
-
expires: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) // 90 days
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
this.logger.info(`Certificate updated for ${domain}`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Gets a cached certificate
|
|
194
|
-
*/
|
|
195
|
-
private getCachedCertificate(domain: string): ICertificateEntry | null {
|
|
196
|
-
return this.certificateCache.get(domain) || null;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* @deprecated Use SmartCertManager instead
|
|
201
|
-
*/
|
|
202
|
-
public async initializePort80Handler(): Promise<any> {
|
|
203
|
-
this.logger.warn('initializePort80Handler is deprecated - use SmartCertManager instead');
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* @deprecated Use SmartCertManager instead
|
|
209
|
-
*/
|
|
210
|
-
public async stopPort80Handler(): Promise<void> {
|
|
211
|
-
this.logger.warn('stopPort80Handler is deprecated - use SmartCertManager instead');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* @deprecated Use SmartCertManager instead
|
|
216
|
-
*/
|
|
217
|
-
public registerDomainsWithPort80Handler(domains: string[]): void {
|
|
218
|
-
this.logger.warn('registerDomainsWithPort80Handler is deprecated - use SmartCertManager instead');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* @deprecated Use SmartCertManager instead
|
|
223
|
-
*/
|
|
224
|
-
public registerRoutesWithPort80Handler(routes: IRouteConfig[]): void {
|
|
225
|
-
this.logger.warn('registerRoutesWithPort80Handler is deprecated - use SmartCertManager instead');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Sets the HTTPS server for certificate updates
|
|
230
|
-
*/
|
|
231
|
-
public setHttpsServer(server: plugins.https.Server): void {
|
|
232
|
-
this.httpsServer = server;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Gets statistics for metrics
|
|
237
|
-
*/
|
|
238
|
-
public getStats() {
|
|
239
|
-
return {
|
|
240
|
-
cachedCertificates: this.certificateCache.size,
|
|
241
|
-
defaultCertEnabled: true
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|