update structure
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { IAcmeOptions } from './types.js';
|
||||
import { Port80Handler } from '../port80handler/classes.port80handler.js';
|
||||
import type { AcmeOptions } from '../models/certificate-types.js';
|
||||
import { ensureCertificateDirectory } from '../utils/certificate-helpers.js';
|
||||
// We'll need to update this import when we move the Port80Handler
|
||||
import { Port80Handler } from '../../port80handler/classes.port80handler.js';
|
||||
|
||||
/**
|
||||
* Factory to create a Port80Handler with common setup.
|
||||
@ -10,14 +12,37 @@ import { Port80Handler } from '../port80handler/classes.port80handler.js';
|
||||
* @returns A new Port80Handler instance
|
||||
*/
|
||||
export function buildPort80Handler(
|
||||
options: IAcmeOptions
|
||||
options: AcmeOptions
|
||||
): Port80Handler {
|
||||
if (options.certificateStore) {
|
||||
const certStorePath = path.resolve(options.certificateStore);
|
||||
if (!fs.existsSync(certStorePath)) {
|
||||
fs.mkdirSync(certStorePath, { recursive: true });
|
||||
console.log(`Created certificate store directory: ${certStorePath}`);
|
||||
}
|
||||
ensureCertificateDirectory(options.certificateStore);
|
||||
console.log(`Ensured certificate store directory: ${options.certificateStore}`);
|
||||
}
|
||||
return new Port80Handler(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates default ACME options with sensible defaults
|
||||
* @param email Account email for ACME provider
|
||||
* @param certificateStore Path to store certificates
|
||||
* @param useProduction Whether to use production ACME servers
|
||||
* @returns Configured ACME options
|
||||
*/
|
||||
export function createDefaultAcmeOptions(
|
||||
email: string,
|
||||
certificateStore: string,
|
||||
useProduction: boolean = false
|
||||
): AcmeOptions {
|
||||
return {
|
||||
accountEmail: email,
|
||||
enabled: true,
|
||||
port: 80,
|
||||
useProduction,
|
||||
httpsRedirectPort: 443,
|
||||
renewThresholdDays: 30,
|
||||
renewCheckIntervalHours: 24,
|
||||
autoRenew: true,
|
||||
certificateStore,
|
||||
skipConfiguredCerts: false
|
||||
};
|
||||
}
|
110
ts/certificate/acme/challenge-handler.ts
Normal file
110
ts/certificate/acme/challenge-handler.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import type { AcmeOptions, CertificateData } 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: AcmeOptions;
|
||||
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: AcmeOptions) {
|
||||
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;
|
||||
}
|
||||
}
|
3
ts/certificate/acme/index.ts
Normal file
3
ts/certificate/acme/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
/**
|
||||
* ACME certificate provisioning
|
||||
*/
|
Reference in New Issue
Block a user