From 2f4066cafb14874a8d52497d5f88d95ba9c2d46f Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 27 Oct 2025 15:55:27 -0700 Subject: [PATCH] Extract types --- backend/src/ee/routes/v1/pki-acme-router.ts | 23 +++++-- .../ee/services/pki-acme/pki-acme-schemas.ts | 68 +++++++++++-------- .../ee/services/pki-acme/pki-acme-service.ts | 20 ++++-- .../ee/services/pki-acme/pki-acme-types.ts | 26 +++++-- 4 files changed, 94 insertions(+), 43 deletions(-) diff --git a/backend/src/ee/routes/v1/pki-acme-router.ts b/backend/src/ee/routes/v1/pki-acme-router.ts index 2a82bfeb3..49cc81021 100644 --- a/backend/src/ee/routes/v1/pki-acme-router.ts +++ b/backend/src/ee/routes/v1/pki-acme-router.ts @@ -1,8 +1,6 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { z } from "zod"; -import { ApiDocsTags } from "@app/lib/api-docs"; -import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { CreateAcmeAccountResponseSchema, CreateAcmeAccountSchema, @@ -25,8 +23,13 @@ import { RespondToAcmeChallengeResponseSchema, RespondToAcmeChallengeSchema } from "@app/ee/services/pki-acme/pki-acme-schemas"; +import { ApiDocsTags } from "@app/lib/api-docs"; +import { getConfig } from "@app/lib/config/env"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { + const appCfg = getConfig(); + server.addContentTypeParser("application/jose+json", { parseAs: "string" }, (_, body, done) => { try { const strBody = body instanceof Buffer ? body.toString() : body; @@ -107,11 +110,21 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { } }, handler: async (req, res) => { + // TODO: check nonce here + // TODO: check signature here + const account = await server.services.pkiAcme.createAcmeAccount(req.params.profileId, req.body); // TODO: deal with existing account case here res.code(201); + res.header( + "Location", + `${appCfg.SITE_URL}/api/v1/pki/acme/profiles/${req.params.profileId}/accounts/${account.accountUrl}` + ); + + // TODO: DRY const nonce = await server.services.pkiAcme.getAcmeNewNonce(req.params.profileId); res.header("Replay-Nonce", nonce); + res.header("Cache-Control", "no-store"); return account; } }); @@ -227,11 +240,7 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const order = await server.services.pkiAcme.finalizeAcmeOrder( - req.params.profileId, - req.params.orderId, - req.body.csr - ); + const order = await server.services.pkiAcme.finalizeAcmeOrder(req.params.profileId, req.params.orderId, req.body); return order; } }); diff --git a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts index 2f470f834..cdce6e9b8 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts @@ -21,23 +21,26 @@ export const GetAcmeNewNonceSchema = z.object({ }) }); +// New Account payload schema +export const CreateAcmeAccountBodySchema = z.object({ + contact: z.array(z.string()).optional(), + termsOfServiceAgreed: z.boolean().optional(), + onlyReturnExisting: z.boolean().optional(), + externalAccountBinding: z + .object({ + protected: z.string(), + payload: z.string(), + signature: z.string() + }) + .optional() +}); + // New Account endpoint export const CreateAcmeAccountSchema = z.object({ params: z.object({ profileId: z.string().uuid() }), - body: z.object({ - contact: z.array(z.string()).optional(), - termsOfServiceAgreed: z.boolean().optional(), - onlyReturnExisting: z.boolean().optional(), - externalAccountBinding: z - .object({ - protected: z.string(), - payload: z.string(), - signature: z.string() - }) - .optional() - }) + body: CreateAcmeAccountBodySchema }); export const CreateAcmeAccountResponseSchema = z.object({ @@ -47,21 +50,24 @@ export const CreateAcmeAccountResponseSchema = z.object({ accountUrl: z.string() }); +// New Order payload schema +export const CreateAcmeOrderBodySchema = z.object({ + identifiers: z.array( + z.object({ + type: z.string(), + value: z.string() + }) + ), + notBefore: z.string().optional(), + notAfter: z.string().optional() +}); + // New Order endpoint export const CreateAcmeOrderSchema = z.object({ params: z.object({ profileId: z.string().uuid() }), - body: z.object({ - identifiers: z.array( - z.object({ - type: z.string(), - value: z.string() - }) - ), - notBefore: z.string().optional(), - notAfter: z.string().optional() - }) + body: CreateAcmeOrderBodySchema }); export const CreateAcmeOrderResponseSchema = z.object({ @@ -78,15 +84,18 @@ export const CreateAcmeOrderResponseSchema = z.object({ certificate: z.string().optional() }); +// Account Deactivation payload schema +export const DeactivateAcmeAccountBodySchema = z.object({ + status: z.literal("deactivated") +}); + // Account Deactivation endpoint export const DeactivateAcmeAccountSchema = z.object({ params: z.object({ profileId: z.string().uuid(), accountId: z.string() }), - body: z.object({ - status: z.literal("deactivated") - }) + body: DeactivateAcmeAccountBodySchema }); export const DeactivateAcmeAccountResponseSchema = z.object({ @@ -127,15 +136,18 @@ export const GetAcmeOrderResponseSchema = z.object({ certificate: z.string().optional() }); +// Finalize Order payload schema +export const FinalizeAcmeOrderBodySchema = z.object({ + csr: z.string() +}); + // Finalize Order endpoint export const FinalizeAcmeOrderSchema = z.object({ params: z.object({ profileId: z.string().uuid(), orderId: z.string() }), - body: z.object({ - csr: z.string() - }) + body: FinalizeAcmeOrderBodySchema }); export const FinalizeAcmeOrderResponseSchema = z.object({ 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 1148dda3d..9e9fce099 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -4,10 +4,14 @@ import { NotFoundError } from "@app/lib/errors"; import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { + TCreateAcmeAccountPayload, TCreateAcmeAccountResponse, + TCreateAcmeOrderPayload, TCreateAcmeOrderResponse, + TDeactivateAcmeAccountPayload, TDeactivateAcmeAccountResponse, TDownloadAcmeCertificateDTO, + TFinalizeAcmeOrderPayload, TFinalizeAcmeOrderResponse, TGetAcmeAuthorizationResponse, TGetAcmeDirectoryResponse, @@ -49,7 +53,10 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService return "FIXME-generate-nonce"; }; - const createAcmeAccount = async (profileId: string, body: unknown): Promise => { + const createAcmeAccount = async ( + profileId: string, + body: TCreateAcmeAccountPayload + ): Promise => { // FIXME: Implement ACME new account registration // Use EAB authentication to find corresponding Infisical machine identity // Check permissions and return account information @@ -63,7 +70,10 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService }; }; - const createAcmeOrder = async (profileId: string, body: unknown): Promise => { + const createAcmeOrder = async ( + profileId: string, + body: TCreateAcmeOrderPayload + ): Promise => { // FIXME: Implement ACME new order creation const orderId = "FIXME-order-id"; const baseUrl = appCfg.SITE_URL || ""; @@ -78,7 +88,8 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService const deactivateAcmeAccount = async ( profileId: string, - accountId: string + accountId: string, + body?: TDeactivateAcmeAccountPayload ): Promise => { // FIXME: Implement ACME account deactivation return { @@ -108,8 +119,9 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService const finalizeAcmeOrder = async ( profileId: string, orderId: string, - csr: string + body: TFinalizeAcmeOrderPayload ): Promise => { + const { csr } = body; // FIXME: Implement ACME finalize order const baseUrl = appCfg.SITE_URL || ""; return { diff --git a/backend/src/ee/services/pki-acme/pki-acme-types.ts b/backend/src/ee/services/pki-acme/pki-acme-types.ts index 4a80ab0f6..f004e5102 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-types.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-types.ts @@ -1,9 +1,13 @@ import { z } from "zod"; import { + CreateAcmeAccountBodySchema, CreateAcmeAccountResponseSchema, + CreateAcmeOrderBodySchema, CreateAcmeOrderResponseSchema, + DeactivateAcmeAccountBodySchema, DeactivateAcmeAccountResponseSchema, + FinalizeAcmeOrderBodySchema, FinalizeAcmeOrderResponseSchema, GetAcmeAuthorizationResponseSchema, GetAcmeDirectoryResponseSchema, @@ -23,15 +27,29 @@ export type TDownloadAcmeCertificateDTO = string; export type TGetAcmeAuthorizationResponse = z.infer; export type TRespondToAcmeChallengeResponse = z.infer; +// Payload types +export type TCreateAcmeAccountPayload = z.infer; +export type TCreateAcmeOrderPayload = z.infer; +export type TDeactivateAcmeAccountPayload = z.infer; +export type TFinalizeAcmeOrderPayload = z.infer; + export type TPkiAcmeServiceFactory = { getAcmeDirectory: (profileId: string) => Promise; getAcmeNewNonce: (profileId: string) => Promise; - createAcmeAccount: (profileId: string, body: unknown) => Promise; - createAcmeOrder: (profileId: string, body: unknown) => Promise; - deactivateAcmeAccount: (profileId: string, accountId: string) => Promise; + createAcmeAccount: (profileId: string, body: TCreateAcmeAccountPayload) => Promise; + createAcmeOrder: (profileId: string, body: TCreateAcmeOrderPayload) => Promise; + deactivateAcmeAccount: ( + profileId: string, + accountId: string, + body?: TDeactivateAcmeAccountPayload + ) => Promise; listAcmeOrders: (profileId: string, accountId: string) => Promise; getAcmeOrder: (profileId: string, orderId: string) => Promise; - finalizeAcmeOrder: (profileId: string, orderId: string, csr: string) => Promise; + finalizeAcmeOrder: ( + profileId: string, + orderId: string, + body: TFinalizeAcmeOrderPayload + ) => Promise; downloadAcmeCertificate: (profileId: string, orderId: string) => Promise; getAcmeAuthorization: (profileId: string, authzId: string) => Promise; respondToAcmeChallenge: (profileId: string, authzId: string) => Promise;