@push.rocks/smartproxy 18.0.2 → 18.2.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.
Files changed (53) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/certificate/certificate-manager.d.ts +150 -0
  3. package/dist_ts/certificate/certificate-manager.js +505 -0
  4. package/dist_ts/certificate/events/simplified-events.d.ts +56 -0
  5. package/dist_ts/certificate/events/simplified-events.js +13 -0
  6. package/dist_ts/certificate/models/certificate-errors.d.ts +69 -0
  7. package/dist_ts/certificate/models/certificate-errors.js +141 -0
  8. package/dist_ts/certificate/models/certificate-strategy.d.ts +60 -0
  9. package/dist_ts/certificate/models/certificate-strategy.js +73 -0
  10. package/dist_ts/certificate/simplified-certificate-manager.d.ts +150 -0
  11. package/dist_ts/certificate/simplified-certificate-manager.js +501 -0
  12. package/dist_ts/http/index.d.ts +1 -9
  13. package/dist_ts/http/index.js +5 -11
  14. package/dist_ts/plugins.d.ts +3 -1
  15. package/dist_ts/plugins.js +4 -2
  16. package/dist_ts/proxies/network-proxy/network-proxy.js +3 -1
  17. package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.d.ts +48 -0
  18. package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.js +76 -0
  19. package/dist_ts/proxies/network-proxy/websocket-handler.js +41 -4
  20. package/dist_ts/proxies/smart-proxy/cert-store.d.ts +10 -0
  21. package/dist_ts/proxies/smart-proxy/cert-store.js +70 -0
  22. package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +116 -0
  23. package/dist_ts/proxies/smart-proxy/certificate-manager.js +401 -0
  24. package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.d.ts +168 -0
  25. package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.js +642 -0
  26. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +26 -0
  27. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  28. package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.d.ts +65 -0
  29. package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.js +31 -0
  30. package/dist_ts/proxies/smart-proxy/models/smartproxy-options.d.ts +102 -0
  31. package/dist_ts/proxies/smart-proxy/models/smartproxy-options.js +73 -0
  32. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +10 -44
  33. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +66 -202
  34. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -0
  35. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +62 -2
  36. package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.d.ts +41 -0
  37. package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.js +132 -0
  38. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +18 -13
  39. package/dist_ts/proxies/smart-proxy/smart-proxy.js +79 -196
  40. package/package.json +7 -5
  41. package/readme.md +224 -10
  42. package/readme.plan.md +1405 -617
  43. package/ts/00_commitinfo_data.ts +1 -1
  44. package/ts/http/index.ts +5 -12
  45. package/ts/plugins.ts +4 -1
  46. package/ts/proxies/network-proxy/network-proxy.ts +3 -0
  47. package/ts/proxies/network-proxy/websocket-handler.ts +38 -3
  48. package/ts/proxies/smart-proxy/cert-store.ts +86 -0
  49. package/ts/proxies/smart-proxy/certificate-manager.ts +506 -0
  50. package/ts/proxies/smart-proxy/models/route-types.ts +33 -3
  51. package/ts/proxies/smart-proxy/network-proxy-bridge.ts +86 -239
  52. package/ts/proxies/smart-proxy/route-connection-handler.ts +74 -1
  53. package/ts/proxies/smart-proxy/smart-proxy.ts +105 -222
