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

86 lines
2.5 KiB
TypeScript

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<string, string>,
body: any,
): Promise<void> => {
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`,
}));
};
}