Check and sync status

This commit is contained in:
Fang-Pen Lin
2025-12-08 17:22:14 -08:00
parent 5a5a44a69f
commit 120947329b
2 changed files with 94 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
import { CertificateRequestStatus } from "@app/services/certificate-request/certificate-request-types";
export type TPkiAcmeOrderDALFactory = ReturnType<typeof pkiAcmeOrderDALFactory>;
@@ -19,6 +20,42 @@ export const pkiAcmeOrderDALFactory = (db: TDbClient) => {
}
};
const findWithCertificateRequestForSync = async (id: string, tx?: Knex) => {
try {
const order = await (tx || db)(TableName.PkiAcmeOrder)
.leftJoin(
TableName.CertificateRequests,
`${TableName.PkiAcmeOrder}.id`,
`${TableName.CertificateRequests}.certificateId`
)
.select(
selectAllTableCols(TableName.PkiAcmeOrder),
db.ref("id").withSchema(TableName.CertificateRequests).as("certificateRequestId"),
db.ref("status").withSchema(TableName.CertificateRequests).as("certificateRequestStatus")
)
.forUpdate(TableName.PkiAcmeOrder)
.where(`${TableName.PkiAcmeOrder}.id`, id)
.first();
if (!order) {
return null;
}
return {
...order,
certificateRequest:
order.certificateRequestId && order.certificateRequestStatus
? {
id: order.certificateRequestId,
status: order.certificateRequestStatus as CertificateRequestStatus,
// The certificate id for async certificate request is the same as the order id
certificateId: order.id
}
: undefined
};
} catch (error) {
throw new DatabaseError({ error, name: "Find PKI ACME order by id with certificate request" });
}
};
const findByAccountAndOrderIdWithAuthorizations = async (accountId: string, orderId: string, tx?: Knex) => {
try {
const rows = await (tx || db)(TableName.PkiAcmeOrder)
@@ -72,6 +109,7 @@ export const pkiAcmeOrderDALFactory = (db: TDbClient) => {
return {
...pkiAcmeOrderOrm,
findByIdForFinalization,
findWithCertificateRequestForSync,
findByAccountAndOrderIdWithAuthorizations,
listByAccountId
};

View File

@@ -104,6 +104,7 @@ import {
TRawJwsPayload,
TRespondToAcmeChallengeResponse
} from "./pki-acme-types";
import { TPkiAcmeOrders } from "@app/db/schemas";
type TPkiAcmeServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction" | "findById">;
@@ -117,11 +118,13 @@ type TPkiAcmeServiceFactoryDep = {
>;
acmeOrderDAL: Pick<
TPkiAcmeOrderDALFactory,
| "findById"
| "create"
| "transaction"
| "updateById"
| "findByAccountAndOrderIdWithAuthorizations"
| "findByIdForFinalization"
| "findWithCertificateRequestForSync"
| "listByAccountId"
>;
acmeAuthDAL: Pick<TPkiAcmeAuthDALFactory, "create" | "findByAccountIdAndAuthIdWithChallenges">;
@@ -370,6 +373,50 @@ export const pkiAcmeServiceFactory = ({
};
};
const checkAndSyncAcmeOrderStatus = async ({ orderId }: { orderId: string }): Promise<TPkiAcmeOrders> => {
const order = await acmeOrderDAL.findById(orderId);
if (!order) {
throw new NotFoundError({ message: "ACME order not found" });
}
if (order.status !== AcmeOrderStatus.Ready) {
return order;
}
return acmeOrderDAL.transaction(async (tx) => {
// Lock the order for syncing with async cert request
const orderWithCertificateRequest = await acmeOrderDAL.findWithCertificateRequestForSync(orderId, tx);
if (!orderWithCertificateRequest) {
throw new NotFoundError({ message: "ACME order not found" });
}
if (
orderWithCertificateRequest.status !== AcmeOrderStatus.Ready ||
!orderWithCertificateRequest.certificateRequest
) {
return orderWithCertificateRequest;
}
let newStatus: AcmeOrderStatus | undefined;
let newCertificateId: string | undefined;
switch (orderWithCertificateRequest.certificateRequest.status) {
case CertificateRequestStatus.PENDING:
break;
case CertificateRequestStatus.ISSUED:
newStatus = AcmeOrderStatus.Valid;
newCertificateId = orderWithCertificateRequest.certificateRequest.certificateId;
break;
case CertificateRequestStatus.FAILED:
newStatus = AcmeOrderStatus.Invalid;
break;
default:
throw new AcmeServerInternalError({
message: `Invalid certificate request status: ${orderWithCertificateRequest.certificateRequest.status as string}`
});
}
if (newStatus) {
return acmeOrderDAL.updateById(orderId, { status: newStatus, certificateId: newCertificateId }, tx);
}
return orderWithCertificateRequest;
});
};
const getAcmeDirectory = async (profileId: string): Promise<TGetAcmeDirectoryResponse> => {
const profile = await validateAcmeProfile(profileId);
return {
@@ -737,9 +784,11 @@ export const pkiAcmeServiceFactory = ({
if (!order) {
throw new NotFoundError({ message: "ACME order not found" });
}
// Sync order first in case if there is a certificate request that needs to be processed
const syncedOrder = await checkAndSyncAcmeOrderStatus({ orderId });
return {
status: 200,
body: buildAcmeOrderResource({ profileId, order }),
body: buildAcmeOrderResource({ profileId, order: syncedOrder }),
headers: {
Location: buildUrl(profileId, `/orders/${orderId}`),
Link: `<${buildUrl(profileId, "/directory")}>;rel="index"`
@@ -1001,7 +1050,7 @@ export const pkiAcmeServiceFactory = ({
}
if (certIssuanceJobData) {
// TODO: ideally, this should be done inside the transaction, but the pg-boss queue doesn't support external transactions
// as it seems to be.
// as it seems to be. we need to commit the transaction before queuing the job, otherwise the job will fail (not found error).
await certificateIssuanceQueue.queueCertificateIssuance(certIssuanceJobData);
}
order = updatedOrder;
@@ -1049,14 +1098,16 @@ export const pkiAcmeServiceFactory = ({
if (!order) {
throw new NotFoundError({ message: "ACME order not found" });
}
if (order.status !== AcmeOrderStatus.Valid) {
// Sync order first in case if there is a certificate request that needs to be processed
const syncedOrder = await checkAndSyncAcmeOrderStatus({ orderId });
if (syncedOrder.status !== AcmeOrderStatus.Valid) {
throw new AcmeOrderNotReadyError({ message: "ACME order is not valid" });
}
if (!order.certificateId) {
if (!syncedOrder.certificateId) {
throw new NotFoundError({ message: "The certificate for this ACME order no longer exists" });
}
const certBody = await certificateBodyDAL.findOne({ certId: order.certificateId });
const certBody = await certificateBodyDAL.findOne({ certId: syncedOrder.certificateId });
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
projectId: profile.projectId,
projectDAL,