import type { IPortProxySettings } from './classes.pp.interfaces.js'; import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js'; /** * Manages ACME certificate operations */ export class AcmeManager { constructor( private settings: IPortProxySettings, private networkProxyBridge: NetworkProxyBridge ) {} /** * Get current ACME settings */ public getAcmeSettings(): IPortProxySettings['acme'] { return this.settings.acme; } /** * Check if ACME is enabled */ public isAcmeEnabled(): boolean { return !!this.settings.acme?.enabled; } /** * Update ACME certificate settings */ public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise { console.log('Updating ACME certificate settings'); // Check if enabled state is changing const enabledChanging = this.settings.acme?.enabled !== acmeSettings.enabled; // Update settings this.settings.acme = { ...this.settings.acme, ...acmeSettings, }; // Get NetworkProxy instance const networkProxy = this.networkProxyBridge.getNetworkProxy(); if (!networkProxy) { console.log('Cannot update ACME settings - NetworkProxy not initialized'); return; } try { // If enabled state changed, we need to restart NetworkProxy if (enabledChanging) { console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`); // Stop the current NetworkProxy await this.networkProxyBridge.stop(); // Reinitialize with new settings await this.networkProxyBridge.initialize(); // Start NetworkProxy with new settings await this.networkProxyBridge.start(); } else { // Just update the settings in the existing NetworkProxy console.log('Updating ACME settings in NetworkProxy without restart'); // Update settings in NetworkProxy if (networkProxy.options && networkProxy.options.acme) { networkProxy.options.acme = { ...this.settings.acme }; // For certificate renewals, we might want to trigger checks with the new settings if (acmeSettings.renewThresholdDays !== undefined) { console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`); networkProxy.options.acme.renewThresholdDays = acmeSettings.renewThresholdDays; } // Update other settings that might affect certificate operations if (acmeSettings.useProduction !== undefined) { console.log(`Setting ACME to ${acmeSettings.useProduction ? 'production' : 'staging'} mode`); } if (acmeSettings.autoRenew !== undefined) { console.log(`Setting auto-renewal to ${acmeSettings.autoRenew ? 'enabled' : 'disabled'}`); } } } } catch (err) { console.log(`Error updating ACME settings: ${err}`); } } /** * Request a certificate for a specific domain */ public async requestCertificate(domain: string): Promise { // Validate domain format if (!this.isValidDomain(domain)) { console.log(`Invalid domain format: ${domain}`); return false; } // Delegate to NetworkProxyManager return this.networkProxyBridge.requestCertificate(domain); } /** * Basic domain validation */ private isValidDomain(domain: string): boolean { // Very basic domain validation if (!domain || domain.length === 0) { return false; } // Check for wildcard domains (they can't get ACME certs) if (domain.includes('*')) { console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`); return false; } // Check if domain has at least one dot and no invalid characters 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])?)*$/; if (!validDomainRegex.test(domain)) { console.log(`Domain "${domain}" has invalid format`); return false; } return true; } /** * Get eligible domains for ACME certificates */ public getEligibleDomains(): string[] { // Collect all eligible domains from domain configs const domains: string[] = []; for (const config of this.settings.domainConfigs) { // Skip domains that can't be used with ACME const eligibleDomains = config.domains.filter(domain => !domain.includes('*') && this.isValidDomain(domain) ); domains.push(...eligibleDomains); } return domains; } }