From e5db20d06254bff577d5c16d9824474810e07edc Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 31 Oct 2025 21:43:53 -0700 Subject: [PATCH] Handle errors --- backend/src/db/schemas/pki-acme-orders.ts | 3 +- .../ee/services/pki-acme/pki-acme-errors.ts | 21 ++++++ .../ee/services/pki-acme/pki-acme-service.ts | 74 ++++++++++++------- .../certificate-profile-dal.ts | 2 +- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/backend/src/db/schemas/pki-acme-orders.ts b/backend/src/db/schemas/pki-acme-orders.ts index 61a15b156..eee8c96f0 100644 --- a/backend/src/db/schemas/pki-acme-orders.ts +++ b/backend/src/db/schemas/pki-acme-orders.ts @@ -18,7 +18,8 @@ export const PkiAcmeOrdersSchema = z.object({ updatedAt: z.date(), csr: z.string().nullable().optional(), certificate: z.string().nullable().optional(), - certificateChain: z.string().nullable().optional() + certificateChain: z.string().nullable().optional(), + error: z.string().nullable().optional() }); export type TPkiAcmeOrders = z.infer; diff --git a/backend/src/ee/services/pki-acme/pki-acme-errors.ts b/backend/src/ee/services/pki-acme/pki-acme-errors.ts index 924a7b0ca..73f17e061 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-errors.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-errors.ts @@ -528,3 +528,24 @@ export class AcmeOrderNotReadyError extends AcmeError { this.name = "AcmeOrderNotReadyError"; } } + +export class AcmeBadCSRError extends AcmeError { + constructor({ + detail = "The CSR is unacceptable", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.BadCsr, + detail, + status: 400, + error, + message + }); + this.name = "AcmeBadCSRError"; + } +} diff --git a/backend/src/ee/services/pki-acme/pki-acme-service.ts b/backend/src/ee/services/pki-acme/pki-acme-service.ts index a3c00a513..43754d74f 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -2,7 +2,7 @@ import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts"; import { TPkiAcmeAuths } from "@app/db/schemas/pki-acme-auths"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; -import { NotFoundError } from "@app/lib/errors"; +import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; @@ -25,6 +25,7 @@ import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal"; import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal"; import { AcmeAccountDoesNotExistError, + AcmeBadCSRError, AcmeBadPublicKeyError, AcmeError, AcmeMalformedError, @@ -520,32 +521,51 @@ export const pkiAcmeServiceFactory = ({ const { csr } = payload; // TODO: validate the CSR and return badCSR error if it's invalid // TODO: this should be the same transaction? - const { certificate, certificateChain, certificateId } = await certificateV3Service.signCertificateFromProfile({ - actor: ActorType.ACME_ACCOUNT, - actorId: accountId, - actorAuthMethod: null, - actorOrgId, - profileId, - csr, - notBefore: order.notBefore ? new Date(order.notBefore) : undefined, - notAfter: order.notAfter ? new Date(order.notAfter) : undefined, - validity: { - // TODO: read config from the profile to get the expiration time instead - ttl: (24 * 60 * 60 * 1000).toString() - }, - enrollmentType: EnrollmentType.ACME - }); - // TODO: associate the certificate with the order - await acmeOrderDAL.updateById( - orderId, - { - status: AcmeOrderStatus.Valid, - csr, - certificateChain, - certificate - }, - tx - ); + try { + const { certificate, certificateChain, certificateId } = + await certificateV3Service.signCertificateFromProfile({ + actor: ActorType.ACME_ACCOUNT, + actorId: accountId, + actorAuthMethod: null, + actorOrgId, + profileId, + csr, + notBefore: order.notBefore ? new Date(order.notBefore) : undefined, + notAfter: order.notAfter ? new Date(order.notAfter) : undefined, + validity: { + // TODO: read config from the profile to get the expiration time instead + ttl: (24 * 60 * 60 * 1000).toString() + }, + enrollmentType: EnrollmentType.ACME + }); + // TODO: associate the certificate with the order + await acmeOrderDAL.updateById( + orderId, + { + status: AcmeOrderStatus.Valid, + csr, + certificateChain, + certificate + }, + tx + ); + } catch (error) { + await acmeOrderDAL.updateById( + orderId, + { + csr, + status: AcmeOrderStatus.Invalid, + error: error instanceof Error ? error.message : "Unknown error" + }, + tx + ); + // TODO: log the error + // TODO: audit log the error + if (error instanceof BadRequestError) { + throw new AcmeBadCSRError({ detail: `Invalid CSR: ${error.message}` }); + } + throw new AcmeServerInternalError({ detail: "Failed to sign certificate" }); + } return await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations(accountId, orderId, tx); }); } else if (order.status !== AcmeOrderStatus.Valid) { diff --git a/backend/src/services/certificate-profile/certificate-profile-dal.ts b/backend/src/services/certificate-profile/certificate-profile-dal.ts index dcf6ca4e6..beea084e3 100644 --- a/backend/src/services/certificate-profile/certificate-profile-dal.ts +++ b/backend/src/services/certificate-profile/certificate-profile-dal.ts @@ -74,7 +74,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { .join(TableName.Project, `${TableName.PkiCertificateProfile}.projectId`, `${TableName.Project}.id`) .select(selectAllTableCols(TableName.PkiCertificateProfile)) .select(db.ref("orgId").withSchema(TableName.Project).as("ownerOrgId")) - .where({ id }) + .where(`${TableName.PkiCertificateProfile}.id`, id) .first()) as (TCertificateProfile & { ownerOrgId: string }) | undefined; return certificateProfile; } catch (error) {