@push.rocks/smartproxy 3.41.8 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/classes.pp.acmemanager.d.ts +34 -0
- package/dist_ts/classes.pp.acmemanager.js +123 -0
- package/dist_ts/classes.pp.connectionhandler.d.ts +39 -0
- package/dist_ts/classes.pp.connectionhandler.js +693 -0
- package/dist_ts/classes.pp.connectionmanager.d.ts +78 -0
- package/dist_ts/classes.pp.connectionmanager.js +378 -0
- package/dist_ts/classes.pp.domainconfigmanager.d.ts +55 -0
- package/dist_ts/classes.pp.domainconfigmanager.js +103 -0
- package/dist_ts/classes.pp.interfaces.d.ts +109 -0
- package/dist_ts/classes.pp.interfaces.js +2 -0
- package/dist_ts/classes.pp.networkproxybridge.d.ts +43 -0
- package/dist_ts/classes.pp.networkproxybridge.js +211 -0
- package/dist_ts/classes.pp.portproxy.d.ts +48 -0
- package/dist_ts/classes.pp.portproxy.js +268 -0
- package/dist_ts/classes.pp.portrangemanager.d.ts +56 -0
- package/dist_ts/classes.pp.portrangemanager.js +179 -0
- package/dist_ts/classes.pp.securitymanager.d.ts +47 -0
- package/dist_ts/classes.pp.securitymanager.js +126 -0
- package/dist_ts/classes.pp.snihandler.d.ts +198 -0
- package/dist_ts/classes.pp.snihandler.js +1210 -0
- package/dist_ts/classes.pp.timeoutmanager.d.ts +47 -0
- package/dist_ts/classes.pp.timeoutmanager.js +154 -0
- package/dist_ts/classes.pp.tlsmanager.d.ts +57 -0
- package/dist_ts/classes.pp.tlsmanager.js +132 -0
- package/dist_ts/index.d.ts +2 -2
- package/dist_ts/index.js +3 -3
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.pp.acmemanager.ts +149 -0
- package/ts/classes.pp.connectionhandler.ts +982 -0
- package/ts/classes.pp.connectionmanager.ts +446 -0
- package/ts/classes.pp.domainconfigmanager.ts +123 -0
- package/ts/classes.pp.interfaces.ts +136 -0
- package/ts/classes.pp.networkproxybridge.ts +258 -0
- package/ts/classes.pp.portproxy.ts +344 -0
- package/ts/classes.pp.portrangemanager.ts +214 -0
- package/ts/classes.pp.securitymanager.ts +147 -0
- package/ts/{classes.snihandler.ts → classes.pp.snihandler.ts} +1 -1
- package/ts/classes.pp.timeoutmanager.ts +190 -0
- package/ts/classes.pp.tlsmanager.ts +206 -0
- package/ts/index.ts +2 -2
- package/ts/classes.portproxy.ts +0 -2503
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { IConnectionRecord, IPortProxySettings } from './classes.pp.interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manages timeouts and inactivity tracking for connections
|
|
4
|
+
*/
|
|
5
|
+
export declare class TimeoutManager {
|
|
6
|
+
private settings;
|
|
7
|
+
constructor(settings: IPortProxySettings);
|
|
8
|
+
/**
|
|
9
|
+
* Ensure timeout values don't exceed Node.js max safe integer
|
|
10
|
+
*/
|
|
11
|
+
ensureSafeTimeout(timeout: number): number;
|
|
12
|
+
/**
|
|
13
|
+
* Generate a slightly randomized timeout to prevent thundering herd
|
|
14
|
+
*/
|
|
15
|
+
randomizeTimeout(baseTimeout: number, variationPercent?: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* Update connection activity timestamp
|
|
18
|
+
*/
|
|
19
|
+
updateActivity(record: IConnectionRecord): void;
|
|
20
|
+
/**
|
|
21
|
+
* Calculate effective inactivity timeout based on connection type
|
|
22
|
+
*/
|
|
23
|
+
getEffectiveInactivityTimeout(record: IConnectionRecord): number;
|
|
24
|
+
/**
|
|
25
|
+
* Calculate effective max lifetime based on connection type
|
|
26
|
+
*/
|
|
27
|
+
getEffectiveMaxLifetime(record: IConnectionRecord): number;
|
|
28
|
+
/**
|
|
29
|
+
* Setup connection timeout
|
|
30
|
+
* @returns The cleanup timer
|
|
31
|
+
*/
|
|
32
|
+
setupConnectionTimeout(record: IConnectionRecord, onTimeout: (record: IConnectionRecord, reason: string) => void): NodeJS.Timeout;
|
|
33
|
+
/**
|
|
34
|
+
* Check for inactivity on a connection
|
|
35
|
+
* @returns Object with check results
|
|
36
|
+
*/
|
|
37
|
+
checkInactivity(record: IConnectionRecord): {
|
|
38
|
+
isInactive: boolean;
|
|
39
|
+
shouldWarn: boolean;
|
|
40
|
+
inactivityTime: number;
|
|
41
|
+
effectiveTimeout: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Apply socket timeout settings
|
|
45
|
+
*/
|
|
46
|
+
applySocketTimeouts(record: IConnectionRecord): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages timeouts and inactivity tracking for connections
|
|
3
|
+
*/
|
|
4
|
+
export class TimeoutManager {
|
|
5
|
+
constructor(settings) {
|
|
6
|
+
this.settings = settings;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Ensure timeout values don't exceed Node.js max safe integer
|
|
10
|
+
*/
|
|
11
|
+
ensureSafeTimeout(timeout) {
|
|
12
|
+
const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
|
|
13
|
+
return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate a slightly randomized timeout to prevent thundering herd
|
|
17
|
+
*/
|
|
18
|
+
randomizeTimeout(baseTimeout, variationPercent = 5) {
|
|
19
|
+
const safeBaseTimeout = this.ensureSafeTimeout(baseTimeout);
|
|
20
|
+
const variation = safeBaseTimeout * (variationPercent / 100);
|
|
21
|
+
return this.ensureSafeTimeout(safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Update connection activity timestamp
|
|
25
|
+
*/
|
|
26
|
+
updateActivity(record) {
|
|
27
|
+
record.lastActivity = Date.now();
|
|
28
|
+
// Clear any inactivity warning
|
|
29
|
+
if (record.inactivityWarningIssued) {
|
|
30
|
+
record.inactivityWarningIssued = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Calculate effective inactivity timeout based on connection type
|
|
35
|
+
*/
|
|
36
|
+
getEffectiveInactivityTimeout(record) {
|
|
37
|
+
let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
|
|
38
|
+
// For immortal keep-alive connections, use an extremely long timeout
|
|
39
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
40
|
+
return Number.MAX_SAFE_INTEGER;
|
|
41
|
+
}
|
|
42
|
+
// For extended keep-alive connections, apply multiplier
|
|
43
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
44
|
+
const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
|
|
45
|
+
effectiveTimeout = effectiveTimeout * multiplier;
|
|
46
|
+
}
|
|
47
|
+
return this.ensureSafeTimeout(effectiveTimeout);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Calculate effective max lifetime based on connection type
|
|
51
|
+
*/
|
|
52
|
+
getEffectiveMaxLifetime(record) {
|
|
53
|
+
// Use domain-specific timeout if available
|
|
54
|
+
const baseTimeout = record.domainConfig?.connectionTimeout ||
|
|
55
|
+
this.settings.maxConnectionLifetime ||
|
|
56
|
+
86400000; // 24 hours default
|
|
57
|
+
// For immortal keep-alive connections, use an extremely long lifetime
|
|
58
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
59
|
+
return Number.MAX_SAFE_INTEGER;
|
|
60
|
+
}
|
|
61
|
+
// For extended keep-alive connections, use the extended lifetime setting
|
|
62
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
63
|
+
return this.ensureSafeTimeout(this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
// Apply randomization if enabled
|
|
67
|
+
if (this.settings.enableRandomizedTimeouts) {
|
|
68
|
+
return this.randomizeTimeout(baseTimeout);
|
|
69
|
+
}
|
|
70
|
+
return this.ensureSafeTimeout(baseTimeout);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Setup connection timeout
|
|
74
|
+
* @returns The cleanup timer
|
|
75
|
+
*/
|
|
76
|
+
setupConnectionTimeout(record, onTimeout) {
|
|
77
|
+
// Clear any existing timer
|
|
78
|
+
if (record.cleanupTimer) {
|
|
79
|
+
clearTimeout(record.cleanupTimer);
|
|
80
|
+
}
|
|
81
|
+
// Calculate effective timeout
|
|
82
|
+
const effectiveLifetime = this.getEffectiveMaxLifetime(record);
|
|
83
|
+
// Set up the timeout
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
// Call the provided callback
|
|
86
|
+
onTimeout(record, 'connection_timeout');
|
|
87
|
+
}, effectiveLifetime);
|
|
88
|
+
// Make sure timeout doesn't keep the process alive
|
|
89
|
+
if (timer.unref) {
|
|
90
|
+
timer.unref();
|
|
91
|
+
}
|
|
92
|
+
return timer;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check for inactivity on a connection
|
|
96
|
+
* @returns Object with check results
|
|
97
|
+
*/
|
|
98
|
+
checkInactivity(record) {
|
|
99
|
+
// Skip for connections with inactivity check disabled
|
|
100
|
+
if (this.settings.disableInactivityCheck) {
|
|
101
|
+
return {
|
|
102
|
+
isInactive: false,
|
|
103
|
+
shouldWarn: false,
|
|
104
|
+
inactivityTime: 0,
|
|
105
|
+
effectiveTimeout: 0
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Skip for immortal keep-alive connections
|
|
109
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
110
|
+
return {
|
|
111
|
+
isInactive: false,
|
|
112
|
+
shouldWarn: false,
|
|
113
|
+
inactivityTime: 0,
|
|
114
|
+
effectiveTimeout: 0
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
const inactivityTime = now - record.lastActivity;
|
|
119
|
+
const effectiveTimeout = this.getEffectiveInactivityTimeout(record);
|
|
120
|
+
// Check if inactive
|
|
121
|
+
const isInactive = inactivityTime > effectiveTimeout;
|
|
122
|
+
// For keep-alive connections, we should warn first
|
|
123
|
+
const shouldWarn = record.hasKeepAlive &&
|
|
124
|
+
isInactive &&
|
|
125
|
+
!record.inactivityWarningIssued;
|
|
126
|
+
return {
|
|
127
|
+
isInactive,
|
|
128
|
+
shouldWarn,
|
|
129
|
+
inactivityTime,
|
|
130
|
+
effectiveTimeout
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Apply socket timeout settings
|
|
135
|
+
*/
|
|
136
|
+
applySocketTimeouts(record) {
|
|
137
|
+
// Skip for immortal keep-alive connections
|
|
138
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
139
|
+
// Disable timeouts completely for immortal connections
|
|
140
|
+
record.incoming.setTimeout(0);
|
|
141
|
+
if (record.outgoing) {
|
|
142
|
+
record.outgoing.setTimeout(0);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Apply normal timeouts
|
|
147
|
+
const timeout = this.ensureSafeTimeout(this.settings.socketTimeout || 3600000); // 1 hour default
|
|
148
|
+
record.incoming.setTimeout(timeout);
|
|
149
|
+
if (record.outgoing) {
|
|
150
|
+
record.outgoing.setTimeout(timeout);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wcC50aW1lb3V0bWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucHAudGltZW91dG1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUN6QixZQUFvQixRQUE0QjtRQUE1QixhQUFRLEdBQVIsUUFBUSxDQUFvQjtJQUFHLENBQUM7SUFFcEQ7O09BRUc7SUFDSSxpQkFBaUIsQ0FBQyxPQUFlO1FBQ3RDLE1BQU0sZ0JBQWdCLEdBQUcsVUFBVSxDQUFDLENBQUMsZ0NBQWdDO1FBQ3JFLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxFQUFFLGdCQUFnQixDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksZ0JBQWdCLENBQUMsV0FBbUIsRUFBRSxtQkFBMkIsQ0FBQztRQUN2RSxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDNUQsTUFBTSxTQUFTLEdBQUcsZUFBZSxHQUFHLENBQUMsZ0JBQWdCLEdBQUcsR0FBRyxDQUFDLENBQUM7UUFDN0QsT0FBTyxJQUFJLENBQUMsaUJBQWlCLENBQzNCLGVBQWUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxTQUFTLEdBQUcsQ0FBQyxDQUFDLEdBQUcsU0FBUyxDQUN4RSxDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksY0FBYyxDQUFDLE1BQXlCO1FBQzdDLE1BQU0sQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRWpDLCtCQUErQjtRQUMvQixJQUFJLE1BQU0sQ0FBQyx1QkFBdUIsRUFBRSxDQUFDO1lBQ25DLE1BQU0sQ0FBQyx1QkFBdUIsR0FBRyxLQUFLLENBQUM7UUFDekMsQ0FBQztJQUNILENB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
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { IPortProxySettings } from './classes.pp.interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Interface for connection information used for SNI extraction
|
|
4
|
+
*/
|
|
5
|
+
interface IConnectionInfo {
|
|
6
|
+
sourceIp: string;
|
|
7
|
+
sourcePort: number;
|
|
8
|
+
destIp: string;
|
|
9
|
+
destPort: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Manages TLS-related operations including SNI extraction and validation
|
|
13
|
+
*/
|
|
14
|
+
export declare class TlsManager {
|
|
15
|
+
private settings;
|
|
16
|
+
constructor(settings: IPortProxySettings);
|
|
17
|
+
/**
|
|
18
|
+
* Check if a data chunk appears to be a TLS handshake
|
|
19
|
+
*/
|
|
20
|
+
isTlsHandshake(chunk: Buffer): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Check if a data chunk appears to be a TLS ClientHello
|
|
23
|
+
*/
|
|
24
|
+
isClientHello(chunk: Buffer): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Extract Server Name Indication (SNI) from TLS handshake
|
|
27
|
+
*/
|
|
28
|
+
extractSNI(chunk: Buffer, connInfo: IConnectionInfo, previousDomain?: string): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Handle session resumption attempts
|
|
31
|
+
*/
|
|
32
|
+
handleSessionResumption(chunk: Buffer, connectionId: string, hasSNI: boolean): {
|
|
33
|
+
shouldBlock: boolean;
|
|
34
|
+
reason?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Check for SNI mismatch during renegotiation
|
|
38
|
+
*/
|
|
39
|
+
checkRenegotiationSNI(chunk: Buffer, connInfo: IConnectionInfo, expectedDomain: string, connectionId: string): {
|
|
40
|
+
hasMismatch: boolean;
|
|
41
|
+
extractedSNI?: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Create a renegotiation handler function for a connection
|
|
45
|
+
*/
|
|
46
|
+
createRenegotiationHandler(connectionId: string, lockedDomain: string, connInfo: IConnectionInfo, onMismatch: (connectionId: string, reason: string) => void): (chunk: Buffer) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Analyze TLS connection for browser fingerprinting
|
|
49
|
+
* This helps identify browser vs non-browser connections
|
|
50
|
+
*/
|
|
51
|
+
analyzeClientHello(chunk: Buffer): {
|
|
52
|
+
isBrowserConnection: boolean;
|
|
53
|
+
isRenewal: boolean;
|
|
54
|
+
hasSNI: boolean;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { SniHandler } from './classes.pp.snihandler.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manages TLS-related operations including SNI extraction and validation
|
|
5
|
+
*/
|
|
6
|
+
export class TlsManager {
|
|
7
|
+
constructor(settings) {
|
|
8
|
+
this.settings = settings;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Check if a data chunk appears to be a TLS handshake
|
|
12
|
+
*/
|
|
13
|
+
isTlsHandshake(chunk) {
|
|
14
|
+
return SniHandler.isTlsHandshake(chunk);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if a data chunk appears to be a TLS ClientHello
|
|
18
|
+
*/
|
|
19
|
+
isClientHello(chunk) {
|
|
20
|
+
return SniHandler.isClientHello(chunk);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extract Server Name Indication (SNI) from TLS handshake
|
|
24
|
+
*/
|
|
25
|
+
extractSNI(chunk, connInfo, previousDomain) {
|
|
26
|
+
// Use the SniHandler to process the TLS packet
|
|
27
|
+
return SniHandler.processTlsPacket(chunk, connInfo, this.settings.enableTlsDebugLogging || false, previousDomain);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Handle session resumption attempts
|
|
31
|
+
*/
|
|
32
|
+
handleSessionResumption(chunk, connectionId, hasSNI) {
|
|
33
|
+
// Skip if session tickets are allowed
|
|
34
|
+
if (this.settings.allowSessionTicket !== false) {
|
|
35
|
+
return { shouldBlock: false };
|
|
36
|
+
}
|
|
37
|
+
// Check for session resumption attempt
|
|
38
|
+
const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging || false);
|
|
39
|
+
// If this is a resumption attempt without SNI, block it
|
|
40
|
+
if (resumptionInfo.isResumption && !hasSNI && !resumptionInfo.hasSNI) {
|
|
41
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
42
|
+
console.log(`[${connectionId}] Session resumption detected without SNI and allowSessionTicket=false. ` +
|
|
43
|
+
`Terminating connection to force new TLS handshake.`);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
shouldBlock: true,
|
|
47
|
+
reason: 'session_ticket_blocked'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { shouldBlock: false };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check for SNI mismatch during renegotiation
|
|
54
|
+
*/
|
|
55
|
+
checkRenegotiationSNI(chunk, connInfo, expectedDomain, connectionId) {
|
|
56
|
+
// Only process if this looks like a TLS ClientHello
|
|
57
|
+
if (!this.isClientHello(chunk)) {
|
|
58
|
+
return { hasMismatch: false };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
// Extract SNI with renegotiation support
|
|
62
|
+
const newSNI = SniHandler.extractSNIWithResumptionSupport(chunk, connInfo, this.settings.enableTlsDebugLogging || false);
|
|
63
|
+
// Skip if no SNI was found
|
|
64
|
+
if (!newSNI)
|
|
65
|
+
return { hasMismatch: false };
|
|
66
|
+
// Check for SNI mismatch
|
|
67
|
+
if (newSNI !== expectedDomain) {
|
|
68
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
69
|
+
console.log(`[${connectionId}] Renegotiation with different SNI: ${expectedDomain} -> ${newSNI}. ` +
|
|
70
|
+
`Terminating connection - SNI domain switching is not allowed.`);
|
|
71
|
+
}
|
|
72
|
+
return { hasMismatch: true, extractedSNI: newSNI };
|
|
73
|
+
}
|
|
74
|
+
else if (this.settings.enableTlsDebugLogging) {
|
|
75
|
+
console.log(`[${connectionId}] Renegotiation detected with same SNI: ${newSNI}. Allowing.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
console.log(`[${connectionId}] Error processing ClientHello: ${err}. Allowing connection to continue.`);
|
|
80
|
+
}
|
|
81
|
+
return { hasMismatch: false };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create a renegotiation handler function for a connection
|
|
85
|
+
*/
|
|
86
|
+
createRenegotiationHandler(connectionId, lockedDomain, connInfo, onMismatch) {
|
|
87
|
+
return (chunk) => {
|
|
88
|
+
const result = this.checkRenegotiationSNI(chunk, connInfo, lockedDomain, connectionId);
|
|
89
|
+
if (result.hasMismatch) {
|
|
90
|
+
onMismatch(connectionId, 'sni_mismatch');
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Analyze TLS connection for browser fingerprinting
|
|
96
|
+
* This helps identify browser vs non-browser connections
|
|
97
|
+
*/
|
|
98
|
+
analyzeClientHello(chunk) {
|
|
99
|
+
// Default result
|
|
100
|
+
const result = {
|
|
101
|
+
isBrowserConnection: false,
|
|
102
|
+
isRenewal: false,
|
|
103
|
+
hasSNI: false
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
// Check if it's a ClientHello
|
|
107
|
+
if (!this.isClientHello(chunk)) {
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
// Check for session resumption
|
|
111
|
+
const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging || false);
|
|
112
|
+
// Extract SNI
|
|
113
|
+
const sni = SniHandler.extractSNI(chunk, this.settings.enableTlsDebugLogging || false);
|
|
114
|
+
// Update result
|
|
115
|
+
result.isRenewal = resumptionInfo.isResumption;
|
|
116
|
+
result.hasSNI = !!sni;
|
|
117
|
+
// Browsers typically:
|
|
118
|
+
// 1. Send SNI extension
|
|
119
|
+
// 2. Have a variety of extensions (ALPN, etc.)
|
|
120
|
+
// 3. Use standard cipher suites
|
|
121
|
+
// ...more complex heuristics could be implemented here
|
|
122
|
+
// Simple heuristic: presence of SNI suggests browser
|
|
123
|
+
result.isBrowserConnection = !!sni;
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.log(`Error analyzing ClientHello: ${err}`);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wcC50bHNtYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5wcC50bHNtYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQVl4RDs7R0FFRztBQUNILE1BQU0sT0FBTyxVQUFVO0lBQ3JCLFlBQW9CLFFBQTRCO1FBQTVCLGFBQVEsR0FBUixRQUFRLENBQW9CO0lBQUcsQ0FBQztJQUVwRDs7T0FFRztJQUNJLGNBQWMsQ0FBQyxLQUFhO1FBQ2pDLE9BQU8sVUFBVSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxhQUFhLENBQUMsS0FBYTtRQUNoQyxPQUFPLFVBQVUsQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDekMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksVUFBVSxDQUNmLEtBQWEsRUFDYixRQUF5QixFQUN6QixjQUF1QjtRQUV2QiwrQ0FBK0M7UUFDL0MsT0FBTyxVQUFVLENBQUMsZ0JBQWdCLENBQ2hDLEtBQUssRUFDTCxRQUFRLEVBQ1IsSUFBSSxDQUFDLFFBQVEsQ0FBQyxxQkFBcUIsSUFBSSxLQUFLLEVBQzVDLGNBQWMsQ0FDZixDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksdUJBQXVCLENBQzVCLEtBQWEsRUFDYixZQUFvQixFQUNwQixNQUFlO1FBRWYsc0NBQXNDO1FBQ3RDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxrQkFBa0IsS0FBSyxLQUFLLEVBQUUsQ0FBQztZQUMvQyxPQUFPLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxDQUFDO1FBQ2hDLENBQUM7UUFFRCx1Q0FBdUM7UUFDdkMsTUFBTSxjQUFjLEdBQUcsVUFBVSxDQUFDLG9CQUFvQixDQUNwRCxLQUFLLEVBQ0wsSUFBSSxDQUFDLFFBQVEsQ0FBQyxxQkFBcUIsSUFBSSxLQUFLLENBQzdDLENBQUM7UUFFRix3REFBd0Q7UUFDeEQsSUFBSSxjQUFjLENBQUMsWUFBWSxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3JFLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO2dCQUN4QyxPQUFPLENBQUMsR0FBRyxDQUNULElBQUksWUFBWSwwRUFBMEU7b0JBQzFGLG9EQUFvRCxDQUNyRCxDQUFDO1lBQ0osQ0FBQztZQUNELE9BQU87Z0JBQ0wsV0FBVyxFQUFFLElBQUk7Z0JBQ2pCLE1BQU0sRUFBRSx3QkFBd0I7YUFDakMsQ0FBQztRQUNKLENBQUM7UUFFRCxPQUFPLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxDQUFDO0lBQ2hDLENBQUM7SUFFRDs7T0FFRztJQUNJLHFCQUFxQixDQUMxQixLQUFhLEVBQ2IsUUFBeUIsRUFDekIsY0FBc0IsRUFDdEIsWUFBb0I7UUFFcEIsb0RBQW9EO1FBQ3BELElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDL0IsT0FBTyxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsQ0FBQztRQUNoQyxDQUFDO1FBRUQsSUFBSSxDQUFDO1lBQ0gseUNBQXlDO1lBQ3pDLE1BQU0sTUFBTSxHQUFHLFVBQVUsQ0FBQywrQkFBK0IsQ0FDdkQsS0FBSyxFQUNMLFFBQVEsRUFDUixJQUFJLENBQUMsUUFBUSxDQUFDLHFCQUFxQixJQUFJLEtBQUssQ0FDN0MsQ0FBQztZQUVGLDJCQUEyQjtZQUMzQixJQUFJLENBQUMsTUFBTTtnQkFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxDQUFDO1lBRTNDLHlCQUF5QjtZQUN6QixJQUFJLE1BQU0sS0FBSyxjQUFjLEVBQUUsQ0FBQztnQkFDOUIsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLHFCQUFxQixFQUFFLENBQUM7b0JBQ3hDLE9BQU8sQ0FBQyxHQUFHLENBQ1QsSUFBSSxZQUFZLHVDQUF1QyxjQUFjLE9BQU8sTUFBTSxJQUFJO3dCQUN0RiwrREFBK0QsQ0FDaEUsQ0FBQztnQkFDSixDQUFDO2dCQUNELE9BQU8sRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQUUsQ0FBQztZQUNyRCxDQUFDO2lCQUFNLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO2dCQUMvQyxPQUFPLENBQUMsR0FBRyxDQUNULElBQUksWUFBWSwyQ0FBMkMsTUFBTSxhQUFhLENBQy9FLENBQUM7WUFDSixDQUFDO1FBQ0gsQ0FBQztRQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7WUFDYixPQUFPLENBQUMsR0FBRyxDQUNULElBQUksWUFBWSxtQ0FBbUMsR0FBRyxvQ0FBb0MsQ0FDM0YsQ0FBQztRQUNKLENBQUM7UUFFRCxPQUFPLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxDQUFDO0lBQ2hDLENBQUM7SUFFRDs7T0FFRztJQUNJLDBCQUEwQixDQUMvQixZQUFvQixFQUNwQixZQUFvQixFQUNwQixRQUF5QixFQUN6QixVQUEwRDtRQUUxRCxPQUFPLENBQUMsS0FBYSxFQUFFLEVBQUU7WUFDdkIsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLHFCQUFxQixDQUFDLEtBQUssRUFBRSxRQUFRLEVBQUUsWUFBWSxFQUFFLFlBQVksQ0FBQyxDQUFDO1lBQ3ZGLElBQUksTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUN2QixVQUFVLENBQUMsWUFBWSxFQUFFLGNBQWMsQ0FBQyxDQUFDO1lBQzNDLENBQUM7UUFDSCxDQUFDLENBQUM7SUFDSixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksa0JBQWtCLENBQUMsS0FBYTtRQUtyQyxpQkFBaUI7UUFDakIsTUFBTSxNQUFNLEdBQUc7WUFDYixtQkFBbUIsRUFBRSxLQUFLO1lBQzFCLFNBQVMsRUFBRSxLQUFLO1lBQ2hCLE1BQU0sRUFBRSxLQUFLO1NBQ2QsQ0FBQztRQUVGLElBQUksQ0FBQztZQUNILDhCQUE4QjtZQUM5QixJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO2dCQUMvQixPQUFPLE1BQU0sQ0FBQztZQUNoQixDQUFDO1lBRUQsK0JBQStCO1lBQy9CLE1BQU0sY0FBYyxHQUFHLFVBQVUsQ0FBQyxvQkFBb0IsQ0FDcEQsS0FBSyxFQUNMLElBQUksQ0FBQyxRQUFRLENBQUMscUJBQXFCLElBQUksS0FBSyxDQUM3QyxDQUFDO1lBRUYsY0FBYztZQUNkLE1BQU0sR0FBRyxHQUFHLFVBQVUsQ0FBQyxVQUFVLENBQy9CLEtBQUssRUFDTCxJQUFJLENBQUMsUUFBUSxDQUFDLHFCQUFxQixJQUFJLEtBQUssQ0FDN0MsQ0FBQztZQUVGLGdCQUFnQjtZQUNoQixNQUFNLENBQUMsU0FBUyxHQUFHLGNBQWMsQ0FBQyxZQUFZLENBQUM7WUFDL0MsTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDO1lBRXRCLHNCQUFzQjtZQUN0Qix3QkFBd0I7WUFDeEIsK0NBQStDO1lBQy9DLGdDQUFnQztZQUNoQyx1REFBdUQ7WUFFdkQscURBQXFEO1lBQ3JELE1BQU0sQ0FBQyxtQkFBbUIsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDO1lBRW5DLE9BQU8sTUFBTSxDQUFDO1FBQ2hCLENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2IsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQ0FBZ0MsR0FBRyxFQUFFLENBQUMsQ0FBQztZQUNuRCxPQUFPLE1BQU0sQ0FBQztRQUNoQixDQUFDO0lBQ0gsQ0FBQztDQUNGIn0=
|
package/dist_ts/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './classes.iptablesproxy.js';
|
|
2
2
|
export * from './classes.networkproxy.js';
|
|
3
|
-
export * from './classes.portproxy.js';
|
|
3
|
+
export * from './classes.pp.portproxy.js';
|
|
4
4
|
export * from './classes.port80handler.js';
|
|
5
5
|
export * from './classes.sslredirect.js';
|
|
6
|
-
export * from './classes.snihandler.js';
|
|
6
|
+
export * from './classes.pp.snihandler.js';
|
package/dist_ts/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from './classes.iptablesproxy.js';
|
|
2
2
|
export * from './classes.networkproxy.js';
|
|
3
|
-
export * from './classes.portproxy.js';
|
|
3
|
+
export * from './classes.pp.portproxy.js';
|
|
4
4
|
export * from './classes.port80handler.js';
|
|
5
5
|
export * from './classes.sslredirect.js';
|
|
6
|
-
export * from './classes.snihandler.js';
|
|
7
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
6
|
+
export * from './classes.pp.snihandler.js';
|
|
7
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyw0QkFBNEIsQ0FBQyJ9
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '
|
|
6
|
+
version: '4.1.0',
|
|
7
7
|
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
|
|
8
8
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { IPortProxySettings } from './classes.pp.interfaces.js';
|
|
2
|
+
import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Manages ACME certificate operations
|
|
6
|
+
*/
|
|
7
|
+
export class AcmeManager {
|
|
8
|
+
constructor(
|
|
9
|
+
private settings: IPortProxySettings,
|
|
10
|
+
private networkProxyBridge: NetworkProxyBridge
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get current ACME settings
|
|
15
|
+
*/
|
|
16
|
+
public getAcmeSettings(): IPortProxySettings['acme'] {
|
|
17
|
+
return this.settings.acme;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if ACME is enabled
|
|
22
|
+
*/
|
|
23
|
+
public isAcmeEnabled(): boolean {
|
|
24
|
+
return !!this.settings.acme?.enabled;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Update ACME certificate settings
|
|
29
|
+
*/
|
|
30
|
+
public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise<void> {
|
|
31
|
+
console.log('Updating ACME certificate settings');
|
|
32
|
+
|
|
33
|
+
// Check if enabled state is changing
|
|
34
|
+
const enabledChanging = this.settings.acme?.enabled !== acmeSettings.enabled;
|
|
35
|
+
|
|
36
|
+
// Update settings
|
|
37
|
+
this.settings.acme = {
|
|
38
|
+
...this.settings.acme,
|
|
39
|
+
...acmeSettings,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Get NetworkProxy instance
|
|
43
|
+
const networkProxy = this.networkProxyBridge.getNetworkProxy();
|
|
44
|
+
|
|
45
|
+
if (!networkProxy) {
|
|
46
|
+
console.log('Cannot update ACME settings - NetworkProxy not initialized');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// If enabled state changed, we need to restart NetworkProxy
|
|
52
|
+
if (enabledChanging) {
|
|
53
|
+
console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`);
|
|
54
|
+
|
|
55
|
+
// Stop the current NetworkProxy
|
|
56
|
+
await this.networkProxyBridge.stop();
|
|
57
|
+
|
|
58
|
+
// Reinitialize with new settings
|
|
59
|
+
await this.networkProxyBridge.initialize();
|
|
60
|
+
|
|
61
|
+
// Start NetworkProxy with new settings
|
|
62
|
+
await this.networkProxyBridge.start();
|
|
63
|
+
} else {
|
|
64
|
+
// Just update the settings in the existing NetworkProxy
|
|
65
|
+
console.log('Updating ACME settings in NetworkProxy without restart');
|
|
66
|
+
|
|
67
|
+
// Update settings in NetworkProxy
|
|
68
|
+
if (networkProxy.options && networkProxy.options.acme) {
|
|
69
|
+
networkProxy.options.acme = { ...this.settings.acme };
|
|
70
|
+
|
|
71
|
+
// For certificate renewals, we might want to trigger checks with the new settings
|
|
72
|
+
if (acmeSettings.renewThresholdDays !== undefined) {
|
|
73
|
+
console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`);
|
|
74
|
+
networkProxy.options.acme.renewThresholdDays = acmeSettings.renewThresholdDays;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Update other settings that might affect certificate operations
|
|
78
|
+
if (acmeSettings.useProduction !== undefined) {
|
|
79
|
+
console.log(`Setting ACME to ${acmeSettings.useProduction ? 'production' : 'staging'} mode`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (acmeSettings.autoRenew !== undefined) {
|
|
83
|
+
console.log(`Setting auto-renewal to ${acmeSettings.autoRenew ? 'enabled' : 'disabled'}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.log(`Error updating ACME settings: ${err}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Request a certificate for a specific domain
|
|
94
|
+
*/
|
|
95
|
+
public async requestCertificate(domain: string): Promise<boolean> {
|
|
96
|
+
// Validate domain format
|
|
97
|
+
if (!this.isValidDomain(domain)) {
|
|
98
|
+
console.log(`Invalid domain format: ${domain}`);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Delegate to NetworkProxyManager
|
|
103
|
+
return this.networkProxyBridge.requestCertificate(domain);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Basic domain validation
|
|
108
|
+
*/
|
|
109
|
+
private isValidDomain(domain: string): boolean {
|
|
110
|
+
// Very basic domain validation
|
|
111
|
+
if (!domain || domain.length === 0) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check for wildcard domains (they can't get ACME certs)
|
|
116
|
+
if (domain.includes('*')) {
|
|
117
|
+
console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if domain has at least one dot and no invalid characters
|
|
122
|
+
const validDomainRegex = /^[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])?)*$/;
|
|
123
|
+
if (!validDomainRegex.test(domain)) {
|
|
124
|
+
console.log(`Domain "${domain}" has invalid format`);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get eligible domains for ACME certificates
|
|
133
|
+
*/
|
|
134
|
+
public getEligibleDomains(): string[] {
|
|
135
|
+
// Collect all eligible domains from domain configs
|
|
136
|
+
const domains: string[] = [];
|
|
137
|
+
|
|
138
|
+
for (const config of this.settings.domainConfigs) {
|
|
139
|
+
// Skip domains that can't be used with ACME
|
|
140
|
+
const eligibleDomains = config.domains.filter(domain =>
|
|
141
|
+
!domain.includes('*') && this.isValidDomain(domain)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
domains.push(...eligibleDomains);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return domains;
|
|
148
|
+
}
|
|
149
|
+
}
|