Refactor code

This commit is contained in:
Fang-Pen Lin
2025-10-30 09:45:15 -07:00
parent 011b0df414
commit 6b85ab54a4
5 changed files with 188 additions and 197 deletions

View File

@@ -12,7 +12,9 @@ export const PkiAcmeOrdersSchema = z.object({
accountId: z.string().uuid(),
status: z.string(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
notBefore: z.date().nullable().optional(),
notAfter: z.date().nullable().optional()
});
export type TPkiAcmeOrders = z.infer<typeof PkiAcmeOrdersSchema>;

View File

@@ -4,16 +4,14 @@ import { FastifyReply } from "fastify";
import { z } from "zod";
import {
AcmeOrderResourceSchema,
CreateAcmeAccountResponseSchema,
CreateAcmeOrderBodySchema,
CreateAcmeOrderResponseSchema,
DeactivateAcmeAccountBodySchema,
DeactivateAcmeAccountResponseSchema,
FinalizeAcmeOrderBodySchema,
FinalizeAcmeOrderResponseSchema,
GetAcmeAuthorizationResponseSchema,
GetAcmeDirectoryResponseSchema,
GetAcmeOrderResponseSchema,
ListAcmeOrdersPayloadSchema,
ListAcmeOrdersResponseSchema,
RawJwsPayloadSchema,
@@ -138,46 +136,6 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => {
}
});
// POST /api/v1/pki/acme/profiles/<profile_id>/new-order
// New Certificate Order (RFC 8555 Section 7.4)
server.route({
method: "POST",
url: "/profiles/:profileId/new-order",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiAcme],
description: "ACME New Order - apply for a new certificate",
params: z.object({
profileId: z.string().uuid()
}),
body: RawJwsPayloadSchema,
response: {
201: CreateAcmeOrderResponseSchema
}
},
// TODO: replace with verify ACME signature here instead
// onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req, res) => {
const { profileId, accountId, payload } = await server.services.pkiAcme.validateExistingAccountJwsPayload({
profileId: req.params.profileId,
rawJwsPayload: req.body,
schema: CreateAcmeOrderBodySchema
});
return sendAcmeResponse(
res,
profileId,
await server.services.pkiAcme.createAcmeOrder({
profileId,
accountId,
payload
})
);
}
});
// POST /api/v1/pki/acme/profiles/<profile_id>/accounts/<account_id>
// Account Deactivation (RFC 8555 Section 7.3.6)
server.route({
@@ -220,42 +178,41 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => {
}
});
// POST /api/v1/pki/acme/profiles/<profile_id>/accounts/<account_id>/orders
// List Orders (RFC 8555 Section 7.1.2.1)
// POST /api/v1/pki/acme/profiles/<profile_id>/new-order
// New Certificate Order (RFC 8555 Section 7.4)
server.route({
method: "POST",
url: "/profiles/:profileId/accounts/:accountId/orders",
url: "/profiles/:profileId/new-order",
config: {
rateLimit: readLimit
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiAcme],
description: "ACME List Orders - get existing orders from current account",
description: "ACME New Order - apply for a new certificate",
params: z.object({
profileId: z.string().uuid(),
accountId: z.string()
profileId: z.string().uuid()
}),
body: RawJwsPayloadSchema,
response: {
200: ListAcmeOrdersResponseSchema
201: AcmeOrderResourceSchema
}
},
// TODO: replace with verify ACME signature here instead
// onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req, res) => {
const { profileId, accountId } = await server.services.pkiAcme.validateExistingAccountJwsPayload({
const { profileId, accountId, payload } = await server.services.pkiAcme.validateExistingAccountJwsPayload({
profileId: req.params.profileId,
rawJwsPayload: req.body,
schema: ListAcmeOrdersPayloadSchema,
expectedAccountId: req.params.accountId
schema: CreateAcmeOrderBodySchema
});
return sendAcmeResponse(
res,
profileId,
await server.services.pkiAcme.listAcmeOrders({
await server.services.pkiAcme.createAcmeOrder({
profileId,
accountId
accountId,
payload
})
);
}
@@ -279,7 +236,7 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => {
}),
body: RawJwsPayloadSchema,
response: {
200: GetAcmeOrderResponseSchema
200: AcmeOrderResourceSchema
}
},
// TODO: replace with verify ACME signature here instead
@@ -319,16 +276,16 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => {
}),
body: RawJwsPayloadSchema,
response: {
200: FinalizeAcmeOrderResponseSchema
200: AcmeOrderResourceSchema
}
},
// TODO: replace with verify ACME signature here instead
// onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req, res) => {
const { profileId, accountId, payload } = await server.services.pkiAcme.validateExistingAccountJwsPayload({
const { profileId, accountId, payload } = await server.services.pkiAcme.validateExistingAccountJwsPayload({
profileId: req.params.profileId,
rawJwsPayload: req.body
schema: FinalizeAcmeOrderBodySchema,
rawJwsPayload: req.body,
schema: FinalizeAcmeOrderBodySchema
});
return sendAcmeResponse(
res,
@@ -342,6 +299,46 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => {
);
}
});
// POST /api/v1/pki/acme/profiles/<profile_id>/accounts/<account_id>/orders
// List Orders (RFC 8555 Section 7.1.2.1)
server.route({
method: "POST",
url: "/profiles/:profileId/accounts/:accountId/orders",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiAcme],
description: "ACME List Orders - get existing orders from current account",
params: z.object({
profileId: z.string().uuid(),
accountId: z.string()
}),
body: RawJwsPayloadSchema,
response: {
200: ListAcmeOrdersResponseSchema
}
},
// TODO: replace with verify ACME signature here instead
// onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req, res) => {
const { profileId, accountId } = await server.services.pkiAcme.validateExistingAccountJwsPayload({
profileId: req.params.profileId,
rawJwsPayload: req.body,
schema: ListAcmeOrdersPayloadSchema,
expectedAccountId: req.params.accountId
});
return sendAcmeResponse(
res,
profileId,
await server.services.pkiAcme.listAcmeOrders({
profileId,
accountId
})
);
}
});
// POST /api/v1/pki/acme/profiles/<profile_id>/orders/<order_id>/certificate
// Download Certificate (RFC 8555 Section 7.4.2)

