Files
smartacme/ts_server/server.handlers.finalize.ts

94 lines
2.8 KiB
TypeScript

import type * as http from 'node:http';
import type { JwsVerifier } from './server.classes.jws.verifier.js';
import { AcmeServerError } from './server.classes.jws.verifier.js';
import type { IServerOrderStore } from './server.interfaces.js';
import type { AcmeServerCA } from './server.classes.ca.js';
/**
* POST /finalize/:id — Submit CSR and issue certificate.
*/
export function createFinalizeHandler(
baseUrl: string,
jwsVerifier: JwsVerifier,
orderStore: IServerOrderStore,
ca: AcmeServerCA,
) {
return async (
req: http.IncomingMessage,
res: http.ServerResponse,
params: Record<string, string>,
body: any,
): Promise<void> => {
const orderId = params.id;
const requestUrl = `${baseUrl}/finalize/${orderId}`;
const verified = await jwsVerifier.verify(body, requestUrl);
if (!verified.kid) {
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Finalize requires kid');
}
const order = await orderStore.getOrder(orderId);
if (!order) {
throw new AcmeServerError(404, 'urn:ietf:params:acme:error:malformed', 'Order not found');
}
// Check all authorizations are valid and update order status if needed
if (order.status === 'pending') {
let allValid = true;
for (const authzId of order.authorizationIds) {
const authz = await orderStore.getAuthorization(authzId);
if (!authz || authz.status !== 'valid') {
allValid = false;
break;
}
}
if (allValid) {
await orderStore.updateOrder(orderId, { status: 'ready' });
order.status = 'ready';
}
}
if (order.status !== 'ready') {
throw new AcmeServerError(
403,
'urn:ietf:params:acme:error:orderNotReady',
`Order is in "${order.status}" state, expected "ready"`,
);
}
const { payload } = verified;
if (!payload?.csr) {
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Missing CSR in finalize request');
}
// Transition to processing
await orderStore.updateOrder(orderId, { status: 'processing' });
// Sign the certificate
const certPem = await ca.signCsr(payload.csr);
// Store certificate and update order
const certUrl = `${baseUrl}/cert/${orderId}`;
await orderStore.storeCertPem(orderId, certPem);
await orderStore.updateOrder(orderId, {
status: 'valid',
certificate: certUrl,
});
const responseBody = {
status: 'valid',
expires: order.expires,
identifiers: order.identifiers,
authorizations: order.authorizationIds.map((id) => `${baseUrl}/authz/${id}`),
finalize: order.finalize,
certificate: certUrl,
};
res.writeHead(200, {
'Content-Type': 'application/json',
'Location': `${baseUrl}/order/${orderId}`,
});
res.end(JSON.stringify(responseBody));
};
}