fix(port80handler): refactor ACME challenge handling to use dedicated Http01MemoryHandler, remove obsolete readme.plan.md, and update version to 10.0.12
This commit is contained in:
parent
47e3c86487
commit
05c9156458
18
changelog.md
18
changelog.md
@ -1,5 +1,23 @@
|
|||||||
# Changelog
|
# 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)
|
## 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()
|
Bump @push.rocks/smartacme to ^7.2.5 and @tsclass/tsclass to ^9.2.0; update MemoryCertManager import to use plugins.smartacme.certmanagers.MemoryCertManager()
|
||||||
|
|
||||||
|
@ -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.
|
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
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.'
|
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.'
|
||||||
}
|
}
|
||||||
|
@ -65,11 +65,11 @@ interface IDomainCertificate {
|
|||||||
*/
|
*/
|
||||||
export class Port80Handler extends plugins.EventEmitter {
|
export class Port80Handler extends plugins.EventEmitter {
|
||||||
private domainCertificates: Map<string, IDomainCertificate>;
|
private domainCertificates: Map<string, IDomainCertificate>;
|
||||||
// In-memory storage for ACME HTTP-01 challenge tokens
|
|
||||||
private acmeHttp01Storage: Map<string, string> = new Map();
|
|
||||||
// SmartAcme instance for certificate management
|
// SmartAcme instance for certificate management
|
||||||
private smartAcme: plugins.smartacme.SmartAcme | null = null;
|
private smartAcme: plugins.smartacme.SmartAcme | null = null;
|
||||||
|
private smartAcmeHttp01Handler!: plugins.smartacme.handlers.Http01MemoryHandler;
|
||||||
private server: plugins.http.Server | null = null;
|
private server: plugins.http.Server | null = null;
|
||||||
|
|
||||||
// Renewal scheduling is handled externally by SmartProxy
|
// Renewal scheduling is handled externally by SmartProxy
|
||||||
// (Removed internal renewal timer)
|
// (Removed internal renewal timer)
|
||||||
private isShuttingDown: boolean = false;
|
private isShuttingDown: boolean = false;
|
||||||
@ -116,13 +116,14 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
console.log('Port80Handler is disabled, skipping start');
|
console.log('Port80Handler is disabled, skipping start');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Initialize SmartAcme for ACME challenge management (diskless HTTP handler)
|
// Initialize SmartAcme with in-memory HTTP-01 challenge handler
|
||||||
if (this.options.enabled) {
|
if (this.options.enabled) {
|
||||||
|
this.smartAcmeHttp01Handler = new plugins.smartacme.handlers.Http01MemoryHandler();
|
||||||
this.smartAcme = new plugins.smartacme.SmartAcme({
|
this.smartAcme = new plugins.smartacme.SmartAcme({
|
||||||
accountEmail: this.options.accountEmail,
|
accountEmail: this.options.accountEmail,
|
||||||
certManager: new plugins.smartacme.certmanagers.MemoryCertManager(),
|
certManager: new plugins.smartacme.certmanagers.MemoryCertManager(),
|
||||||
environment: this.options.useProduction ? 'production' : 'integration',
|
environment: this.options.useProduction ? 'production' : 'integration',
|
||||||
challengeHandlers: [ new plugins.smartacme.handlers.Http01MemoryHandler() ],
|
challengeHandlers: [ this.smartAcmeHttp01Handler ],
|
||||||
challengePriority: ['http-01'],
|
challengePriority: ['http-01'],
|
||||||
});
|
});
|
||||||
await this.smartAcme.start();
|
await this.smartAcme.start();
|
||||||
@ -433,17 +434,12 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
res.end('Not found');
|
res.end('Not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Serve challenge response from in-memory storage
|
// Delegate to Http01MemoryHandler
|
||||||
const token = req.url.split('/').pop() || '';
|
if (this.smartAcmeHttp01Handler) {
|
||||||
const keyAuth = this.acmeHttp01Storage.get(token);
|
this.smartAcmeHttp01Handler.handleRequest(req, res);
|
||||||
if (keyAuth) {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.end(keyAuth);
|
|
||||||
console.log(`Served ACME challenge response for ${domain}`);
|
|
||||||
} else {
|
} else {
|
||||||
res.statusCode = 404;
|
res.statusCode = 500;
|
||||||
res.end('Challenge token not found');
|
res.end('ACME HTTP-01 handler not initialized');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,7 @@ export class CertProvisioner extends plugins.EventEmitter {
|
|||||||
// Initial provisioning for all domains
|
// Initial provisioning for all domains
|
||||||
const domains = this.domainConfigs.flatMap(cfg => cfg.domains);
|
const domains = this.domainConfigs.flatMap(cfg => cfg.domains);
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
// Skip wildcard domains
|
const isWildcard = domain.includes('*');
|
||||||
if (domain.includes('*')) continue;
|
|
||||||
let provision: ISmartProxyCertProvisionObject | 'http01' = 'http01';
|
let provision: ISmartProxyCertProvisionObject | 'http01' = 'http01';
|
||||||
if (this.certProvider) {
|
if (this.certProvider) {
|
||||||
try {
|
try {
|
||||||
@ -90,11 +89,20 @@ export class CertProvisioner extends plugins.EventEmitter {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`certProvider error for ${domain}:`, 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 (provision === 'http01') {
|
||||||
|
if (isWildcard) {
|
||||||
|
console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.provisionMap.set(domain, 'http01');
|
this.provisionMap.set(domain, 'http01');
|
||||||
this.port80Handler.addDomain({ domainName: domain, sslRedirect: true, acmeMaintenance: true });
|
this.port80Handler.addDomain({ domainName: domain, sslRedirect: true, acmeMaintenance: true });
|
||||||
} else {
|
} else {
|
||||||
|
// Static certificate (e.g., DNS-01 provisioned or user-provided) supports wildcard domains
|
||||||
this.provisionMap.set(domain, 'static');
|
this.provisionMap.set(domain, 'static');
|
||||||
const certObj = provision as plugins.tsclass.network.ICert;
|
const certObj = provision as plugins.tsclass.network.ICert;
|
||||||
const certData: ICertificateData = {
|
const certData: ICertificateData = {
|
||||||
@ -162,18 +170,22 @@ export class CertProvisioner extends plugins.EventEmitter {
|
|||||||
* @param domain Domain name to provision
|
* @param domain Domain name to provision
|
||||||
*/
|
*/
|
||||||
public async requestCertificate(domain: string): Promise<void> {
|
public async requestCertificate(domain: string): Promise<void> {
|
||||||
// Skip wildcard domains
|
const isWildcard = domain.includes('*');
|
||||||
if (domain.includes('*')) {
|
|
||||||
throw new Error(`Cannot request certificate for wildcard domain: ${domain}`);
|
|
||||||
}
|
|
||||||
// Determine provisioning method
|
// Determine provisioning method
|
||||||
let provision: ISmartProxyCertProvisionObject | 'http01' = 'http01';
|
let provision: ISmartProxyCertProvisionObject | 'http01' = 'http01';
|
||||||
if (this.certProvider) {
|
if (this.certProvider) {
|
||||||
provision = await this.certProvider(domain);
|
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 (provision === 'http01') {
|
||||||
|
if (isWildcard) {
|
||||||
|
throw new Error(`Cannot request HTTP-01 certificate for wildcard domain: ${domain}`);
|
||||||
|
}
|
||||||
await this.port80Handler.renewCertificate(domain);
|
await this.port80Handler.renewCertificate(domain);
|
||||||
} else {
|
} else {
|
||||||
|
// Static certificate (e.g., DNS-01 provisioned) supports wildcards
|
||||||
const certObj = provision as plugins.tsclass.network.ICert;
|
const certObj = provision as plugins.tsclass.network.ICert;
|
||||||
const certData: ICertificateData = {
|
const certData: ICertificateData = {
|
||||||
domain: certObj.domainName,
|
domain: certObj.domainName,
|
||||||
|
@ -391,16 +391,23 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
if (this.port80Handler && this.settings.acme?.enabled) {
|
if (this.port80Handler && this.settings.acme?.enabled) {
|
||||||
for (const domainConfig of newDomainConfigs) {
|
for (const domainConfig of newDomainConfigs) {
|
||||||
for (const domain of domainConfig.domains) {
|
for (const domain of domainConfig.domains) {
|
||||||
if (domain.includes('*')) continue;
|
const isWildcard = domain.includes('*');
|
||||||
let provision = 'http01' as string | plugins.tsclass.network.ICert;
|
let provision: string | plugins.tsclass.network.ICert = 'http01';
|
||||||
if (this.settings.certProvisionFunction) {
|
if (this.settings.certProvisionFunction) {
|
||||||
try {
|
try {
|
||||||
provision = await this.settings.certProvisionFunction(domain);
|
provision = await this.settings.certProvisionFunction(domain);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`certProvider error for ${domain}: ${err}`);
|
console.log(`certProvider error for ${domain}: ${err}`);
|
||||||
}
|
}
|
||||||
|
} else if (isWildcard) {
|
||||||
|
console.warn(`Skipping wildcard domain without certProvisionFunction: ${domain}`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (provision === 'http01') {
|
if (provision === 'http01') {
|
||||||
|
if (isWildcard) {
|
||||||
|
console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.port80Handler.addDomain({
|
this.port80Handler.addDomain({
|
||||||
domainName: domain,
|
domainName: domain,
|
||||||
sslRedirect: true,
|
sslRedirect: true,
|
||||||
@ -408,6 +415,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
});
|
});
|
||||||
console.log(`Registered domain ${domain} with Port80Handler for HTTP-01`);
|
console.log(`Registered domain ${domain} with Port80Handler for HTTP-01`);
|
||||||
} else {
|
} else {
|
||||||
|
// Static certificate (e.g., DNS-01 provisioned) supports wildcards
|
||||||
const certObj = provision as plugins.tsclass.network.ICert;
|
const certObj = provision as plugins.tsclass.network.ICert;
|
||||||
const certData: ICertificateData = {
|
const certData: ICertificateData = {
|
||||||
domain: certObj.domainName,
|
domain: certObj.domainName,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user