import * as plugins from '../../plugins.js'; import type { IAcmeOptions, ICertificateData } from '../models/certificate-types.js'; import { CertificateEvents } from '../events/certificate-events.js'; /** * Manages ACME challenges and certificate validation */ export class AcmeChallengeHandler extends plugins.EventEmitter { private options: IAcmeOptions; private client: any; // ACME client from plugins private pendingChallenges: Map; /** * Creates a new ACME challenge handler * @param options ACME configuration options */ constructor(options: IAcmeOptions) { super(); this.options = options; this.pendingChallenges = new Map(); // Initialize ACME client if needed // This is just a placeholder implementation since we don't use the actual // client directly in this implementation - it's handled by Port80Handler this.client = null; console.log('Created challenge handler with options:', options.accountEmail, options.useProduction ? 'production' : 'staging' ); } /** * Gets or creates the ACME account key */ private getAccountKey(): Buffer { // Implementation details would depend on plugin requirements // This is a simplified version if (!this.options.certificateStore) { throw new Error('Certificate store is required for ACME challenges'); } // This is just a placeholder - actual implementation would check for // existing account key and create one if needed return Buffer.from('account-key-placeholder'); } /** * Validates a domain using HTTP-01 challenge * @param domain Domain to validate * @param challengeToken ACME challenge token * @param keyAuthorization Key authorization for the challenge */ public async handleHttpChallenge( domain: string, challengeToken: string, keyAuthorization: string ): Promise { // Store challenge for response this.pendingChallenges.set(challengeToken, keyAuthorization); try { // Wait for challenge validation - this would normally be handled by the ACME client await new Promise(resolve => setTimeout(resolve, 1000)); this.emit(CertificateEvents.CERTIFICATE_ISSUED, { domain, success: true }); } catch (error) { this.emit(CertificateEvents.CERTIFICATE_FAILED, { domain, error: error instanceof Error ? error.message : String(error), isRenewal: false }); throw error; } finally { // Clean up the challenge this.pendingChallenges.delete(challengeToken); } } /** * Responds to an HTTP-01 challenge request * @param token Challenge token from the request path * @returns The key authorization if found */ public getChallengeResponse(token: string): string | null { return this.pendingChallenges.get(token) || null; } /** * Checks if a request path is an ACME challenge * @param path Request path * @returns True if this is an ACME challenge request */ public isAcmeChallenge(path: string): boolean { return path.startsWith('/.well-known/acme-challenge/'); } /** * Extracts the challenge token from an ACME challenge path * @param path Request path * @returns The challenge token if valid */ public extractChallengeToken(path: string): string | null { if (!this.isAcmeChallenge(path)) return null; const parts = path.split('/'); return parts[parts.length - 1] || null; } }