@push.rocks/smartproxy 5.0.0 → 6.0.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 (78) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.pp.interfaces.d.ts +23 -0
  3. package/dist_ts/classes.pp.networkproxybridge.d.ts +15 -1
  4. package/dist_ts/classes.pp.networkproxybridge.js +116 -21
  5. package/dist_ts/classes.pp.portproxy.d.ts +20 -4
  6. package/dist_ts/classes.pp.portproxy.js +321 -22
  7. package/dist_ts/index.d.ts +6 -6
  8. package/dist_ts/index.js +7 -7
  9. package/dist_ts/networkproxy/classes.np.certificatemanager.d.ts +77 -0
  10. package/dist_ts/networkproxy/classes.np.certificatemanager.js +354 -0
  11. package/dist_ts/networkproxy/classes.np.connectionpool.d.ts +47 -0
  12. package/dist_ts/networkproxy/classes.np.connectionpool.js +210 -0
  13. package/dist_ts/networkproxy/classes.np.networkproxy.d.ts +117 -0
  14. package/dist_ts/networkproxy/classes.np.networkproxy.js +375 -0
  15. package/dist_ts/networkproxy/classes.np.requesthandler.d.ts +51 -0
  16. package/dist_ts/networkproxy/classes.np.requesthandler.js +210 -0
  17. package/dist_ts/networkproxy/classes.np.types.d.ts +82 -0
  18. package/dist_ts/networkproxy/classes.np.types.js +35 -0
  19. package/dist_ts/networkproxy/classes.np.websockethandler.d.ts +38 -0
  20. package/dist_ts/networkproxy/classes.np.websockethandler.js +188 -0
  21. package/dist_ts/networkproxy/index.d.ts +6 -0
  22. package/dist_ts/networkproxy/index.js +8 -0
  23. package/dist_ts/nfttablesproxy/classes.nftablesproxy.d.ts +219 -0
  24. package/dist_ts/nfttablesproxy/classes.nftablesproxy.js +1542 -0
  25. package/dist_ts/port80handler/classes.port80handler.d.ts +260 -0
  26. package/dist_ts/port80handler/classes.port80handler.js +928 -0
  27. package/dist_ts/smartproxy/classes.pp.connectionhandler.d.ts +39 -0
  28. package/dist_ts/smartproxy/classes.pp.connectionhandler.js +754 -0
  29. package/dist_ts/smartproxy/classes.pp.connectionmanager.d.ts +78 -0
  30. package/dist_ts/smartproxy/classes.pp.connectionmanager.js +378 -0
  31. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +55 -0
  32. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +103 -0
  33. package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +133 -0
  34. package/dist_ts/smartproxy/classes.pp.interfaces.js +2 -0
  35. package/dist_ts/smartproxy/classes.pp.networkproxybridge.d.ts +57 -0
  36. package/dist_ts/smartproxy/classes.pp.networkproxybridge.js +306 -0
  37. package/dist_ts/smartproxy/classes.pp.portrangemanager.d.ts +56 -0
  38. package/dist_ts/smartproxy/classes.pp.portrangemanager.js +179 -0
  39. package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +47 -0
  40. package/dist_ts/smartproxy/classes.pp.securitymanager.js +126 -0
  41. package/dist_ts/smartproxy/classes.pp.snihandler.d.ts +153 -0
  42. package/dist_ts/smartproxy/classes.pp.snihandler.js +1053 -0
  43. package/dist_ts/smartproxy/classes.pp.timeoutmanager.d.ts +47 -0
  44. package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +154 -0
  45. package/dist_ts/smartproxy/classes.pp.tlsalert.d.ts +149 -0
  46. package/dist_ts/smartproxy/classes.pp.tlsalert.js +225 -0
  47. package/dist_ts/smartproxy/classes.pp.tlsmanager.d.ts +57 -0
  48. package/dist_ts/smartproxy/classes.pp.tlsmanager.js +132 -0
  49. package/dist_ts/smartproxy/classes.smartproxy.d.ts +64 -0
  50. package/dist_ts/smartproxy/classes.smartproxy.js +567 -0
  51. package/package.json +1 -1
  52. package/readme.md +77 -27
  53. package/ts/00_commitinfo_data.ts +1 -1
  54. package/ts/index.ts +6 -6
  55. package/ts/networkproxy/classes.np.certificatemanager.ts +398 -0
  56. package/ts/networkproxy/classes.np.connectionpool.ts +241 -0
  57. package/ts/networkproxy/classes.np.networkproxy.ts +469 -0
  58. package/ts/networkproxy/classes.np.requesthandler.ts +278 -0
  59. package/ts/networkproxy/classes.np.types.ts +123 -0
  60. package/ts/networkproxy/classes.np.websockethandler.ts +226 -0
  61. package/ts/networkproxy/index.ts +7 -0
  62. package/ts/{classes.port80handler.ts → port80handler/classes.port80handler.ts} +249 -1
  63. package/ts/{classes.pp.connectionhandler.ts → smartproxy/classes.pp.connectionhandler.ts} +1 -1
  64. package/ts/{classes.pp.connectionmanager.ts → smartproxy/classes.pp.connectionmanager.ts} +1 -1
  65. package/ts/{classes.pp.domainconfigmanager.ts → smartproxy/classes.pp.domainconfigmanager.ts} +1 -1
  66. package/ts/{classes.pp.interfaces.ts → smartproxy/classes.pp.interfaces.ts} +31 -5
  67. package/ts/{classes.pp.networkproxybridge.ts → smartproxy/classes.pp.networkproxybridge.ts} +129 -28
  68. package/ts/{classes.pp.securitymanager.ts → smartproxy/classes.pp.securitymanager.ts} +1 -1
  69. package/ts/{classes.pp.tlsmanager.ts → smartproxy/classes.pp.tlsmanager.ts} +1 -1
  70. package/ts/smartproxy/classes.smartproxy.ts +679 -0
  71. package/ts/classes.networkproxy.ts +0 -1730
  72. package/ts/classes.pp.acmemanager.ts +0 -149
  73. package/ts/classes.pp.portproxy.ts +0 -344
  74. /package/ts/{classes.nftablesproxy.ts → nfttablesproxy/classes.nftablesproxy.ts} +0 -0
  75. /package/ts/{classes.pp.portrangemanager.ts → smartproxy/classes.pp.portrangemanager.ts} +0 -0
  76. /package/ts/{classes.pp.snihandler.ts → smartproxy/classes.pp.snihandler.ts} +0 -0
  77. /package/ts/{classes.pp.timeoutmanager.ts → smartproxy/classes.pp.timeoutmanager.ts} +0 -0
  78. /package/ts/{classes.pp.tlsalert.ts → smartproxy/classes.pp.tlsalert.ts} +0 -0
