Refine routers

This commit is contained in:
Fang-Pen Lin
2025-11-07 09:18:23 -08:00
parent 3a7ba8d138
commit 80904b1dd1
4 changed files with 144 additions and 146 deletions
@@ -1,5 +1,26 @@
import { z } from "zod";
export enum AcmeIdentifierType {
DNS = "dns"
}
export enum AcmeOrderStatus {
Pending = "pending",
Processing = "processing",
Ready = "ready",
Valid = "valid",
Invalid = "invalid"
}
export enum AcmeAuthStatus {
Pending = "pending",
Valid = "valid",
Invalid = "invalid",
Deactivated = "deactivated",
Expired = "expired",
Revoked = "revoked"
}
export const ProtectedHeaderSchema = z
.object({
alg: z.string(),
@@ -20,13 +41,6 @@ export const RawJwsPayloadSchema = z.object({
signature: z.string()
});
// Directory endpoint
export const GetAcmeDirectorySchema = z.object({
params: z.object({
profileId: z.string().uuid()
})
});
export const GetAcmeDirectoryResponseSchema = z.object({
newNonce: z.string(),
newAccount: z.string(),
@@ -34,13 +48,6 @@ export const GetAcmeDirectoryResponseSchema = z.object({
revokeCert: z.string().optional()
});
// New Nonce endpoint
export const GetAcmeNewNonceSchema = z.object({
params: z.object({
profileId: z.string().uuid()
})
});
// New Account payload schema
export const CreateAcmeAccountBodySchema = z.object({
contact: z.array(z.string()).optional(),
@@ -73,22 +80,16 @@ export const CreateAcmeAccountResponseSchema = z.object({
export const CreateAcmeOrderBodySchema = z.object({
identifiers: z.array(
z.object({
type: z.string(),
value: z.string()
type: z
.string()
.regex(/^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*$/, "Invalid DNS identifier"),
value: z.enum(Object.values(AcmeIdentifierType) as [string, ...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: CreateAcmeOrderBodySchema
});
export const CreateAcmeOrderResponseSchema = z.object({
status: z.string(),
expires: z.string(),
@@ -108,41 +109,17 @@ 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: DeactivateAcmeAccountBodySchema
});
export const DeactivateAcmeAccountResponseSchema = z.object({
status: z.string()
});
// List Orders endpoint
export const ListAcmeOrdersSchema = z.object({
params: z.object({
profileId: z.string().uuid(),
accountId: z.string()
})
});
export const ListAcmeOrdersResponseSchema = z.object({
orders: z.array(z.string())
});
// Get Order endpoint
export const GetAcmeOrderSchema = z.object({
params: z.object({
profileId: z.string().uuid(),
orderId: z.string()
})
});
export const GetAcmeOrderResponseSchema = z.object({
status: z.string(),
status: z.enum(Object.values(AcmeOrderStatus) as [string, ...string[]]),
expires: z.string().optional(),
identifiers: z.array(
z.object({
@@ -160,17 +137,8 @@ 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: FinalizeAcmeOrderBodySchema
});
export const FinalizeAcmeOrderResponseSchema = z.object({
status: z.string(),
status: z.enum(Object.values(AcmeOrderStatus) as [string, ...string[]]),
expires: z.string().optional(),
identifiers: z.array(
z.object({
@@ -183,24 +151,8 @@ export const FinalizeAcmeOrderResponseSchema = z.object({
certificate: z.string().optional()
});
// Download Certificate endpoint
export const DownloadAcmeCertificateSchema = z.object({
params: z.object({
profileId: z.string().uuid(),
orderId: z.string()
})
});
// Get Authorization endpoint
export const GetAcmeAuthorizationSchema = z.object({
params: z.object({
profileId: z.string().uuid(),
authzId: z.string()
})
});
export const GetAcmeAuthorizationResponseSchema = z.object({
status: z.string(),
status: z.enum(Object.values(AcmeAuthStatus) as [string, ...string[]]),
expires: z.string().optional(),
identifier: z.object({
type: z.string(),
@@ -217,14 +169,6 @@ export const GetAcmeAuthorizationResponseSchema = z.object({
)
});
// Respond to Challenge endpoint
export const RespondToAcmeChallengeSchema = z.object({
params: z.object({
profileId: z.string().uuid(),
authzId: z.string()
})
});
export const RespondToAcmeChallengeResponseSchema = z.object({
type: z.string(),
url: z.string(),
@@ -19,8 +19,15 @@ import {
import { errors, flattenedVerify, FlattenedVerifyResult, importJWK, JWSHeaderParameters } from "jose";
import { z, ZodError } from "zod";
import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal";
import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal";
import { TPkiAcmeOrderDALFactory } from "./pki-acme-order-dal";
import { CreateAcmeAccountBodySchema, ProtectedHeaderSchema } from "./pki-acme-schemas";
import {
AcmeAuthStatus,
AcmeIdentifierType,
AcmeOrderStatus,
CreateAcmeAccountBodySchema,
ProtectedHeaderSchema
} from "./pki-acme-schemas";
import {
TAcmeResponse,
TAuthenciatedJwsPayload,
@@ -46,12 +53,14 @@ type TPkiAcmeServiceFactoryDep = {
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findById">;
acmeAccountDAL: Pick<TPkiAcmeAccountDALFactory, "findById" | "findByPublicKey" | "create">;
acmeOrderDAL: Pick<TPkiAcmeOrderDALFactory, "create">;
acmeAuthDAL: Pick<TPkiAcmeAuthDALFactory, "create">;
};
export const pkiAcmeServiceFactory = ({
certificateProfileDAL,
acmeAccountDAL,
acmeOrderDAL
acmeOrderDAL,
acmeAuthDAL
}: TPkiAcmeServiceFactoryDep): TPkiAcmeServiceFactory => {
const validateAcmeProfile = async (profileId: string): Promise<TCertificateProfileWithConfigs> => {
const profile = await certificateProfileDAL.findById(profileId);
@@ -168,11 +177,11 @@ export const pkiAcmeServiceFactory = ({
};
const getAcmeDirectory = async (profileId: string): Promise<TGetAcmeDirectoryResponse> => {
await validateAcmeProfile(profileId);
const profile = await validateAcmeProfile(profileId);
return {
newNonce: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/new-nonce`),
newAccount: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/new-account`),
newOrder: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/new-order`)
newNonce: buildUrl(`/api/v1/pki/acme/profiles/${profile.id}/new-nonce`),
newAccount: buildUrl(`/api/v1/pki/acme/profiles/${profile.id}/new-account`),
newOrder: buildUrl(`/api/v1/pki/acme/profiles/${profile.id}/new-order`)
};
};
@@ -244,15 +253,29 @@ export const pkiAcmeServiceFactory = ({
payload: TCreateAcmeOrderPayload;
}): Promise<TAcmeResponse<TCreateAcmeOrderResponse>> => {
const account = await acmeAccountDAL.findById(profileId, accountId)!;
// TODO: check and see if we have existing orders for this account that meet the criteria
// if we do, return the existing order
orders = await acmeOrderDAL.create({
profileId,
accountId,
status: "pending"
const order = await acmeOrderDAL.create({
accountId: account.id,
status: AcmeOrderStatus.Pending
});
payload.identifiers.forEach(async (identifier) => {
if (identifier.type === AcmeIdentifierType.DNS) {
// TODO: reuse existing authorizations for this identifier if they exist
const auth = await acmeAuthDAL.create({
accountId: account.id,
status: AcmeAuthStatus.Pending,
identifierType: identifier.type,
identifierValue: identifier.value,
// TODO: read config from the profile to get the expiration time instead
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
});
} else {
throw new AcmeMalformedError({ detail: "Only DNS identifiers are supported" });
}
});
// FIXME: Implement ACME new order creation
const orderId = "FIXME-order-id";
return {
@@ -262,10 +285,10 @@ export const pkiAcmeServiceFactory = ({
expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
identifiers: [],
authorizations: [],
finalize: buildUrl(`/api/v1/pki/acme/profiles/${profile.id}/orders/${orderId}/finalize`)
finalize: buildUrl(`/api/v1/pki/acme/profiles/${account.profileId}/orders/${orderId}/finalize`)
},
headers: {
Location: buildUrl(`/api/v1/pki/acme/profiles/${profile.id}/orders/${orderId}`)
Location: buildUrl(`/api/v1/pki/acme/profiles/${account.profileId}/orders/${orderId}`)
}
};
};