From 05c915645860e9a5dfd60e46fa29bbb64fa368b2 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Mon, 5 May 2025 15:16:26 +0000 Subject: [PATCH] fix(port80handler): refactor ACME challenge handling to use dedicated Http01MemoryHandler, remove obsolete readme.plan.md, and update version to 10.0.12 --- changelog.md | 18 +++++++++++++ readme.plan.md | 29 --------------------- ts/00_commitinfo_data.ts | 2 +- ts/port80handler/classes.port80handler.ts | 24 +++++++---------- ts/smartproxy/classes.pp.certprovisioner.ts | 24 ++++++++++++----- ts/smartproxy/classes.smartproxy.ts | 12 +++++++-- 6 files changed, 57 insertions(+), 52 deletions(-) diff --git a/changelog.md b/changelog.md index 15ad5e2..2f00b97 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,23 @@ # Changelog +## 2025-05-05 - 10.0.12 - fix(port80handler) +refactor ACME challenge handling to use dedicated Http01MemoryHandler, remove obsolete readme.plan.md, and update version to 10.0.12 + +- Removed readme.plan.md planning document +- Eliminated internal acmeHttp01Storage from Port80Handler +- Instantiated and integrated Http01MemoryHandler as a class property for managing HTTP-01 challenges +- Delegated ACME HTTP-01 challenge responses to smartAcmeHttp01Handler +- Updated ts/00_commitinfo_data.ts version from 10.0.11 to 10.0.12 +- Adjusted certificate provisioning logic to properly handle wildcard domains and on-demand requests + +## 2025-05-05 - 10.0.12 - fix(port80handler) +Remove obsolete readme.plan.md and refactor Port80Handler's ACME challenge handling to use a dedicated Http01MemoryHandler + +- Deleted readme.plan.md planning document which was no longer needed +- Removed internal acmeHttp01Storage map from Port80Handler +- Instantiated Http01MemoryHandler as a class property and provided it to SmartAcme for challenge handling +- Delegated ACME HTTP-01 challenge responses to the new smartAcmeHttp01Handler instead of in-memory storage + ## 2025-05-05 - 10.0.11 - fix(dependencies) Bump @push.rocks/smartacme to ^7.2.5 and @tsclass/tsclass to ^9.2.0; update MemoryCertManager import to use plugins.smartacme.certmanagers.MemoryCertManager() diff --git a/readme.plan.md b/readme.plan.md index cef94a1..e69de29 100644 --- a/readme.plan.md +++ b/readme.plan.md @@ -1,29 +0,0 @@ -# Project Simplification Plan - -This document outlines a roadmap to simplify and refactor the SmartProxy & NetworkProxy codebase for better maintainability, reduced duplication, and clearer configuration. - -## Goals -- Eliminate duplicate code and shared types -- Unify certificate management flow across components -- Simplify configuration schemas and option handling -- Centralize plugin imports and module interfaces -- Strengthen type safety and linting -- Improve test coverage and CI integration - -## Plan -- [x] Extract all shared interfaces and types (e.g., certificate, proxy, domain configs) into a common `ts/common` module -- [x] Consolidate ACME/Port80Handler logic: - - [x] Merge standalone Port80Handler into a single certificate service - - [x] Remove duplicate ACME setup in SmartProxy and NetworkProxy -- [x] Unify configuration options: - - [x] Merge `INetworkProxyOptions.acme`, `IPort80HandlerOptions`, and `port80HandlerConfig` into one schema - - [x] Deprecate old option names and provide clear upgrade path -- [x] Centralize plugin imports in `ts/plugins.ts` and update all modules to use it -- [x] Remove legacy or unused code paths (e.g., old HTTP/2 fallback logic if obsolete) -- [ ] Enhance and expand test coverage: - - Add unit tests for certificate issuance, renewal, and error handling - - Add integration tests for HTTP challenge routing and request forwarding -- [ ] Update main README.md with architecture overview and configuration guide -- [ ] Review and prune external dependencies no longer needed - -Once these steps are complete, the project will be cleaner, easier to understand, and simpler to extend. \ No newline at end of file diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 69d29fb..08a630a 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '10.0.11', + version: '10.0.12', 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.' } diff --git a/ts/port80handler/classes.port80handler.ts b/ts/port80handler/classes.port80handler.ts index 5f55554..d08b66e 100644 --- a/ts/port80handler/classes.port80handler.ts +++ b/ts/port80handler/classes.port80handler.ts @@ -65,11 +65,11 @@ interface IDomainCertificate { */ export class Port80Handler extends plugins.EventEmitter { private domainCertificates: Map; - // In-memory storage for ACME HTTP-01 challenge tokens - private acmeHttp01Storage: Map = new Map(); // SmartAcme instance for certificate management private smartAcme: plugins.smartacme.SmartAcme | null = null; + private smartAcmeHttp01Handler!: plugins.smartacme.handlers.Http01MemoryHandler; private server: plugins.http.Server | null = null; + // Renewal scheduling is handled externally by SmartProxy // (Removed internal renewal timer) private isShuttingDown: boolean = false; @@ -116,13 +116,14 @@ export class Port80Handler extends plugins.EventEmitter { console.log('Port80Handler is disabled, skipping start'); return; } - // Initialize SmartAcme for ACME challenge management (diskless HTTP handler) + // Initialize SmartAcme with in-memory HTTP-01 challenge handler if (this.options.enabled) { + this.smartAcmeHttp01Handler = new plugins.smartacme.handlers.Http01MemoryHandler(); this.smartAcme = new plugins.smartacme.SmartAcme({ accountEmail: this.options.accountEmail, certManager: new plugins.smartacme.certmanagers.MemoryCertManager(), environment: this.options.useProduction ? 'production' : 'integration', - challengeHandlers: [ new plugins.smartacme.handlers.Http01MemoryHandler() ], + challengeHandlers: [ this.smartAcmeHttp01Handler ], challengePriority: ['http-01'], }); await this.smartAcme.start(); @@ -433,17 +434,12 @@ export class Port80Handler extends plugins.EventEmitter { res.end('Not found'); return; } - // Serve challenge response from in-memory storage - const token = req.url.split('/').pop() || ''; - const keyAuth = this.acmeHttp01Storage.get(token); - if (keyAuth) { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end(keyAuth); - console.log(`Served ACME challenge response for ${domain}`); + // Delegate to Http01MemoryHandler + if (this.smartAcmeHttp01Handler) { + this.smartAcmeHttp01Handler.handleRequest(req, res); } else { - res.statusCode = 404; - res.end('Challenge token not found'); + res.statusCode = 500; + res.end('ACME HTTP-01 handler not initialized'); } return; } diff --git a/ts/smartproxy/classes.pp.certprovisioner.ts b/ts/smartproxy/classes.pp.certprovisioner.ts index ba4f0fe..dd1a027 100644 --- a/ts/smartproxy/classes.pp.certprovisioner.ts +++ b/ts/smartproxy/classes.pp.certprovisioner.ts @@ -81,8 +81,7 @@ export class CertProvisioner extends plugins.EventEmitter { // Initial provisioning for all domains const domains = this.domainConfigs.flatMap(cfg => cfg.domains); for (const domain of domains) { - // Skip wildcard domains - if (domain.includes('*')) continue; + const isWildcard = domain.includes('*'); let provision: ISmartProxyCertProvisionObject | 'http01' = 'http01'; if (this.certProvider) { try { @@ -90,11 +89,20 @@ export class CertProvisioner extends plugins.EventEmitter { } catch (err) { console.error(`certProvider error for ${domain}:`, err); } + } else if (isWildcard) { + // No certProvider: cannot handle wildcard without DNS-01 support + console.warn(`Skipping wildcard domain without certProvisionFunction: ${domain}`); + continue; } if (provision === 'http01') { + if (isWildcard) { + console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`); + continue; + } this.provisionMap.set(domain, 'http01'); this.port80Handler.addDomain({ domainName: domain, sslRedirect: true, acmeMaintenance: true }); } else { + // Static certificate (e.g., DNS-01 provisioned or user-provided) supports wildcard domains this.provisionMap.set(domain, 'static'); const certObj = provision as plugins.tsclass.network.ICert; const certData: ICertificateData = { @@ -162,18 +170,22 @@ export class CertProvisioner extends plugins.EventEmitter { * @param domain Domain name to provision */ public async requestCertificate(domain: string): Promise { - // Skip wildcard domains - if (domain.includes('*')) { - throw new Error(`Cannot request certificate for wildcard domain: ${domain}`); - } + const isWildcard = domain.includes('*'); // Determine provisioning method let provision: ISmartProxyCertProvisionObject | 'http01' = 'http01'; if (this.certProvider) { provision = await this.certProvider(domain); + } else if (isWildcard) { + // Cannot perform HTTP-01 on wildcard without certProvider + throw new Error(`Cannot request certificate for wildcard domain without certProvisionFunction: ${domain}`); } if (provision === 'http01') { + if (isWildcard) { + throw new Error(`Cannot request HTTP-01 certificate for wildcard domain: ${domain}`); + } await this.port80Handler.renewCertificate(domain); } else { + // Static certificate (e.g., DNS-01 provisioned) supports wildcards const certObj = provision as plugins.tsclass.network.ICert; const certData: ICertificateData = { domain: certObj.domainName, diff --git a/ts/smartproxy/classes.smartproxy.ts b/ts/smartproxy/classes.smartproxy.ts index 794a4d5..ed8bbac 100644 --- a/ts/smartproxy/classes.smartproxy.ts +++ b/ts/smartproxy/classes.smartproxy.ts @@ -391,16 +391,23 @@ export class SmartProxy extends plugins.EventEmitter { if (this.port80Handler && this.settings.acme?.enabled) { for (const domainConfig of newDomainConfigs) { for (const domain of domainConfig.domains) { - if (domain.includes('*')) continue; - let provision = 'http01' as string | plugins.tsclass.network.ICert; + const isWildcard = domain.includes('*'); + let provision: string | plugins.tsclass.network.ICert = 'http01'; if (this.settings.certProvisionFunction) { try { provision = await this.settings.certProvisionFunction(domain); } catch (err) { console.log(`certProvider error for ${domain}: ${err}`); } + } else if (isWildcard) { + console.warn(`Skipping wildcard domain without certProvisionFunction: ${domain}`); + continue; } if (provision === 'http01') { + if (isWildcard) { + console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`); + continue; + } this.port80Handler.addDomain({ domainName: domain, sslRedirect: true, @@ -408,6 +415,7 @@ export class SmartProxy extends plugins.EventEmitter { }); console.log(`Registered domain ${domain} with Port80Handler for HTTP-01`); } else { + // Static certificate (e.g., DNS-01 provisioned) supports wildcards const certObj = provision as plugins.tsclass.network.ICert; const certData: ICertificateData = { domain: certObj.domainName,