import type * as http from 'node:http'; import * as crypto from 'node:crypto'; import { AcmeCrypto } from '../ts/acme/acme.classes.crypto.js'; import type { JwsVerifier } from './server.classes.jws.verifier.js'; import { AcmeServerError } from './server.classes.jws.verifier.js'; import type { IServerAccountStore } from './server.interfaces.js'; /** * POST /new-account — Register or retrieve an ACME account. * Expects JWS with JWK in protected header (not kid). */ export function createAccountHandler( baseUrl: string, jwsVerifier: JwsVerifier, accountStore: IServerAccountStore, ) { return async ( req: http.IncomingMessage, res: http.ServerResponse, _params: Record, body: any, ): Promise => { const requestUrl = `${baseUrl}/new-account`; const verified = await jwsVerifier.verify(body, requestUrl); // Account creation must use JWK, not kid if (verified.kid) { throw new AcmeServerError( 400, 'urn:ietf:params:acme:error:malformed', 'newAccount requests must use JWK, not kid', ); } const { payload, jwk, thumbprint } = verified; // Check if account already exists const existing = await accountStore.getByThumbprint(thumbprint); if (existing) { // If onlyReturnExisting, or just returning the existing account res.writeHead(200, { 'Content-Type': 'application/json', 'Location': existing.url, }); res.end(JSON.stringify({ status: existing.status, contact: existing.contact, orders: `${baseUrl}/account/${existing.id}/orders`, })); return; } // onlyReturnExisting = true but no account found if (payload?.onlyReturnExisting) { throw new AcmeServerError( 400, 'urn:ietf:params:acme:error:accountDoesNotExist', 'Account does not exist', ); } // Create new account const id = crypto.randomBytes(16).toString('hex'); const accountUrl = `${baseUrl}/account/${id}`; const account = await accountStore.create({ id, thumbprint, url: accountUrl, jwk, status: 'valid', contact: payload?.contact || [], createdAt: new Date().toISOString(), }); res.writeHead(201, { 'Content-Type': 'application/json', 'Location': accountUrl, }); res.end(JSON.stringify({ status: account.status, contact: account.contact, orders: `${baseUrl}/account/${id}/orders`, })); }; }