110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
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<string, any>;
|
|
|
|
/**
|
|
* 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<void> {
|
|
// 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;
|
|
}
|
|
} |