feat(smartproxy): Migrate internal module paths and update HTTP/ACME components for SmartProxy
This commit is contained in:
@ -35,58 +35,92 @@ export class ChallengeResponder extends plugins.EventEmitter {
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
try {
|
||||
// Initialize SmartAcme
|
||||
this.smartAcme = new plugins.smartacme.SmartAcme({
|
||||
useProduction: this.useProduction,
|
||||
accountEmail: this.email,
|
||||
directoryUrl: this.useProduction
|
||||
? 'https://acme-v02.api.letsencrypt.org/directory' // Production
|
||||
: 'https://acme-staging-v02.api.letsencrypt.org/directory', // Staging
|
||||
});
|
||||
|
||||
// Initialize HTTP-01 challenge handler
|
||||
this.http01Handler = new plugins.smartacme.handlers.Http01MemoryHandler();
|
||||
this.smartAcme.useHttpChallenge(this.http01Handler);
|
||||
|
||||
// Initialize SmartAcme with proper options
|
||||
this.smartAcme = new plugins.smartacme.SmartAcme({
|
||||
accountEmail: this.email,
|
||||
certManager: new plugins.smartacme.certmanagers.MemoryCertManager(),
|
||||
environment: this.useProduction ? 'production' : 'integration',
|
||||
challengeHandlers: [this.http01Handler],
|
||||
challengePriority: ['http-01'],
|
||||
});
|
||||
|
||||
// Ensure certificate store directory exists
|
||||
await this.ensureCertificateStore();
|
||||
|
||||
// Subscribe to SmartAcme events
|
||||
this.smartAcme.on('certificate-issued', (data: any) => {
|
||||
const certData: CertificateData = {
|
||||
domain: data.domain,
|
||||
certificate: data.cert,
|
||||
privateKey: data.key,
|
||||
expiryDate: new Date(data.expiryDate),
|
||||
};
|
||||
this.emit(CertificateEvents.CERTIFICATE_ISSUED, certData);
|
||||
});
|
||||
|
||||
this.smartAcme.on('certificate-renewed', (data: any) => {
|
||||
const certData: CertificateData = {
|
||||
domain: data.domain,
|
||||
certificate: data.cert,
|
||||
privateKey: data.key,
|
||||
expiryDate: new Date(data.expiryDate),
|
||||
};
|
||||
this.emit(CertificateEvents.CERTIFICATE_RENEWED, certData);
|
||||
});
|
||||
|
||||
this.smartAcme.on('certificate-error', (data: any) => {
|
||||
const error: CertificateFailure = {
|
||||
domain: data.domain,
|
||||
error: data.error instanceof Error ? data.error.message : String(data.error),
|
||||
isRenewal: data.isRenewal || false,
|
||||
};
|
||||
this.emit(CertificateEvents.CERTIFICATE_FAILED, error);
|
||||
});
|
||||
|
||||
await this.smartAcme.initialize();
|
||||
// Set up event forwarding from SmartAcme
|
||||
this.setupEventForwarding();
|
||||
|
||||
// Start SmartAcme
|
||||
await this.smartAcme.start();
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to initialize ACME client: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up event forwarding from SmartAcme to this component
|
||||
*/
|
||||
private setupEventForwarding(): void {
|
||||
if (!this.smartAcme) return;
|
||||
|
||||
// Cast smartAcme to any since different versions have different event APIs
|
||||
const smartAcmeAny = this.smartAcme as any;
|
||||
|
||||
// Forward certificate events to our own emitter
|
||||
if (typeof smartAcmeAny.on === 'function') {
|
||||
smartAcmeAny.on('certificate', (data: any) => {
|
||||
const certData: CertificateData = {
|
||||
domain: data.domain,
|
||||
certificate: data.cert || data.publicKey,
|
||||
privateKey: data.key || data.privateKey,
|
||||
expiryDate: new Date(data.expiryDate || data.validUntil),
|
||||
source: 'http01'
|
||||
};
|
||||
// Emit as issued or renewed based on the renewal flag
|
||||
const eventType = data.isRenewal
|
||||
? CertificateEvents.CERTIFICATE_RENEWED
|
||||
: CertificateEvents.CERTIFICATE_ISSUED;
|
||||
this.emit(eventType, certData);
|
||||
});
|
||||
|
||||
smartAcmeAny.on('error', (data: any) => {
|
||||
const failure: CertificateFailure = {
|
||||
domain: data.domain || 'unknown',
|
||||
error: data.message || data.toString(),
|
||||
isRenewal: false
|
||||
};
|
||||
this.emit(CertificateEvents.CERTIFICATE_FAILED, failure);
|
||||
});
|
||||
} else if (smartAcmeAny.eventEmitter && typeof smartAcmeAny.eventEmitter.on === 'function') {
|
||||
// Alternative event emitter approach for newer versions
|
||||
smartAcmeAny.eventEmitter.on('certificate', (data: any) => {
|
||||
const certData: CertificateData = {
|
||||
domain: data.domain,
|
||||
certificate: data.cert || data.publicKey,
|
||||
privateKey: data.key || data.privateKey,
|
||||
expiryDate: new Date(data.expiryDate || data.validUntil),
|
||||
source: 'http01'
|
||||
};
|
||||
const eventType = data.isRenewal
|
||||
? CertificateEvents.CERTIFICATE_RENEWED
|
||||
: CertificateEvents.CERTIFICATE_ISSUED;
|
||||
this.emit(eventType, certData);
|
||||
});
|
||||
|
||||
smartAcmeAny.eventEmitter.on('error', (data: any) => {
|
||||
const failure: CertificateFailure = {
|
||||
domain: data.domain || 'unknown',
|
||||
error: data.message || data.toString(),
|
||||
isRenewal: false
|
||||
};
|
||||
this.emit(CertificateEvents.CERTIFICATE_FAILED, failure);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure certificate store directory exists
|
||||
*/
|
||||
@ -110,32 +144,84 @@ export class ChallengeResponder extends plugins.EventEmitter {
|
||||
}
|
||||
|
||||
const url = req.url || '/';
|
||||
|
||||
|
||||
// Check if this is an ACME challenge request
|
||||
if (url.startsWith('/.well-known/acme-challenge/')) {
|
||||
const token = url.split('/').pop() || '';
|
||||
|
||||
if (token) {
|
||||
const response = this.http01Handler.getResponse(token);
|
||||
|
||||
if (response) {
|
||||
// This is a valid ACME challenge
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
||||
res.writeHead(200);
|
||||
res.end(response);
|
||||
return true;
|
||||
|
||||
if (token && this.http01Handler) {
|
||||
try {
|
||||
// Try to delegate to the handler - casting to any for flexibility
|
||||
const handler = this.http01Handler as any;
|
||||
|
||||
// Different versions may have different handler methods
|
||||
if (typeof handler.handleChallenge === 'function') {
|
||||
handler.handleChallenge(req, res);
|
||||
return true;
|
||||
} else if (typeof handler.handleRequest === 'function') {
|
||||
// Some versions use handleRequest instead
|
||||
handler.handleRequest(req, res);
|
||||
return true;
|
||||
} else {
|
||||
// Fall back to manual response
|
||||
const resp = this.getTokenResponse(token);
|
||||
if (resp) {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
||||
res.writeHead(200);
|
||||
res.end(resp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Challenge not found
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Invalid ACME challenge
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response for a specific token if available
|
||||
* This is a fallback method in case direct handler access isn't available
|
||||
*/
|
||||
private getTokenResponse(token: string): string | null {
|
||||
if (!this.http01Handler) return null;
|
||||
|
||||
try {
|
||||
// Cast to any to handle different versions of the API
|
||||
const handler = this.http01Handler as any;
|
||||
|
||||
// Try different methods that might be available in different versions
|
||||
if (typeof handler.getResponse === 'function') {
|
||||
return handler.getResponse(token);
|
||||
}
|
||||
|
||||
if (typeof handler.getChallengeVerification === 'function') {
|
||||
return handler.getChallengeVerification(token);
|
||||
}
|
||||
|
||||
// Try to access the challenges directly from the handler's internal state
|
||||
if (handler.challenges && typeof handler.challenges === 'object' && handler.challenges[token]) {
|
||||
return handler.challenges[token];
|
||||
}
|
||||
|
||||
// Try the token map if it exists (another common pattern)
|
||||
if (handler.tokenMap && typeof handler.tokenMap === 'object' && handler.tokenMap[token]) {
|
||||
return handler.tokenMap[token];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error getting token response:', err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a certificate for a domain
|
||||
@ -148,16 +234,20 @@ export class ChallengeResponder extends plugins.EventEmitter {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.smartAcme.getCertificate(domain);
|
||||
// Request certificate via SmartAcme
|
||||
const certObj = await this.smartAcme.getCertificateForDomain(domain);
|
||||
|
||||
const certData: CertificateData = {
|
||||
domain,
|
||||
certificate: result.cert,
|
||||
privateKey: result.key,
|
||||
expiryDate: new Date(result.expiryDate),
|
||||
certificate: certObj.publicKey,
|
||||
privateKey: certObj.privateKey,
|
||||
expiryDate: new Date(certObj.validUntil),
|
||||
source: 'http01',
|
||||
isRenewal
|
||||
};
|
||||
|
||||
// Emit appropriate event
|
||||
// SmartACME will emit its own events, but we'll emit our own too
|
||||
// for consistency with the rest of the system
|
||||
if (isRenewal) {
|
||||
this.emit(CertificateEvents.CERTIFICATE_RENEWED, certData);
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user