feat(handlers): Add in-memory HTTP-01 challenge handler and rename file-based handler to Http01Webroot

This commit is contained in:
2025-04-30 14:55:03 +00:00
parent 9f66a0487f
commit fc420eb615
9 changed files with 204 additions and 14 deletions

View File

@ -6,14 +6,14 @@ 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 Http01HandlerOptions {
export interface Http01WebrootOptions {
/**
* Directory that serves HTTP requests for /.well-known/acme-challenge
*/
webroot: string;
}
export class Http01Handler implements IChallengeHandler<{
export class Http01Webroot implements IChallengeHandler<{
type: string;
token: string;
keyAuthorization: string;
@ -21,7 +21,7 @@ export class Http01Handler implements IChallengeHandler<{
}> {
private webroot: string;
constructor(options: Http01HandlerOptions) {
constructor(options: Http01WebrootOptions) {
this.webroot = options.webroot;
}

View File

@ -0,0 +1,67 @@
import type { IChallengeHandler } from './IChallengeHandler.js';
/**
* HTTP-01 ACME challenge handler using in-memory storage.
* Stores challenge tokens and key authorizations in memory
* and serves them via handleRequest for arbitrary HTTP servers.
*/
export interface Http01MemoryHandlerChallenge {
type: string;
token: string;
keyAuthorization: string;
webPath: string;
}
export class Http01MemoryHandler implements IChallengeHandler<Http01MemoryHandlerChallenge> {
private store: Map<string, string> = new Map();
public getSupportedTypes(): string[] {
return ['http-01'];
}
public async prepare(ch: Http01MemoryHandlerChallenge): Promise<void> {
this.store.set(ch.token, ch.keyAuthorization);
}
public async verify(_ch: Http01MemoryHandlerChallenge): Promise<void> {
// No-op
return;
}
public async cleanup(ch: Http01MemoryHandlerChallenge): Promise<void> {
this.store.delete(ch.token);
}
/**
* HTTP request handler for serving ACME HTTP-01 challenges.
* @param req HTTP request object (should have url property)
* @param res HTTP response object
* @param next Optional next() callback for Express-style fallthrough
*/
public handleRequest(req: any, res: any, next?: () => void): void {
const url = req.url || '';
const prefix = '/.well-known/acme-challenge/';
if (!url.startsWith(prefix)) {
if (next) {
return next();
}
res.statusCode = 404;
return res.end();
}
const token = url.slice(prefix.length);
const keyAuth = this.store.get(token);
if (keyAuth !== undefined) {
if (typeof res.status === 'function' && typeof res.send === 'function') {
return res.status(200).send(keyAuth);
}
res.statusCode = 200;
res.setHeader('content-type', 'text/plain');
return res.end(keyAuth);
}
if (next) {
return next();
}
res.statusCode = 404;
return res.end();
}
}

View File

@ -1,4 +1,5 @@
export type { IChallengeHandler } from './IChallengeHandler.js';
// Removed legacy handler adapter
export { Dns01Handler } from './Dns01Handler.js';
export { Http01Handler } from './Http01Handler.js';
export { Http01Webroot } from './Http01Handler.js';
export { Http01MemoryHandler } from './Http01MemoryHandler.js';