@@ -0,0 +1,928 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { IncomingMessage, ServerResponse } from 'http';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ /**
6
+ * Custom error classes for better error handling
7
+ */
8
+ export class Port80HandlerError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'Port80HandlerError';
12
+ }
13
+ }
14
+ export class CertificateError extends Port80HandlerError {
15
+ constructor(message, domain, isRenewal = false) {
16
+ super(`${message} for domain ${domain}${isRenewal ? ' (renewal)' : ''}`);
17
+ this.domain = domain;
18
+ this.isRenewal = isRenewal;
19
+ this.name = 'CertificateError';
20
+ }
21
+ }
22
+ export class ServerError extends Port80HandlerError {
23
+ constructor(message, code) {
24
+ super(message);
25
+ this.code = code;
26
+ this.name = 'ServerError';
27
+ }
28
+ }
29
+ /**
30
+ * Events emitted by the Port80Handler
31
+ */
32
+ export var Port80HandlerEvents;
33
+ (function (Port80HandlerEvents) {
34
+ Port80HandlerEvents["CERTIFICATE_ISSUED"] = "certificate-issued";
35
+ Port80HandlerEvents["CERTIFICATE_RENEWED"] = "certificate-renewed";
36
+ Port80HandlerEvents["CERTIFICATE_FAILED"] = "certificate-failed";
37
+ Port80HandlerEvents["CERTIFICATE_EXPIRING"] = "certificate-expiring";
38
+ Port80HandlerEvents["MANAGER_STARTED"] = "manager-started";
39
+ Port80HandlerEvents["MANAGER_STOPPED"] = "manager-stopped";
40
+ Port80HandlerEvents["REQUEST_FORWARDED"] = "request-forwarded";
41
+ })(Port80HandlerEvents || (Port80HandlerEvents = {}));
42
+ /**
43
+ * Port80Handler with ACME certificate management and request forwarding capabilities
44
+ * Now with glob pattern support for domain matching
45
+ */
46
+ export class Port80Handler extends plugins.EventEmitter {
47
+ /**
48
+ * Creates a new Port80Handler
49
+ * @param options Configuration options
50
+ */
51
+ constructor(options = {}) {
52
+ super();
53
+ this.server = null;
54
+ this.acmeClient = null;
55
+ this.accountKey = null;
56
+ this.renewalTimer = null;
57
+ this.isShuttingDown = false;
58
+ this.domainCertificates = new Map();
59
+ // Default options
60
+ this.options = {
61
+ port: options.port ?? 80,
62
+ contactEmail: options.contactEmail ?? 'admin@example.com',
63
+ useProduction: options.useProduction ?? false, // Safer default: staging
64
+ renewThresholdDays: options.renewThresholdDays ?? 10, // Changed to 10 days as per requirements
65
+ httpsRedirectPort: options.httpsRedirectPort ?? 443,
66
+ renewCheckIntervalHours: options.renewCheckIntervalHours ?? 24,
67
+ enabled: options.enabled ?? true, // Enable by default
68
+ autoRenew: options.autoRenew ?? true, // Auto-renew by default
69
+ certificateStore: options.certificateStore ?? './certs', // Default store location
70
+ skipConfiguredCerts: options.skipConfiguredCerts ?? false
71
+ };
72
+ }
73
+ /**
74
+ * Starts the HTTP server for ACME challenges
75
+ */
76
+ async start() {
77
+ if (this.server) {
78
+ throw new ServerError('Server is already running');
79
+ }
80
+ if (this.isShuttingDown) {
81
+ throw new ServerError('Server is shutting down');
82
+ }
83
+ // Skip if disabled
84
+ if (this.options.enabled === false) {
85
+ console.log('Port80Handler is disabled, skipping start');
86
+ return;
87
+ }
88
+ return new Promise((resolve, reject) => {
89
+ try {
90
+ // Load certificates from store if enabled
91
+ if (this.options.certificateStore) {
92
+ this.loadCertificatesFromStore();
93
+ }
94
+ this.server = plugins.http.createServer((req, res) => this.handleRequest(req, res));
95
+ this.server.on('error', (error) => {
96
+ if (error.code === 'EACCES') {
97
+ reject(new ServerError(`Permission denied to bind to port ${this.options.port}. Try running with elevated privileges or use a port > 1024.`, error.code));
98
+ }
99
+ else if (error.code === 'EADDRINUSE') {
100
+ reject(new ServerError(`Port ${this.options.port} is already in use.`, error.code));
101
+ }
102
+ else {
103
+ reject(new ServerError(error.message, error.code));
104
+ }
105
+ });
106
+ this.server.listen(this.options.port, () => {
107
+ console.log(`Port80Handler is listening on port ${this.options.port}`);
108
+ this.startRenewalTimer();
109
+ this.emit(Port80HandlerEvents.MANAGER_STARTED, this.options.port);
110
+ // Start certificate process for domains with acmeMaintenance enabled
111
+ for (const [domain, domainInfo] of this.domainCertificates.entries()) {
112
+ // Skip glob patterns for certificate issuance
113
+ if (this.isGlobPattern(domain)) {
114
+ console.log(`Skipping initial certificate for glob pattern: ${domain}`);
115
+ continue;
116
+ }
117
+ if (domainInfo.options.acmeMaintenance && !domainInfo.certObtained && !domainInfo.obtainingInProgress) {
118
+ this.obtainCertificate(domain).catch(err => {
119
+ console.error(`Error obtaining initial certificate for ${domain}:`, err);
120
+ });
121
+ }
122
+ }
123
+ resolve();
124
+ });
125
+ }
126
+ catch (error) {
127
+ const message = error instanceof Error ? error.message : 'Unknown error starting server';
128
+ reject(new ServerError(message));
129
+ }
130
+ });
131
+ }
132
+ /**
133
+ * Stops the HTTP server and renewal timer
134
+ */
135
+ async stop() {
136
+ if (!this.server) {
137
+ return;
138
+ }
139
+ this.isShuttingDown = true;
140
+ // Stop the renewal timer
141
+ if (this.renewalTimer) {
142
+ clearInterval(this.renewalTimer);
143
+ this.renewalTimer = null;
144
+ }
145
+ return new Promise((resolve) => {
146
+ if (this.server) {
147
+ this.server.close(() => {
148
+ this.server = null;
149
+ this.isShuttingDown = false;
150
+ this.emit(Port80HandlerEvents.MANAGER_STOPPED);
151
+ resolve();
152
+ });
153
+ }
154
+ else {
155
+ this.isShuttingDown = false;
156
+ resolve();
157
+ }
158
+ });
159
+ }
160
+ /**
161
+ * Adds a domain with configuration options
162
+ * @param options Domain configuration options
163
+ */
164
+ addDomain(options) {
165
+ if (!options.domainName || typeof options.domainName !== 'string') {
166
+ throw new Port80HandlerError('Invalid domain name');
167
+ }
168
+ const domainName = options.domainName;
169
+ if (!this.domainCertificates.has(domainName)) {
170
+ this.domainCertificates.set(domainName, {
171
+ options,
172
+ certObtained: false,
173
+ obtainingInProgress: false
174
+ });
175
+ console.log(`Domain added: ${domainName} with configuration:`, {
176
+ sslRedirect: options.sslRedirect,
177
+ acmeMaintenance: options.acmeMaintenance,
178
+ hasForward: !!options.forward,
179
+ hasAcmeForward: !!options.acmeForward
180
+ });
181
+ // If acmeMaintenance is enabled and not a glob pattern, start certificate process immediately
182
+ if (options.acmeMaintenance && this.server && !this.isGlobPattern(domainName)) {
183
+ this.obtainCertificate(domainName).catch(err => {
184
+ console.error(`Error obtaining initial certificate for ${domainName}:`, err);
185
+ });
186
+ }
187
+ }
188
+ else {
189
+ // Update existing domain with new options
190
+ const existing = this.domainCertificates.get(domainName);
191
+ existing.options = options;
192
+ console.log(`Domain ${domainName} configuration updated`);
193
+ }
194
+ }
195
+ /**
196
+ * Removes a domain from management
197
+ * @param domain The domain to remove
198
+ */
199
+ removeDomain(domain) {
200
+ if (this.domainCertificates.delete(domain)) {
201
+ console.log(`Domain removed: ${domain}`);
202
+ }
203
+ }
204
+ /**
205
+ * Sets a certificate for a domain directly (for externally obtained certificates)
206
+ * @param domain The domain for the certificate
207
+ * @param certificate The certificate (PEM format)
208
+ * @param privateKey The private key (PEM format)
209
+ * @param expiryDate Optional expiry date
210
+ */
211
+ setCertificate(domain, certificate, privateKey, expiryDate) {
212
+ if (!domain || !certificate || !privateKey) {
213
+ throw new Port80HandlerError('Domain, certificate and privateKey are required');
214
+ }
215
+ // Don't allow setting certificates for glob patterns
216
+ if (this.isGlobPattern(domain)) {
217
+ throw new Port80HandlerError('Cannot set certificate for glob pattern domains');
218
+ }
219
+ let domainInfo = this.domainCertificates.get(domain);
220
+ if (!domainInfo) {
221
+ // Create default domain options if not already configured
222
+ const defaultOptions = {
223
+ domainName: domain,
224
+ sslRedirect: true,
225
+ acmeMaintenance: true
226
+ };
227
+ domainInfo = {
228
+ options: defaultOptions,
229
+ certObtained: false,
230
+ obtainingInProgress: false
231
+ };
232
+ this.domainCertificates.set(domain, domainInfo);
233
+ }
234
+ domainInfo.certificate = certificate;
235
+ domainInfo.privateKey = privateKey;
236
+ domainInfo.certObtained = true;
237
+ domainInfo.obtainingInProgress = false;
238
+ if (expiryDate) {
239
+ domainInfo.expiryDate = expiryDate;
240
+ }
241
+ else {
242
+ // Extract expiry date from certificate
243
+ domainInfo.expiryDate = this.extractExpiryDateFromCertificate(certificate, domain);
244
+ }
245
+ console.log(`Certificate set for ${domain}`);
246
+ // Save certificate to store if enabled
247
+ if (this.options.certificateStore) {
248
+ this.saveCertificateToStore(domain, certificate, privateKey);
249
+ }
250
+ // Emit certificate event
251
+ this.emitCertificateEvent(Port80HandlerEvents.CERTIFICATE_ISSUED, {
252
+ domain,
253
+ certificate,
254
+ privateKey,
255
+ expiryDate: domainInfo.expiryDate || this.getDefaultExpiryDate()
256
+ });
257
+ }
258
+ /**
259
+ * Gets the certificate for a domain if it exists
260
+ * @param domain The domain to get the certificate for
261
+ */
262
+ getCertificate(domain) {
263
+ // Can't get certificates for glob patterns
264
+ if (this.isGlobPattern(domain)) {
265
+ return null;
266
+ }
267
+ const domainInfo = this.domainCertificates.get(domain);
268
+ if (!domainInfo || !domainInfo.certObtained || !domainInfo.certificate || !domainInfo.privateKey) {
269
+ return null;
270
+ }
271
+ return {
272
+ domain,
273
+ certificate: domainInfo.certificate,
274
+ privateKey: domainInfo.privateKey,
275
+ expiryDate: domainInfo.expiryDate || this.getDefaultExpiryDate()
276
+ };
277
+ }
278
+ /**
279
+ * Saves a certificate to the filesystem store
280
+ * @param domain The domain for the certificate
281
+ * @param certificate The certificate (PEM format)
282
+ * @param privateKey The private key (PEM format)
283
+ * @private
284
+ */
285
+ saveCertificateToStore(domain, certificate, privateKey) {
286
+ // Skip if certificate store is not enabled
287
+ if (!this.options.certificateStore)
288
+ return;
289
+ try {
290
+ const storePath = this.options.certificateStore;
291
+ // Ensure the directory exists
292
+ if (!fs.existsSync(storePath)) {
293
+ fs.mkdirSync(storePath, { recursive: true });
294
+ console.log(`Created certificate store directory: ${storePath}`);
295
+ }
296
+ const certPath = path.join(storePath, `${domain}.cert.pem`);
297
+ const keyPath = path.join(storePath, `${domain}.key.pem`);
298
+ // Write certificate and private key files
299
+ fs.writeFileSync(certPath, certificate);
300
+ fs.writeFileSync(keyPath, privateKey);
301
+ // Set secure permissions for private key
302
+ try {
303
+ fs.chmodSync(keyPath, 0o600);
304
+ }
305
+ catch (err) {
306
+ console.log(`Warning: Could not set secure permissions on ${keyPath}`);
307
+ }
308
+ console.log(`Saved certificate for ${domain} to ${certPath}`);
309
+ }
310
+ catch (err) {
311
+ console.error(`Error saving certificate for ${domain}:`, err);
312
+ }
313
+ }
314
+ /**
315
+ * Loads certificates from the certificate store
316
+ * @private
317
+ */
318
+ loadCertificatesFromStore() {
319
+ if (!this.options.certificateStore)
320
+ return;
321
+ try {
322
+ const storePath = this.options.certificateStore;
323
+ // Ensure the directory exists
324
+ if (!fs.existsSync(storePath)) {
325
+ fs.mkdirSync(storePath, { recursive: true });
326
+ console.log(`Created certificate store directory: ${storePath}`);
327
+ return;
328
+ }
329
+ // Get list of certificate files
330
+ const files = fs.readdirSync(storePath);
331
+ const certFiles = files.filter(file => file.endsWith('.cert.pem'));
332
+ // Load each certificate
333
+ for (const certFile of certFiles) {
334
+ const domain = certFile.replace('.cert.pem', '');
335
+ const keyFile = `${domain}.key.pem`;
336
+ // Skip if key file doesn't exist
337
+ if (!files.includes(keyFile)) {
338
+ console.log(`Warning: Found certificate for ${domain} but no key file`);
339
+ continue;
340
+ }
341
+ // Skip if we should skip configured certs
342
+ if (this.options.skipConfiguredCerts) {
343
+ const domainInfo = this.domainCertificates.get(domain);
344
+ if (domainInfo && domainInfo.certObtained) {
345
+ console.log(`Skipping already configured certificate for ${domain}`);
346
+ continue;
347
+ }
348
+ }
349
+ // Load certificate and key
350
+ try {
351
+ const certificate = fs.readFileSync(path.join(storePath, certFile), 'utf8');
352
+ const privateKey = fs.readFileSync(path.join(storePath, keyFile), 'utf8');
353
+ // Extract expiry date
354
+ let expiryDate;
355
+ try {
356
+ const matches = certificate.match(/Not After\s*:\s*(.*?)(?:\n|$)/i);
357
+ if (matches && matches[1]) {
358
+ expiryDate = new Date(matches[1]);
359
+ }
360
+ }
361
+ catch (err) {
362
+ console.log(`Warning: Could not extract expiry date from certificate for ${domain}`);
363
+ }
364
+ // Check if domain is already registered
365
+ let domainInfo = this.domainCertificates.get(domain);
366
+ if (!domainInfo) {
367
+ // Register domain if not already registered
368
+ domainInfo = {
369
+ options: {
370
+ domainName: domain,
371
+ sslRedirect: true,
372
+ acmeMaintenance: true
373
+ },
374
+ certObtained: false,
375
+ obtainingInProgress: false
376
+ };
377
+ this.domainCertificates.set(domain, domainInfo);
378
+ }
379
+ // Set certificate
380
+ domainInfo.certificate = certificate;
381
+ domainInfo.privateKey = privateKey;
382
+ domainInfo.certObtained = true;
383
+ domainInfo.expiryDate = expiryDate;
384
+ console.log(`Loaded certificate for ${domain} from store, valid until ${expiryDate?.toISOString() || 'unknown'}`);
385
+ }
386
+ catch (err) {
387
+ console.error(`Error loading certificate for ${domain}:`, err);
388
+ }
389
+ }
390
+ }
391
+ catch (err) {
392
+ console.error('Error loading certificates from store:', err);
393
+ }
394
+ }
395
+ /**
396
+ * Check if a domain is a glob pattern
397
+ * @param domain Domain to check
398
+ * @returns True if the domain is a glob pattern
399
+ */
400
+ isGlobPattern(domain) {
401
+ return domain.includes('*');
402
+ }
403
+ /**
404
+ * Get domain info for a specific domain, using glob pattern matching if needed
405
+ * @param requestDomain The actual domain from the request
406
+ * @returns The domain info or null if not found
407
+ */
408
+ getDomainInfoForRequest(requestDomain) {
409
+ // Try direct match first
410
+ if (this.domainCertificates.has(requestDomain)) {
411
+ return {
412
+ domainInfo: this.domainCertificates.get(requestDomain),
413
+ pattern: requestDomain
414
+ };
415
+ }
416
+ // Then try glob patterns
417
+ for (const [pattern, domainInfo] of this.domainCertificates.entries()) {
418
+ if (this.isGlobPattern(pattern) && this.domainMatchesPattern(requestDomain, pattern)) {
419
+ return { domainInfo, pattern };
420
+ }
421
+ }
422
+ return null;
423
+ }
424
+ /**
425
+ * Check if a domain matches a glob pattern
426
+ * @param domain The domain to check
427
+ * @param pattern The pattern to match against
428
+ * @returns True if the domain matches the pattern
429
+ */
430
+ domainMatchesPattern(domain, pattern) {
431
+ // Handle different glob pattern styles
432
+ if (pattern.startsWith('*.')) {
433
+ // *.example.com matches any subdomain
434
+ const suffix = pattern.substring(2);
435
+ return domain.endsWith(suffix) && domain.includes('.') && domain !== suffix;
436
+ }
437
+ else if (pattern.endsWith('.*')) {
438
+ // example.* matches any TLD
439
+ const prefix = pattern.substring(0, pattern.length - 2);
440
+ const domainParts = domain.split('.');
441
+ return domain.startsWith(prefix + '.') && domainParts.length >= 2;
442
+ }
443
+ else if (pattern === '*') {
444
+ // Wildcard matches everything
445
+ return true;
446
+ }
447
+ else {
448
+ // Exact match (shouldn't reach here as we check exact matches first)
449
+ return domain === pattern;
450
+ }
451
+ }
452
+ /**
453
+ * Lazy initialization of the ACME client
454
+ * @returns An ACME client instance
455
+ */
456
+ async getAcmeClient() {
457
+ if (this.acmeClient) {
458
+ return this.acmeClient;
459
+ }
460
+ try {
461
+ // Generate a new account key
462
+ this.accountKey = (await plugins.acme.forge.createPrivateKey()).toString();
463
+ this.acmeClient = new plugins.acme.Client({
464
+ directoryUrl: this.options.useProduction
465
+ ? plugins.acme.directory.letsencrypt.production
466
+ : plugins.acme.directory.letsencrypt.staging,
467
+ accountKey: this.accountKey,
468
+ });
469
+ // Create a new account
470
+ await this.acmeClient.createAccount({
471
+ termsOfServiceAgreed: true,
472
+ contact: [`mailto:${this.options.contactEmail}`],
473
+ });
474
+ return this.acmeClient;
475
+ }
476
+ catch (error) {
477
+ const message = error instanceof Error ? error.message : 'Unknown error initializing ACME client';
478
+ throw new Port80HandlerError(`Failed to initialize ACME client: ${message}`);
479
+ }
480
+ }
481
+ /**
482
+ * Handles incoming HTTP requests
483
+ * @param req The HTTP request
484
+ * @param res The HTTP response
485
+ */
486
+ handleRequest(req, res) {
487
+ const hostHeader = req.headers.host;
488
+ if (!hostHeader) {
489
+ res.statusCode = 400;
490
+ res.end('Bad Request: Host header is missing');
491
+ return;
492
+ }
493
+ // Extract domain (ignoring any port in the Host header)
494
+ const domain = hostHeader.split(':')[0];
495
+ // Get domain config, using glob pattern matching if needed
496
+ const domainMatch = this.getDomainInfoForRequest(domain);
497
+ if (!domainMatch) {
498
+ res.statusCode = 404;
499
+ res.end('Domain not configured');
500
+ return;
501
+ }
502
+ const { domainInfo, pattern } = domainMatch;
503
+ const options = domainInfo.options;
504
+ // If the request is for an ACME HTTP-01 challenge, handle it
505
+ if (req.url && req.url.startsWith('/.well-known/acme-challenge/') && (options.acmeMaintenance || options.acmeForward)) {
506
+ // Check if we should forward ACME requests
507
+ if (options.acmeForward) {
508
+ this.forwardRequest(req, res, options.acmeForward, 'ACME challenge');
509
+ return;
510
+ }
511
+ // Only handle ACME challenges for non-glob patterns
512
+ if (!this.isGlobPattern(pattern)) {
513
+ this.handleAcmeChallenge(req, res, domain);
514
+ return;
515
+ }
516
+ }
517
+ // Check if we should forward non-ACME requests
518
+ if (options.forward) {
519
+ this.forwardRequest(req, res, options.forward, 'HTTP');
520
+ return;
521
+ }
522
+ // If certificate exists and sslRedirect is enabled, redirect to HTTPS
523
+ // (Skip for glob patterns as they won't have certificates)
524
+ if (!this.isGlobPattern(pattern) && domainInfo.certObtained && options.sslRedirect) {
525
+ const httpsPort = this.options.httpsRedirectPort;
526
+ const portSuffix = httpsPort === 443 ? '' : `:${httpsPort}`;
527
+ const redirectUrl = `https://${domain}${portSuffix}${req.url || '/'}`;
528
+ res.statusCode = 301;
529
+ res.setHeader('Location', redirectUrl);
530
+ res.end(`Redirecting to ${redirectUrl}`);
531
+ return;
532
+ }
533
+ // Handle case where certificate maintenance is enabled but not yet obtained
534
+ // (Skip for glob patterns as they can't have certificates)
535
+ if (!this.isGlobPattern(pattern) && options.acmeMaintenance && !domainInfo.certObtained) {
536
+ // Trigger certificate issuance if not already running
537
+ if (!domainInfo.obtainingInProgress) {
538
+ this.obtainCertificate(domain).catch(err => {
539
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
540
+ this.emit(Port80HandlerEvents.CERTIFICATE_FAILED, {
541
+ domain,
542
+ error: errorMessage,
543
+ isRenewal: false
544
+ });
545
+ console.error(`Error obtaining certificate for ${domain}:`, err);
546
+ });
547
+ }
548
+ res.statusCode = 503;
549
+ res.end('Certificate issuance in progress, please try again later.');
550
+ return;
551
+ }
552
+ // Default response for unhandled request
553
+ res.statusCode = 404;
554
+ res.end('No handlers configured for this request');
555
+ }
556
+ /**
557
+ * Forwards an HTTP request to the specified target
558
+ * @param req The original request
559
+ * @param res The response object
560
+ * @param target The forwarding target (IP and port)
561
+ * @param requestType Type of request for logging
562
+ */
563
+ forwardRequest(req, res, target, requestType) {
564
+ const options = {
565
+ hostname: target.ip,
566
+ port: target.port,
567
+ path: req.url,
568
+ method: req.method,
569
+ headers: { ...req.headers }
570
+ };
571
+ const domain = req.headers.host?.split(':')[0] || 'unknown';
572
+ console.log(`Forwarding ${requestType} request for ${domain} to ${target.ip}:${target.port}`);
573
+ const proxyReq = plugins.http.request(options, (proxyRes) => {
574
+ // Copy status code
575
+ res.statusCode = proxyRes.statusCode || 500;
576
+ // Copy headers
577
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
578
+ if (value)
579
+ res.setHeader(key, value);
580
+ }
581
+ // Pipe response data
582
+ proxyRes.pipe(res);
583
+ this.emit(Port80HandlerEvents.REQUEST_FORWARDED, {
584
+ domain,
585
+ requestType,
586
+ target: `${target.ip}:${target.port}`,
587
+ statusCode: proxyRes.statusCode
588
+ });
589
+ });
590
+ proxyReq.on('error', (error) => {
591
+ console.error(`Error forwarding request to ${target.ip}:${target.port}:`, error);
592
+ if (!res.headersSent) {
593
+ res.statusCode = 502;
594
+ res.end(`Proxy error: ${error.message}`);
595
+ }
596
+ else {
597
+ res.end();
598
+ }
599
+ });
600
+ // Pipe original request to proxy request
601
+ if (req.readable) {
602
+ req.pipe(proxyReq);
603
+ }
604
+ else {
605
+ proxyReq.end();
606
+ }
607
+ }
608
+ /**
609
+ * Serves the ACME HTTP-01 challenge response
610
+ * @param req The HTTP request
611
+ * @param res The HTTP response
612
+ * @param domain The domain for the challenge
613
+ */
614
+ handleAcmeChallenge(req, res, domain) {
615
+ const domainInfo = this.domainCertificates.get(domain);
616
+ if (!domainInfo) {
617
+ res.statusCode = 404;
618
+ res.end('Domain not configured');
619
+ return;
620
+ }
621
+ // The token is the last part of the URL
622
+ const urlParts = req.url?.split('/');
623
+ const token = urlParts ? urlParts[urlParts.length - 1] : '';
624
+ if (domainInfo.challengeToken === token && domainInfo.challengeKeyAuthorization) {
625
+ res.statusCode = 200;
626
+ res.setHeader('Content-Type', 'text/plain');
627
+ res.end(domainInfo.challengeKeyAuthorization);
628
+ console.log(`Served ACME challenge response for ${domain}`);
629
+ }
630
+ else {
631
+ res.statusCode = 404;
632
+ res.end('Challenge token not found');
633
+ }
634
+ }
635
+ /**
636
+ * Obtains a certificate for a domain using ACME HTTP-01 challenge
637
+ * @param domain The domain to obtain a certificate for
638
+ * @param isRenewal Whether this is a renewal attempt
639
+ */
640
+ async obtainCertificate(domain, isRenewal = false) {
641
+ // Don't allow certificate issuance for glob patterns
642
+ if (this.isGlobPattern(domain)) {
643
+ throw new CertificateError('Cannot obtain certificates for glob pattern domains', domain, isRenewal);
644
+ }
645
+ // Get the domain info
646
+ const domainInfo = this.domainCertificates.get(domain);
647
+ if (!domainInfo) {
648
+ throw new CertificateError('Domain not found', domain, isRenewal);
649
+ }
650
+ // Verify that acmeMaintenance is enabled
651
+ if (!domainInfo.options.acmeMaintenance) {
652
+ console.log(`Skipping certificate issuance for ${domain} - acmeMaintenance is disabled`);
653
+ return;
654
+ }
655
+ // Prevent concurrent certificate issuance
656
+ if (domainInfo.obtainingInProgress) {
657
+ console.log(`Certificate issuance already in progress for ${domain}`);
658
+ return;
659
+ }
660
+ domainInfo.obtainingInProgress = true;
661
+ domainInfo.lastRenewalAttempt = new Date();
662
+ try {
663
+ const client = await this.getAcmeClient();
664
+ // Create a new order for the domain
665
+ const order = await client.createOrder({
666
+ identifiers: [{ type: 'dns', value: domain }],
667
+ });
668
+ // Get the authorizations for the order
669
+ const authorizations = await client.getAuthorizations(order);
670
+ // Process each authorization
671
+ await this.processAuthorizations(client, domain, authorizations);
672
+ // Generate a CSR and private key
673
+ const [csrBuffer, privateKeyBuffer] = await plugins.acme.forge.createCsr({
674
+ commonName: domain,
675
+ });
676
+ const csr = csrBuffer.toString();
677
+ const privateKey = privateKeyBuffer.toString();
678
+ // Finalize the order with our CSR
679
+ await client.finalizeOrder(order, csr);
680
+ // Get the certificate with the full chain
681
+ const certificate = await client.getCertificate(order);
682
+ // Store the certificate and key
683
+ domainInfo.certificate = certificate;
684
+ domainInfo.privateKey = privateKey;
685
+ domainInfo.certObtained = true;
686
+ // Clear challenge data
687
+ delete domainInfo.challengeToken;
688
+ delete domainInfo.challengeKeyAuthorization;
689
+ // Extract expiry date from certificate
690
+ domainInfo.expiryDate = this.extractExpiryDateFromCertificate(certificate, domain);
691
+ console.log(`Certificate ${isRenewal ? 'renewed' : 'obtained'} for ${domain}`);
692
+ // Save the certificate to the store if enabled
693
+ if (this.options.certificateStore) {
694
+ this.saveCertificateToStore(domain, certificate, privateKey);
695
+ }
696
+ // Emit the appropriate event
697
+ const eventType = isRenewal
698
+ ? Port80HandlerEvents.CERTIFICATE_RENEWED
699
+ : Port80HandlerEvents.CERTIFICATE_ISSUED;
700
+ this.emitCertificateEvent(eventType, {
701
+ domain,
702
+ certificate,
703
+ privateKey,
704
+ expiryDate: domainInfo.expiryDate || this.getDefaultExpiryDate()
705
+ });
706
+ }
707
+ catch (error) {
708
+ // Check for rate limit errors
709
+ if (error.message && (error.message.includes('rateLimited') ||
710
+ error.message.includes('too many certificates') ||
711
+ error.message.includes('rate limit'))) {
712
+ console.error(`Rate limit reached for ${domain}. Waiting before retry.`);
713
+ }
714
+ else {
715
+ console.error(`Error during certificate issuance for ${domain}:`, error);
716
+ }
717
+ // Emit failure event
718
+ this.emit(Port80HandlerEvents.CERTIFICATE_FAILED, {
719
+ domain,
720
+ error: error.message || 'Unknown error',
721
+ isRenewal
722
+ });
723
+ throw new CertificateError(error.message || 'Certificate issuance failed', domain, isRenewal);
724
+ }
725
+ finally {
726
+ // Reset flag whether successful or not
727
+ domainInfo.obtainingInProgress = false;
728
+ }
729
+ }
730
+ /**
731
+ * Process ACME authorizations by verifying and completing challenges
732
+ * @param client ACME client
733
+ * @param domain Domain name
734
+ * @param authorizations Authorizations to process
735
+ */
736
+ async processAuthorizations(client, domain, authorizations) {
737
+ const domainInfo = this.domainCertificates.get(domain);
738
+ if (!domainInfo) {
739
+ throw new CertificateError('Domain not found during authorization', domain);
740
+ }
741
+ for (const authz of authorizations) {
742
+ const challenge = authz.challenges.find(ch => ch.type === 'http-01');
743
+ if (!challenge) {
744
+ throw new CertificateError('HTTP-01 challenge not found', domain);
745
+ }
746
+ // Get the key authorization for the challenge
747
+ const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
748
+ // Store the challenge data
749
+ domainInfo.challengeToken = challenge.token;
750
+ domainInfo.challengeKeyAuthorization = keyAuthorization;
751
+ // ACME client type definition workaround - use compatible approach
752
+ // First check if challenge verification is needed
753
+ const authzUrl = authz.url;
754
+ try {
755
+ // Check if authzUrl exists and perform verification
756
+ if (authzUrl) {
757
+ await client.verifyChallenge(authz, challenge);
758
+ }
759
+ // Complete the challenge
760
+ await client.completeChallenge(challenge);
761
+ // Wait for validation
762
+ await client.waitForValidStatus(challenge);
763
+ console.log(`HTTP-01 challenge completed for ${domain}`);
764
+ }
765
+ catch (error) {
766
+ const errorMessage = error instanceof Error ? error.message : 'Unknown challenge error';
767
+ console.error(`Challenge error for ${domain}:`, error);
768
+ throw new CertificateError(`Challenge verification failed: ${errorMessage}`, domain);
769
+ }
770
+ }
771
+ }
772
+ /**
773
+ * Starts the certificate renewal timer
774
+ */
775
+ startRenewalTimer() {
776
+ if (this.renewalTimer) {
777
+ clearInterval(this.renewalTimer);
778
+ }
779
+ // Convert hours to milliseconds
780
+ const checkInterval = this.options.renewCheckIntervalHours * 60 * 60 * 1000;
781
+ this.renewalTimer = setInterval(() => this.checkForRenewals(), checkInterval);
782
+ // Prevent the timer from keeping the process alive
783
+ if (this.renewalTimer.unref) {
784
+ this.renewalTimer.unref();
785
+ }
786
+ console.log(`Certificate renewal check scheduled every ${this.options.renewCheckIntervalHours} hours`);
787
+ }
788
+ /**
789
+ * Checks for certificates that need renewal
790
+ */
791
+ checkForRenewals() {
792
+ if (this.isShuttingDown) {
793
+ return;
794
+ }
795
+ // Skip renewal if auto-renewal is disabled
796
+ if (this.options.autoRenew === false) {
797
+ console.log('Auto-renewal is disabled, skipping certificate renewal check');
798
+ return;
799
+ }
800
+ console.log('Checking for certificates that need renewal...');
801
+ const now = new Date();
802
+ const renewThresholdMs = this.options.renewThresholdDays * 24 * 60 * 60 * 1000;
803
+ for (const [domain, domainInfo] of this.domainCertificates.entries()) {
804
+ // Skip glob patterns
805
+ if (this.isGlobPattern(domain)) {
806
+ continue;
807
+ }
808
+ // Skip domains with acmeMaintenance disabled
809
+ if (!domainInfo.options.acmeMaintenance) {
810
+ continue;
811
+ }
812
+ // Skip domains without certificates or already in renewal
813
+ if (!domainInfo.certObtained || domainInfo.obtainingInProgress) {
814
+ continue;
815
+ }
816
+ // Skip domains without expiry dates
817
+ if (!domainInfo.expiryDate) {
818
+ continue;
819
+ }
820
+ const timeUntilExpiry = domainInfo.expiryDate.getTime() - now.getTime();
821
+ // Check if certificate is near expiry
822
+ if (timeUntilExpiry <= renewThresholdMs) {
823
+ console.log(`Certificate for ${domain} expires soon, renewing...`);
824
+ const daysRemaining = Math.ceil(timeUntilExpiry / (24 * 60 * 60 * 1000));
825
+ this.emit(Port80HandlerEvents.CERTIFICATE_EXPIRING, {
826
+ domain,
827
+ expiryDate: domainInfo.expiryDate,
828
+ daysRemaining
829
+ });
830
+ // Start renewal process
831
+ this.obtainCertificate(domain, true).catch(err => {
832
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
833
+ console.error(`Error renewing certificate for ${domain}:`, errorMessage);
834
+ });
835
+ }
836
+ }
837
+ }
838
+ /**
839
+ * Extract expiry date from certificate using a more robust approach
840
+ * @param certificate Certificate PEM string
841
+ * @param domain Domain for logging
842
+ * @returns Extracted expiry date or default
843
+ */
844
+ extractExpiryDateFromCertificate(certificate, domain) {
845
+ try {
846
+ // This is still using regex, but in a real implementation you would use
847
+ // a library like node-forge or x509 to properly parse the certificate
848
+ const matches = certificate.match(/Not After\s*:\s*(.*?)(?:\n|$)/i);
849
+ if (matches && matches[1]) {
850
+ const expiryDate = new Date(matches[1]);
851
+ // Validate that we got a valid date
852
+ if (!isNaN(expiryDate.getTime())) {
853
+ console.log(`Certificate for ${domain} will expire on ${expiryDate.toISOString()}`);
854
+ return expiryDate;
855
+ }
856
+ }
857
+ console.warn(`Could not extract valid expiry date from certificate for ${domain}, using default`);
858
+ return this.getDefaultExpiryDate();
859
+ }
860
+ catch (error) {
861
+ console.warn(`Failed to extract expiry date from certificate for ${domain}, using default`);
862
+ return this.getDefaultExpiryDate();
863
+ }
864
+ }
865
+ /**
866
+ * Get a default expiry date (90 days from now)
867
+ * @returns Default expiry date
868
+ */
869
+ getDefaultExpiryDate() {
870
+ return new Date(Date.now() + 90 * 24 * 60 * 60 * 1000); // 90 days default
871
+ }
872
+ /**
873
+ * Emits a certificate event with the certificate data
874
+ * @param eventType The event type to emit
875
+ * @param data The certificate data
876
+ */
877
+ emitCertificateEvent(eventType, data) {
878
+ this.emit(eventType, data);
879
+ }
880
+ /**
881
+ * Gets all domains and their certificate status
882
+ * @returns Map of domains to certificate status
883
+ */
884
+ getDomainCertificateStatus() {
885
+ const result = new Map();
886
+ const now = new Date();
887
+ for (const [domain, domainInfo] of this.domainCertificates.entries()) {
888
+ // Skip glob patterns
889
+ if (this.isGlobPattern(domain))
890
+ continue;
891
+ const status = {
892
+ certObtained: domainInfo.certObtained,
893
+ expiryDate: domainInfo.expiryDate,
894
+ obtainingInProgress: domainInfo.obtainingInProgress,
895
+ lastRenewalAttempt: domainInfo.lastRenewalAttempt
896
+ };
897
+ // Calculate days remaining if expiry date is available
898
+ if (domainInfo.expiryDate) {
899
+ const daysRemaining = Math.ceil((domainInfo.expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000));
900
+ status.daysRemaining = daysRemaining;
901
+ }
902
+ result.set(domain, status);
903
+ }
904
+ return result;
905
+ }
906
+ /**
907
+ * Gets information about managed domains
908
+ * @returns Array of domain information
909
+ */
910
+ getManagedDomains() {
911
+ return Array.from(this.domainCertificates.entries()).map(([domain, info]) => ({
912
+ domain,
913
+ isGlobPattern: this.isGlobPattern(domain),
914
+ hasCertificate: info.certObtained,
915
+ hasForwarding: !!info.options.forward,
916
+ sslRedirect: info.options.sslRedirect,
917
+ acmeMaintenance: info.options.acmeMaintenance
918
+ }));
919
+ }
920
+ /**
921
+ * Gets configuration details
922
+ * @returns Current configuration
923
+ */
924
+ getConfig() {
925
+ return { ...this.options };
926
+ }
927
+ }
928
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wb3J0ODBoYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvcG9ydDgwaGFuZGxlci9jbGFzc2VzLnBvcnQ4MGhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLGVBQWUsRUFBRSxjQUFjLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDdkQsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDekIsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFFN0I7O0dBRUc7QUFDSCxNQUFNLE9BQU8sa0JBQW1CLFNBQVEsS0FBSztJQUMzQyxZQUFZLE9BQWU7UUFDekIsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2YsSUFBSSxDQUFDLElBQUksR0FBRyxvQkFBb0IsQ0FBQztJQUNuQyxDQUFDO0NBQ0Y7QUFFRCxNQUFNLE9BQU8sZ0JBQWlCLFNBQVEsa0JBQWtCO0lBQ3RELFlBQ0UsT0FBZSxFQUNDLE1BQWMsRUFDZCxZQUFxQixLQUFLO1FBRTFDLEtBQUssQ0FBQyxHQUFHLE9BQU8sZUFBZSxNQUFNLEdBQUcsU0FBUyxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFIekQsV0FBTSxHQUFOLE1BQU0sQ0FBUTtRQUNkLGNBQVMsR0FBVCxTQUFTLENBQWlCO1FBRzFDLElBQUksQ0FBQyxJQUFJLEdBQUcsa0JBQWtCLENBQUM7SUFDakMsQ0FBQztDQUNGO0FBRUQsTUFBTSxPQUFPLFdBQVksU0FBUSxrQkFBa0I7SUFDakQsWUFBWSxPQUFlLEVBQWtCLElBQWE7UUFDeEQsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRDRCLFNBQUksR0FBSixJQUFJLENBQVM7UUFFeEQsSUFBSSxDQUFDLElBQUksR0FBRyxhQUFhLENBQUM7SUFDNUIsQ0FBQztDQUNG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