smartacme/ts/handlers/Http01Handler.ts

69 lines
2.3 KiB
TypeScript

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 <webroot>/.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<void> {
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<void> {
// Optional: implement HTTP polling if desired
return;
}
public async cleanup(ch: { token: string; webPath: string }): Promise<void> {
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<boolean> {
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;
}
}