@@ -0,0 +1,642 @@
1
+ import * as plugins from '../../plugins.js';
2
+ // Importing required components
3
+ import { ConnectionManager } from './connection-manager.js';
4
+ import { SecurityManager } from './security-manager.js';
5
+ import { TlsManager } from './tls-manager.js';
6
+ import { NetworkProxyBridge } from './network-proxy-bridge.js';
7
+ import { TimeoutManager } from './timeout-manager.js';
8
+ import { PortManager } from './port-manager.js';
9
+ import { RouteManager } from './route-manager.js';
10
+ import { RouteConnectionHandler } from './route-connection-handler.js';
11
+ import { NFTablesManager } from './nftables-manager.js';
12
+ // External dependencies
13
+ import { Port80Handler } from '../../http/port80/port80-handler.js';
14
+ import { CertProvisioner } from '../../certificate/providers/cert-provisioner.js';
15
+ import { buildPort80Handler } from '../../certificate/acme/acme-factory.js';
16
+ import { createPort80HandlerOptions } from '../../common/port80-adapter.js';
17
+ /**
18
+ * SmartProxy - Pure route-based API
19
+ *
20
+ * SmartProxy is a unified proxy system that works with routes to define connection handling behavior.
21
+ * Each route contains matching criteria (ports, domains, etc.) and an action to take (forward, redirect, block).
22
+ *
23
+ * Configuration is provided through a set of routes, with each route defining:
24
+ * - What to match (ports, domains, paths, client IPs)
25
+ * - What to do with matching traffic (forward, redirect, block)
26
+ * - How to handle TLS (passthrough, terminate, terminate-and-reencrypt)
27
+ * - Security settings (IP restrictions, connection limits)
28
+ * - Advanced options (timeout, headers, etc.)
29
+ */
30
+ export class SmartProxy extends plugins.EventEmitter {
31
+ /**
32
+ * Constructor for SmartProxy
33
+ *
34
+ * @param settingsArg Configuration options containing routes and other settings
35
+ * Routes define how traffic is matched and handled, with each route having:
36
+ * - match: criteria for matching traffic (ports, domains, paths, IPs)
37
+ * - action: what to do with matched traffic (forward, redirect, block)
38
+ *
39
+ * Example:
40
+ * ```ts
41
+ * const proxy = new SmartProxy({
42
+ * routes: [
43
+ * {
44
+ * match: {
45
+ * ports: 443,
46
+ * domains: ['example.com', '*.example.com']
47
+ * },
48
+ * action: {
49
+ * type: 'forward',
50
+ * target: { host: '10.0.0.1', port: 8443 },
51
+ * tls: { mode: 'passthrough' }
52
+ * }
53
+ * }
54
+ * ],
55
+ * defaults: {
56
+ * target: { host: 'localhost', port: 8080 },
57
+ * security: { ipAllowList: ['*'] }
58
+ * }
59
+ * });
60
+ * ```
61
+ */
62
+ constructor(settingsArg) {
63
+ super();
64
+ this.connectionLogger = null;
65
+ this.isShuttingDown = false;
66
+ // Port80Handler for ACME certificate management
67
+ this.port80Handler = null;
68
+ // Set reasonable defaults for all settings
69
+ this.settings = {
70
+ ...settingsArg,
71
+ initialDataTimeout: settingsArg.initialDataTimeout || 120000,
72
+ socketTimeout: settingsArg.socketTimeout || 3600000,
73
+ inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000,
74
+ maxConnectionLifetime: settingsArg.maxConnectionLifetime || 86400000,
75
+ inactivityTimeout: settingsArg.inactivityTimeout || 14400000,
76
+ gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000,
77
+ noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
78
+ keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
79
+ keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000,
80
+ maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024,
81
+ disableInactivityCheck: settingsArg.disableInactivityCheck || false,
82
+ enableKeepAliveProbes: settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
83
+ enableDetailedLogging: settingsArg.enableDetailedLogging || false,
84
+ enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
85
+ enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
86
+ allowSessionTicket: settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
87
+ maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
88
+ connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
89
+ keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
90
+ keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
91
+ extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
92
+ networkProxyPort: settingsArg.networkProxyPort || 8443,
93
+ };
94
+ // Set default ACME options if not provided
95
+ this.settings.acme = this.settings.acme || {};
96
+ if (Object.keys(this.settings.acme).length === 0) {
97
+ this.settings.acme = {
98
+ enabled: false,
99
+ port: 80,
100
+ accountEmail: 'admin@example.com',
101
+ useProduction: false,
102
+ renewThresholdDays: 30,
103
+ autoRenew: true,
104
+ certificateStore: './certs',
105
+ skipConfiguredCerts: false,
106
+ httpsRedirectPort: 443,
107
+ renewCheckIntervalHours: 24,
108
+ routeForwards: []
109
+ };
110
+ }
111
+ // Initialize component managers
112
+ this.timeoutManager = new TimeoutManager(this.settings);
113
+ this.securityManager = new SecurityManager(this.settings);
114
+ this.connectionManager = new ConnectionManager(this.settings, this.securityManager, this.timeoutManager);
115
+ // Create the route manager
116
+ this.routeManager = new RouteManager(this.settings);
117
+ // Create other required components
118
+ this.tlsManager = new TlsManager(this.settings);
119
+ this.networkProxyBridge = new NetworkProxyBridge(this.settings);
120
+ // Initialize connection handler with route support
121
+ this.routeConnectionHandler = new RouteConnectionHandler(this.settings, this.connectionManager, this.securityManager, this.tlsManager, this.networkProxyBridge, this.timeoutManager, this.routeManager);
122
+ // Initialize port manager
123
+ this.portManager = new PortManager(this.settings, this.routeConnectionHandler);
124
+ // Initialize NFTablesManager
125
+ this.nftablesManager = new NFTablesManager(this.settings);
126
+ }
127
+ /**
128
+ * Initialize the Port80Handler for ACME certificate management
129
+ */
130
+ async initializePort80Handler() {
131
+ const config = this.settings.acme;
132
+ if (!config.enabled) {
133
+ console.log('ACME is disabled in configuration');
134
+ return;
135
+ }
136
+ try {
137
+ // Build and start the Port80Handler
138
+ this.port80Handler = buildPort80Handler({
139
+ ...config,
140
+ httpsRedirectPort: config.httpsRedirectPort || 443
141
+ });
142
+ // Share Port80Handler with NetworkProxyBridge before start
143
+ this.networkProxyBridge.setPort80Handler(this.port80Handler);
144
+ await this.port80Handler.start();
145
+ console.log(`Port80Handler started on port ${config.port}`);
146
+ }
147
+ catch (err) {
148
+ console.log(`Error initializing Port80Handler: ${err}`);
149
+ }
150
+ }
151
+ /**
152
+ * Start the proxy server with support for both configuration types
153
+ */
154
+ async start() {
155
+ // Don't start if already shutting down
156
+ if (this.isShuttingDown) {
157
+ console.log("Cannot start SmartProxy while it's shutting down");
158
+ return;
159
+ }
160
+ // Pure route-based configuration - no domain configs needed
161
+ // Initialize Port80Handler if enabled
162
+ await this.initializePort80Handler();
163
+ // Initialize CertProvisioner for unified certificate workflows
164
+ if (this.port80Handler) {
165
+ const acme = this.settings.acme;
166
+ // Setup route forwards
167
+ const routeForwards = acme.routeForwards?.map(f => f) || [];
168
+ // Create CertProvisioner with appropriate parameters
169
+ // No longer need to support multiple configuration types
170
+ // Just pass the routes directly
171
+ this.certProvisioner = new CertProvisioner(this.settings.routes, this.port80Handler, this.networkProxyBridge, this.settings.certProvisionFunction, acme.renewThresholdDays, acme.renewCheckIntervalHours, acme.autoRenew, routeForwards);
172
+ // Register certificate event handler
173
+ this.certProvisioner.on('certificate', (certData) => {
174
+ this.emit('certificate', {
175
+ domain: certData.domain,
176
+ publicKey: certData.certificate,
177
+ privateKey: certData.privateKey,
178
+ expiryDate: certData.expiryDate,
179
+ source: certData.source,
180
+ isRenewal: certData.isRenewal
181
+ });
182
+ });
183
+ await this.certProvisioner.start();
184
+ console.log('CertProvisioner started');
185
+ }
186
+ // Initialize and start NetworkProxy if needed
187
+ if (this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) {
188
+ await this.networkProxyBridge.initialize();
189
+ await this.networkProxyBridge.start();
190
+ }
191
+ // Validate the route configuration
192
+ const configWarnings = this.routeManager.validateConfiguration();
193
+ if (configWarnings.length > 0) {
194
+ console.log("Route configuration warnings:");
195
+ for (const warning of configWarnings) {
196
+ console.log(` - ${warning}`);
197
+ }
198
+ }
199
+ // Get listening ports from RouteManager
200
+ const listeningPorts = this.routeManager.getListeningPorts();
201
+ // Provision NFTables rules for routes that use NFTables
202
+ for (const route of this.settings.routes) {
203
+ if (route.action.forwardingEngine === 'nftables') {
204
+ await this.nftablesManager.provisionRoute(route);
205
+ }
206
+ }
207
+ // Start port listeners using the PortManager
208
+ await this.portManager.addPorts(listeningPorts);
209
+ // Set up periodic connection logging and inactivity checks
210
+ this.connectionLogger = setInterval(() => {
211
+ // Immediately return if shutting down
212
+ if (this.isShuttingDown)
213
+ return;
214
+ // Perform inactivity check
215
+ this.connectionManager.performInactivityCheck();
216
+ // Log connection statistics
217
+ const now = Date.now();
218
+ let maxIncoming = 0;
219
+ let maxOutgoing = 0;
220
+ let tlsConnections = 0;
221
+ let nonTlsConnections = 0;
222
+ let completedTlsHandshakes = 0;
223
+ let pendingTlsHandshakes = 0;
224
+ let keepAliveConnections = 0;
225
+ let networkProxyConnections = 0;
226
+ // Get connection records for analysis
227
+ const connectionRecords = this.connectionManager.getConnections();
228
+ // Analyze active connections
229
+ for (const record of connectionRecords.values()) {
230
+ // Track connection stats
231
+ if (record.isTLS) {
232
+ tlsConnections++;
233
+ if (record.tlsHandshakeComplete) {
234
+ completedTlsHandshakes++;
235
+ }
236
+ else {
237
+ pendingTlsHandshakes++;
238
+ }
239
+ }
240
+ else {
241
+ nonTlsConnections++;
242
+ }
243
+ if (record.hasKeepAlive) {
244
+ keepAliveConnections++;
245
+ }
246
+ if (record.usingNetworkProxy) {
247
+ networkProxyConnections++;
248
+ }
249
+ maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
250
+ if (record.outgoingStartTime) {
251
+ maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
252
+ }
253
+ }
254
+ // Get termination stats
255
+ const terminationStats = this.connectionManager.getTerminationStats();
256
+ // Log detailed stats
257
+ console.log(`Active connections: ${connectionRecords.size}. ` +
258
+ `Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
259
+ `Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, NetworkProxy=${networkProxyConnections}. ` +
260
+ `Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(maxOutgoing)}. ` +
261
+ `Termination stats: ${JSON.stringify({
262
+ IN: terminationStats.incoming,
263
+ OUT: terminationStats.outgoing,
264
+ })}`);
265
+ }, this.settings.inactivityCheckInterval || 60000);
266
+ // Make sure the interval doesn't keep the process alive
267
+ if (this.connectionLogger.unref) {
268
+ this.connectionLogger.unref();
269
+ }
270
+ }
271
+ /**
272
+ * Extract domain configurations from routes for certificate provisioning
273
+ *
274
+ * Note: This method has been removed as we now work directly with routes
275
+ */
276
+ /**
277
+ * Stop the proxy server
278
+ */
279
+ async stop() {
280
+ console.log('SmartProxy shutting down...');
281
+ this.isShuttingDown = true;
282
+ this.portManager.setShuttingDown(true);
283
+ // Stop CertProvisioner if active
284
+ if (this.certProvisioner) {
285
+ await this.certProvisioner.stop();
286
+ console.log('CertProvisioner stopped');
287
+ }
288
+ // Stop NFTablesManager
289
+ await this.nftablesManager.stop();
290
+ console.log('NFTablesManager stopped');
291
+ // Stop the Port80Handler if running
292
+ if (this.port80Handler) {
293
+ try {
294
+ await this.port80Handler.stop();
295
+ console.log('Port80Handler stopped');
296
+ this.port80Handler = null;
297
+ }
298
+ catch (err) {
299
+ console.log(`Error stopping Port80Handler: ${err}`);
300
+ }
301
+ }
302
+ // Stop the connection logger
303
+ if (this.connectionLogger) {
304
+ clearInterval(this.connectionLogger);
305
+ this.connectionLogger = null;
306
+ }
307
+ // Stop all port listeners
308
+ await this.portManager.closeAll();
309
+ console.log('All servers closed. Cleaning up active connections...');
310
+ // Clean up all active connections
311
+ this.connectionManager.clearConnections();
312
+ // Stop NetworkProxy
313
+ await this.networkProxyBridge.stop();
314
+ console.log('SmartProxy shutdown complete.');
315
+ }
316
+ /**
317
+ * Updates the domain configurations for the proxy
318
+ *
319
+ * Note: This legacy method has been removed. Use updateRoutes instead.
320
+ */
321
+ async updateDomainConfigs() {
322
+ console.warn('Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.');
323
+ throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead');
324
+ }
325
+ /**
326
+ * Update routes with new configuration
327
+ *
328
+ * This method replaces the current route configuration with the provided routes.
329
+ * It also provisions certificates for routes that require TLS termination and have
330
+ * `certificate: 'auto'` set in their TLS configuration.
331
+ *
332
+ * @param newRoutes Array of route configurations to use
333
+ *
334
+ * Example:
335
+ * ```ts
336
+ * proxy.updateRoutes([
337
+ * {
338
+ * match: { ports: 443, domains: 'secure.example.com' },
339
+ * action: {
340
+ * type: 'forward',
341
+ * target: { host: '10.0.0.1', port: 8443 },
342
+ * tls: { mode: 'terminate', certificate: 'auto' }
343
+ * }
344
+ * }
345
+ * ]);
346
+ * ```
347
+ */
348
+ async updateRoutes(newRoutes) {
349
+ console.log(`Updating routes (${newRoutes.length} routes)`);
350
+ // Get existing routes that use NFTables
351
+ const oldNfTablesRoutes = this.settings.routes.filter(r => r.action.forwardingEngine === 'nftables');
352
+ // Get new routes that use NFTables
353
+ const newNfTablesRoutes = newRoutes.filter(r => r.action.forwardingEngine === 'nftables');
354
+ // Find routes to remove, update, or add
355
+ for (const oldRoute of oldNfTablesRoutes) {
356
+ const newRoute = newNfTablesRoutes.find(r => r.name === oldRoute.name);
357
+ if (!newRoute) {
358
+ // Route was removed
359
+ await this.nftablesManager.deprovisionRoute(oldRoute);
360
+ }
361
+ else {
362
+ // Route was updated
363
+ await this.nftablesManager.updateRoute(oldRoute, newRoute);
364
+ }
365
+ }
366
+ // Find new routes to add
367
+ for (const newRoute of newNfTablesRoutes) {
368
+ const oldRoute = oldNfTablesRoutes.find(r => r.name === newRoute.name);
369
+ if (!oldRoute) {
370
+ // New route
371
+ await this.nftablesManager.provisionRoute(newRoute);
372
+ }
373
+ }
374
+ // Update routes in RouteManager
375
+ this.routeManager.updateRoutes(newRoutes);
376
+ // Get the new set of required ports
377
+ const requiredPorts = this.routeManager.getListeningPorts();
378
+ // Update port listeners to match the new configuration
379
+ await this.portManager.updatePorts(requiredPorts);
380
+ // Update settings with the new routes
381
+ this.settings.routes = newRoutes;
382
+ // If NetworkProxy is initialized, resync the configurations
383
+ if (this.networkProxyBridge.getNetworkProxy()) {
384
+ await this.networkProxyBridge.syncRoutesToNetworkProxy(newRoutes);
385
+ }
386
+ // If Port80Handler is running, provision certificates based on routes
387
+ if (this.port80Handler && this.settings.acme?.enabled) {
388
+ // Register all eligible domains from routes
389
+ this.port80Handler.addDomainsFromRoutes(newRoutes);
390
+ // Handle static certificates from certProvisionFunction if available
391
+ if (this.settings.certProvisionFunction) {
392
+ for (const route of newRoutes) {
393
+ // Skip routes without domains
394
+ if (!route.match.domains)
395
+ continue;
396
+ // Skip non-forward routes
397
+ if (route.action.type !== 'forward')
398
+ continue;
399
+ // Skip routes without TLS termination
400
+ if (!route.action.tls ||
401
+ route.action.tls.mode === 'passthrough' ||
402
+ !route.action.target)
403
+ continue;
404
+ // Skip certificate provisioning if certificate is not auto
405
+ if (route.action.tls.certificate !== 'auto')
406
+ continue;
407
+ const domains = Array.isArray(route.match.domains)
408
+ ? route.match.domains
409
+ : [route.match.domains];
410
+ for (const domain of domains) {
411
+ try {
412
+ const provision = await this.settings.certProvisionFunction(domain);
413
+ // Skip http01 as those are handled by Port80Handler
414
+ if (provision !== 'http01') {
415
+ // Handle static certificate (e.g., DNS-01 provisioned)
416
+ const certObj = provision;
417
+ const certData = {
418
+ domain: certObj.domainName,
419
+ certificate: certObj.publicKey,
420
+ privateKey: certObj.privateKey,
421
+ expiryDate: new Date(certObj.validUntil),
422
+ routeReference: {
423
+ routeName: route.name
424
+ }
425
+ };
426
+ this.networkProxyBridge.applyExternalCertificate(certData);
427
+ console.log(`Applied static certificate for ${domain} from certProvider`);
428
+ }
429
+ }
430
+ catch (err) {
431
+ console.log(`certProvider error for ${domain}: ${err}`);
432
+ }
433
+ }
434
+ }
435
+ }
436
+ console.log('Provisioned certificates for new routes');
437
+ }
438
+ }
439
+ /**
440
+ * Request a certificate for a specific domain
441
+ *
442
+ * @param domain The domain to request a certificate for
443
+ * @param routeName Optional route name to associate with the certificate
444
+ */
445
+ async requestCertificate(domain, routeName) {
446
+ // Validate domain format
447
+ if (!this.isValidDomain(domain)) {
448
+ console.log(`Invalid domain format: ${domain}`);
449
+ return false;
450
+ }
451
+ // Use Port80Handler if available
452
+ if (this.port80Handler) {
453
+ try {
454
+ // Check if we already have a certificate
455
+ const cert = this.port80Handler.getCertificate(domain);
456
+ if (cert) {
457
+ console.log(`Certificate already exists for ${domain}, valid until ${cert.expiryDate.toISOString()}`);
458
+ return true;
459
+ }
460
+ // Register domain for certificate issuance
461
+ this.port80Handler.addDomain({
462
+ domain,
463
+ sslRedirect: true,
464
+ acmeMaintenance: true,
465
+ routeReference: routeName ? { routeName } : undefined
466
+ });
467
+ console.log(`Domain ${domain} registered for certificate issuance` + (routeName ? ` for route '${routeName}'` : ''));
468
+ return true;
469
+ }
470
+ catch (err) {
471
+ console.log(`Error registering domain with Port80Handler: ${err}`);
472
+ return false;
473
+ }
474
+ }
475
+ // Fall back to NetworkProxyBridge
476
+ return this.networkProxyBridge.requestCertificate(domain);
477
+ }
478
+ /**
479
+ * Validates if a domain name is valid for certificate issuance
480
+ */
481
+ isValidDomain(domain) {
482
+ // Very basic domain validation
483
+ if (!domain || domain.length === 0) {
484
+ return false;
485
+ }
486
+ // Check for wildcard domains (they can't get ACME certs)
487
+ if (domain.includes('*')) {
488
+ console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`);
489
+ return false;
490
+ }
491
+ // Check if domain has at least one dot and no invalid characters
492
+ 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])?)*$/;
493
+ if (!validDomainRegex.test(domain)) {
494
+ console.log(`Domain "${domain}" has invalid format`);
495
+ return false;
496
+ }
497
+ return true;
498
+ }
499
+ /**
500
+ * Add a new listening port without changing the route configuration
501
+ *
502
+ * This allows you to add a port listener without updating routes.
503
+ * Useful for preparing to listen on a port before adding routes for it.
504
+ *
505
+ * @param port The port to start listening on
506
+ * @returns Promise that resolves when the port is listening
507
+ */
508
+ async addListeningPort(port) {
509
+ return this.portManager.addPort(port);
510
+ }
511
+ /**
512
+ * Stop listening on a specific port without changing the route configuration
513
+ *
514
+ * This allows you to stop a port listener without updating routes.
515
+ * Useful for temporary maintenance or port changes.
516
+ *
517
+ * @param port The port to stop listening on
518
+ * @returns Promise that resolves when the port is closed
519
+ */
520
+ async removeListeningPort(port) {
521
+ return this.portManager.removePort(port);
522
+ }
523
+ /**
524
+ * Get a list of all ports currently being listened on
525
+ *
526
+ * @returns Array of port numbers
527
+ */
528
+ getListeningPorts() {
529
+ return this.portManager.getListeningPorts();
530
+ }
531
+ /**
532
+ * Get statistics about current connections
533
+ */
534
+ getStatistics() {
535
+ const connectionRecords = this.connectionManager.getConnections();
536
+ const terminationStats = this.connectionManager.getTerminationStats();
537
+ let tlsConnections = 0;
538
+ let nonTlsConnections = 0;
539
+ let keepAliveConnections = 0;
540
+ let networkProxyConnections = 0;
541
+ // Analyze active connections
542
+ for (const record of connectionRecords.values()) {
543
+ if (record.isTLS)
544
+ tlsConnections++;
545
+ else
546
+ nonTlsConnections++;
547
+ if (record.hasKeepAlive)
548
+ keepAliveConnections++;
549
+ if (record.usingNetworkProxy)
550
+ networkProxyConnections++;
551
+ }
552
+ return {
553
+ activeConnections: connectionRecords.size,
554
+ tlsConnections,
555
+ nonTlsConnections,
556
+ keepAliveConnections,
557
+ networkProxyConnections,
558
+ terminationStats,
559
+ acmeEnabled: !!this.port80Handler,
560
+ port80HandlerPort: this.port80Handler ? this.settings.acme?.port : null,
561
+ routes: this.routeManager.getListeningPorts().length,
562
+ listeningPorts: this.portManager.getListeningPorts(),
563
+ activePorts: this.portManager.getListeningPorts().length
564
+ };
565
+ }
566
+ /**
567
+ * Get a list of eligible domains for ACME certificates
568
+ */
569
+ getEligibleDomainsForCertificates() {
570
+ const domains = [];
571
+ // Get domains from routes
572
+ const routes = this.settings.routes || [];
573
+ for (const route of routes) {
574
+ if (!route.match.domains)
575
+ continue;
576
+ // Skip routes without TLS termination or auto certificates
577
+ if (route.action.type !== 'forward' ||
578
+ !route.action.tls ||
579
+ route.action.tls.mode === 'passthrough' ||
580
+ route.action.tls.certificate !== 'auto')
581
+ continue;
582
+ const routeDomains = Array.isArray(route.match.domains)
583
+ ? route.match.domains
584
+ : [route.match.domains];
585
+ // Skip domains that can't be used with ACME
586
+ const eligibleDomains = routeDomains.filter(domain => !domain.includes('*') && this.isValidDomain(domain));
587
+ domains.push(...eligibleDomains);
588
+ }
589
+ // Legacy mode is no longer supported
590
+ return domains;
591
+ }
592
+ /**
593
+ * Get NFTables status
594
+ */
595
+ async getNfTablesStatus() {
596
+ return this.nftablesManager.getStatus();
597
+ }
598
+ /**
599
+ * Get status of certificates managed by Port80Handler
600
+ */
601
+ getCertificateStatus() {
602
+ if (!this.port80Handler) {
603
+ return {
604
+ enabled: false,
605
+ message: 'Port80Handler is not enabled'
606
+ };
607
+ }
608
+ // Get eligible domains
609
+ const eligibleDomains = this.getEligibleDomainsForCertificates();
610
+ const certificateStatus = {};
611
+ // Check each domain
612
+ for (const domain of eligibleDomains) {
613
+ const cert = this.port80Handler.getCertificate(domain);
614
+ if (cert) {
615
+ const now = new Date();
616
+ const expiryDate = cert.expiryDate;
617
+ const daysRemaining = Math.floor((expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000));
618
+ certificateStatus[domain] = {
619
+ status: 'valid',
620
+ expiryDate: expiryDate.toISOString(),
621
+ daysRemaining,
622
+ renewalNeeded: daysRemaining <= (this.settings.acme?.renewThresholdDays ?? 0)
623
+ };
624
+ }
625
+ else {
626
+ certificateStatus[domain] = {
627
+ status: 'missing',
628
+ message: 'No certificate found'
629
+ };
630
+ }
631
+ }
632
+ const acme = this.settings.acme;
633
+ return {
634
+ enabled: true,
635
+ port: acme.port,
636
+ useProduction: acme.useProduction,
637
+ autoRenew: acme.autoRenew,
638
+ certificates: certificateStatus
639
+ };
640
+ }
641
+ }
642
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGVnYWN5LXNtYXJ0LXByb3h5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcHJveGllcy9zbWFydC1wcm94eS9sZWdhY3ktc21hcnQtcHJveHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUU1QyxnQ0FBZ0M7QUFDaEMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQ3hELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUM5QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUMvRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDdEQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUN2RSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFeEQsd0JBQXdCO0FBQ3hCLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxxQ0FBcUMsQ0FBQztBQUNwRSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0saURBQWlELENBQUM7QUFFbEYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sd0NBQXdDLENBQUM7QUFDNUUsT0FBTyxFQUFFLDBCQUEwQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFRNUU7Ozs7Ozs7Ozs7OztHQVlHO0FBQ0gsTUFBTSxPQUFPLFVBQVcsU0FBUSxPQUFPLENBQUMsWUFBWTtJQXFCbEQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQThCRztJQUNILFlBQVksV0FB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