View File

@@ -90,9 +90,11 @@ export const CreateAcmeOrderBodySchema = z.object({
notAfter: z.string().optional()
});
export const CreateAcmeOrderResponseSchema = z.object({
status: z.string(),
expires: z.string(),
export const AcmeOrderResourceSchema = z.object({
status: z.enum(Object.values(AcmeOrderStatus) as [string, ...string[]]),
expires: z.string().optional(),
notBefore: z.string().optional(),
notAfter: z.string().optional(),
identifiers: z.array(
z.object({
type: z.string(),
@@ -120,39 +122,11 @@ export const ListAcmeOrdersResponseSchema = z.object({
orders: z.array(z.string())
});
export const GetAcmeOrderResponseSchema = z.object({
status: z.enum(Object.values(AcmeOrderStatus) as [string, ...string[]]),
expires: z.string().optional(),
identifiers: z.array(
z.object({
type: z.string(),
value: z.string()
})
),
authorizations: z.array(z.string()),
finalize: z.string(),
certificate: z.string().optional()
});
// Finalize Order payload schema
export const FinalizeAcmeOrderBodySchema = z.object({
csr: z.string()
});
export const FinalizeAcmeOrderResponseSchema = z.object({
status: z.enum(Object.values(AcmeOrderStatus) as [string, ...string[]]),
expires: z.string().optional(),
identifiers: z.array(
z.object({
type: z.string(),
value: z.string()
})
),
authorizations: z.array(z.string()),
finalize: z.string(),
certificate: z.string().optional()
});
export const GetAcmeAuthorizationResponseSchema = z.object({
status: z.enum(Object.values(AcmeAuthStatus) as [string, ...string[]]),
expires: z.string().optional(),

View File

@@ -37,16 +37,14 @@ import {
TCreateAcmeAccountPayload,
TCreateAcmeAccountResponse,
TCreateAcmeOrderPayload,
TCreateAcmeOrderResponse,
TDeactivateAcmeAccountPayload,
TDeactivateAcmeAccountResponse,
TFinalizeAcmeOrderPayload,
TFinalizeAcmeOrderResponse,
TGetAcmeAuthorizationResponse,
TGetAcmeDirectoryResponse,
TGetAcmeOrderResponse,
TJwsPayload,
TListAcmeOrdersResponse,
TAcmeOrderResource,
TPkiAcmeServiceFactory,
TRawJwsPayload,
TRespondToAcmeChallengeResponse
@@ -206,6 +204,36 @@ export const pkiAcmeServiceFactory = ({
};
};
const buildAcmeOrderResource = ({
profileId,
order
}: {
order: {
id: string;
status: string;
expiresAt: Date;
notBefore?: Date | null;
notAfter?: Date | null;
authorizations: TPkiAcmeAuths[];
};
profileId: string;
}) => {
return {
status: order.status,
expires: order.expiresAt.toISOString(),
notBefore: order.notBefore?.toISOString(),
notAfter: order.notAfter?.toISOString(),
identifiers: order.authorizations.map((auth: TPkiAcmeAuths) => ({
type: auth.identifierType,
value: auth.identifierValue
})),
authorizations: order.authorizations.map((auth: TPkiAcmeAuths) =>
buildUrl(`/api/v1/pki/acme/profiles/${profileId}/authorizations/${auth.id}`)
),
finalize: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${order.id}/finalize`)
};
};
const getAcmeNewNonce = async (profileId: string): Promise<string> => {
const profile = await validateAcmeProfile(profileId);
// FIXME: Implement ACME new nonce generation
@@ -213,6 +241,9 @@ export const pkiAcmeServiceFactory = ({
return "FIXME-generate-nonce";
};
/** --------------------------------------------------------------
* ACME Account
* -------------------------------------------------------------- */
const createAcmeAccount = async ({
profileId,
alg,
@@ -251,6 +282,7 @@ export const pkiAcmeServiceFactory = ({
publicKey: jwk,
emails: contact ?? []
});
// TODO: create audit log here
// TODO: check EAB authentication here
return {
status: 201,
@@ -265,6 +297,31 @@ export const pkiAcmeServiceFactory = ({
};
};
const deactivateAcmeAccount = async ({
profileId,
accountId,
payload: { status } = { status: "deactivated" }
}: {
profileId: string;
accountId: string;
payload?: TDeactivateAcmeAccountPayload;
}): Promise<TAcmeResponse<TDeactivateAcmeAccountResponse>> => {
const profile = await validateAcmeProfile(profileId);
// FIXME: Implement ACME account deactivation
return {
status: 200,
body: {
status: "deactivated"
},
headers: {
Location: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/accounts/${accountId}`)
}
};
};
/** --------------------------------------------------------------
* ACME Order
* -------------------------------------------------------------- */
const createAcmeOrder = async ({
profileId,
accountId,
@@ -273,7 +330,7 @@ export const pkiAcmeServiceFactory = ({
profileId: string;
accountId: string;
payload: TCreateAcmeOrderPayload;
}): Promise<TAcmeResponse<TCreateAcmeOrderResponse>> => {
}): Promise<TAcmeResponse<TAcmeOrderResource>> => {
// TODO: check and see if we have existing orders for this account that meet the criteria
// if we do, return the existing order
@@ -314,47 +371,68 @@ export const pkiAcmeServiceFactory = ({
})),
tx
);
// TODO: create audit log here
return { ...createdOrder, authorizations, account };
});
return {
status: 201,
body: {
status: order.status,
expires: order.expiresAt.toISOString(),
identifiers: order.authorizations.map((auth: TPkiAcmeAuths) => ({
type: auth.identifierType,
value: auth.identifierValue
})),
authorizations: order.authorizations.map((auth: TPkiAcmeAuths) =>
buildUrl(`/api/v1/pki/acme/profiles/${order.account.profileId}/authorizations/${auth.id}`)
),
finalize: buildUrl(`/api/v1/pki/acme/profiles/${order.account.profileId}/orders/${order.id}/finalize`)
},
body: buildAcmeOrderResource({
profileId,
order
}),
headers: {
Location: buildUrl(`/api/v1/pki/acme/profiles/${order.account.profileId}/orders/${order.id}`)
}
};
};
const deactivateAcmeAccount = async ({
const getAcmeOrder = async ({
profileId,
accountId,
payload: { status } = { status: "deactivated" }
orderId
}: {
profileId: string;
accountId: string;
payload?: TDeactivateAcmeAccountPayload;
}): Promise<TAcmeResponse<TDeactivateAcmeAccountResponse>> => {
orderId: string;
}): Promise<TAcmeResponse<TAcmeOrderResource>> => {
const order = await acmeOrderDAL.findByIdWithAuthorizations(orderId);
if (!order || order.accountId !== accountId) {
throw new NotFoundError({ message: "ACME order not found" });
}
return {
status: 200,
body: buildAcmeOrderResource({ profileId, order }),
headers: { Location: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}`) }
};
};
const finalizeAcmeOrder = async ({
profileId,
accountId,
orderId,
payload
}: {
profileId: string;
accountId: string;
orderId: string;
payload: TFinalizeAcmeOrderPayload;
}): Promise<TAcmeResponse<TAcmeOrderResource>> => {
const profile = await validateAcmeProfile(profileId);
// FIXME: Implement ACME account deactivation
const { csr } = payload;
// FIXME: Implement ACME finalize order
return {
status: 200,
body: {
status: "deactivated"
status: "processing",
expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
identifiers: [],
authorizations: [],
finalize: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize`),
certificate: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/certificate`)
},
headers: {
Location: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/accounts/${accountId}`)
Location: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}`)
}
};
};
@@ -379,67 +457,6 @@ export const pkiAcmeServiceFactory = ({
};
};
const getAcmeOrder = async ({
profileId,
accountId,
orderId
}: {
profileId: string;
accountId: string;
orderId: string;
}): Promise<TAcmeResponse<TGetAcmeOrderResponse>> => {
const order = await acmeOrderDAL.findByIdWithAuthorizations(orderId);
if (!order || order.accountId !== accountId) {
throw new NotFoundError({ message: "ACME order not found" });
}
return {
status: 200,
body: {
status: order.status,
expires: order.expiresAt.toISOString(),
identifiers: order.authorizations.map((auth: TPkiAcmeAuths) => ({
type: auth.identifierType,
value: auth.identifierValue
})),
authorizations: order.authorizations.map((auth: TPkiAcmeAuths) =>
buildUrl(`/api/v1/pki/acme/profiles/${profileId}/authorizations/${auth.id}`)
),
finalize: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize`)
},
headers: { Location: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}`) }
};
};
const finalizeAcmeOrder = async ({
profileId,
accountId,
orderId,
payload
}: {
profileId: string;
accountId: string;
orderId: string;
payload: TFinalizeAcmeOrderPayload;
}): Promise<TAcmeResponse<TFinalizeAcmeOrderResponse>> => {
const profile = await validateAcmeProfile(profileId);
const { csr } = payload;
// FIXME: Implement ACME finalize order
return {
status: 200,
body: {
status: "processing",
expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
identifiers: [],
authorizations: [],
finalize: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize`),
certificate: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/certificate`)
},
headers: {
Location: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}`)
}
};
};
const downloadAcmeCertificate = async ({
profileId,
orderId
@@ -459,6 +476,9 @@ export const pkiAcmeServiceFactory = ({
};
};
/** --------------------------------------------------------------
* ACME Authorization
* -------------------------------------------------------------- */
const getAcmeAuthorization = async ({
profileId,
accountId,

View File

@@ -2,17 +2,15 @@ import { z } from "zod";
import { JWSHeaderParameters } from "jose";
import {
AcmeOrderResourceSchema,
CreateAcmeAccountBodySchema,
CreateAcmeAccountResponseSchema,
CreateAcmeOrderBodySchema,
CreateAcmeOrderResponseSchema,
DeactivateAcmeAccountBodySchema,
DeactivateAcmeAccountResponseSchema,
FinalizeAcmeOrderBodySchema,
FinalizeAcmeOrderResponseSchema,
GetAcmeAuthorizationResponseSchema,
GetAcmeDirectoryResponseSchema,
GetAcmeOrderResponseSchema,
ListAcmeOrdersResponseSchema,
ProtectedHeaderSchema,
RawJwsPayloadSchema,
@@ -21,11 +19,9 @@ import {
export type TGetAcmeDirectoryResponse = z.infer<typeof GetAcmeDirectoryResponseSchema>;
export type TCreateAcmeAccountResponse = z.infer<typeof CreateAcmeAccountResponseSchema>;
export type TCreateAcmeOrderResponse = z.infer<typeof CreateAcmeOrderResponseSchema>;
export type TAcmeOrderResource = z.infer<typeof AcmeOrderResourceSchema>;
export type TDeactivateAcmeAccountResponse = z.infer<typeof DeactivateAcmeAccountResponseSchema>;
export type TListAcmeOrdersResponse = z.infer<typeof ListAcmeOrdersResponseSchema>;
export type TGetAcmeOrderResponse = z.infer<typeof GetAcmeOrderResponseSchema>;
export type TFinalizeAcmeOrderResponse = z.infer<typeof FinalizeAcmeOrderResponseSchema>;
export type TDownloadAcmeCertificateDTO = string;
export type TGetAcmeAuthorizationResponse = z.infer<typeof GetAcmeAuthorizationResponseSchema>;
export type TRespondToAcmeChallengeResponse = z.infer<typeof RespondToAcmeChallengeResponseSchema>;
@@ -89,15 +85,6 @@ export type TPkiAcmeServiceFactory = {
jwk: JsonWebKey;
payload: TCreateAcmeAccountPayload;
}) => Promise<TAcmeResponse<TCreateAcmeAccountResponse>>;
createAcmeOrder: ({
profileId,
accountId,
payload
}: {
profileId: string;
accountId: string;
payload: TCreateAcmeOrderPayload;
}) => Promise<TAcmeResponse<TCreateAcmeOrderResponse>>;
deactivateAcmeAccount: ({
profileId,
accountId,
@@ -107,13 +94,15 @@ export type TPkiAcmeServiceFactory = {
accountId: string;
payload?: TDeactivateAcmeAccountPayload;
}) => Promise<TAcmeResponse<TDeactivateAcmeAccountResponse>>;
listAcmeOrders: ({
createAcmeOrder: ({
profileId,
accountId
accountId,
payload
}: {
profileId: string;
accountId: string;
}) => Promise<TAcmeResponse<TListAcmeOrdersResponse>>;
payload: TCreateAcmeOrderPayload;
}) => Promise<TAcmeResponse<TAcmeOrderResource>>;
getAcmeOrder: ({
profileId,
accountId,
@@ -122,7 +111,7 @@ export type TPkiAcmeServiceFactory = {
profileId: string;
accountId: string;
orderId: string;
}) => Promise<TAcmeResponse<TGetAcmeOrderResponse>>;
}) => Promise<TAcmeResponse<TAcmeOrderResource>>;
finalizeAcmeOrder: ({
profileId,
accountId,
@@ -133,12 +122,21 @@ export type TPkiAcmeServiceFactory = {
accountId: string;
orderId: string;
payload: TFinalizeAcmeOrderPayload;
}) => Promise<TAcmeResponse<TFinalizeAcmeOrderResponse>>;
}) => Promise<TAcmeResponse<TAcmeOrderResource>>;
listAcmeOrders: ({
profileId,
accountId
}: {
profileId: string;
accountId: string;
}) => Promise<TAcmeResponse<TListAcmeOrdersResponse>>;
downloadAcmeCertificate: ({
profileId,
accountId,
orderId
}: {
profileId: string;
accountId: string;
orderId: string;
}) => Promise<TAcmeResponse<string>>;
getAcmeAuthorization: ({