import * as plugins from '../plugins.js'; import type { IChallengeHandler } from './IChallengeHandler.js'; /** * HTTP-01 ACME challenge handler using file-system webroot. * Writes and removes the challenge file under /.well-known/acme-challenge/. */ export interface Http01WebrootOptions { /** * Directory that serves HTTP requests for /.well-known/acme-challenge */ webroot: string; } export class Http01Webroot implements IChallengeHandler<{ type: string; token: string; keyAuthorization: string; webPath: string; }> { private smartnetworkInstance = new plugins.smartnetwork.SmartNetwork(); private smartdnsClient = new plugins.smartdnsClient.Smartdns({}); private webroot: string; constructor(options: Http01WebrootOptions) { this.webroot = options.webroot; } public getSupportedTypes(): string[] { return ['http-01']; } public async prepare(ch: { token: string; keyAuthorization: string; webPath: string }): Promise { const relWebPath = ch.webPath.replace(/^\/+/, ''); const filePath = plugins.path.join(this.webroot, relWebPath); const dir = plugins.path.dirname(filePath); await plugins.fs.promises.mkdir(dir, { recursive: true }); await plugins.fs.promises.writeFile(filePath, ch.keyAuthorization, 'utf8'); } public async verify(ch: { webPath: string; keyAuthorization: string }): Promise { // Optional: implement HTTP polling if desired return; } public async cleanup(ch: { token: string; webPath: string }): Promise { const relWebPath = ch.webPath.replace(/^\/+/, ''); const filePath = plugins.path.join(this.webroot, relWebPath); try { await plugins.fs.promises.unlink(filePath); } catch { // ignore missing file } } public async checkWetherDomainIsSupported(domainArg: string): Promise { const publicIps = await this.smartnetworkInstance.getPublicIps(); const aRecords = await this.smartdnsClient.getRecordsA(domainArg); const aaaaRecords = await this.smartdnsClient.getRecordsAAAA(domainArg); if (aRecords.length && aRecords.some(record => record.value !== publicIps.v4)) { return false; } if (aaaaRecords.length && aaaaRecords.some(record => record.value !== publicIps.v6)) { return false; } return true; } }