Remove ACMESANType usage on redundant issuance param

This commit is contained in:
Carlos Monastyrski
2025-12-02 16:26:36 -03:00
parent 6772eb5478
commit ec115bc4fb
9 changed files with 16 additions and 191 deletions

View File

@@ -11,12 +11,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { addNoCacheHeaders } from "@app/server/lib/caching"; import { addNoCacheHeaders } from "@app/server/lib/caching";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { import { CertKeyAlgorithm, CertSignatureAlgorithm, CrlReason } from "@app/services/certificate/certificate-types";
ACMESANType,
CertKeyAlgorithm,
CertSignatureAlgorithm,
CrlReason
} from "@app/services/certificate/certificate-types";
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums"; import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators"; import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
import { import {
@@ -129,18 +124,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
.optional(), .optional(),
signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(), signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(),
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional(), keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional(),
subjectAlternativeNames: z
.array(
z.object({
type: z.nativeEnum(ACMESANType),
value: z
.string()
.trim()
.min(1, "SAN value cannot be empty")
.max(255, "SAN value must be less than 255 characters")
})
)
.optional(),
ttl: z ttl: z
.string() .string()
.trim() .trim()
@@ -199,19 +182,9 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
useOrderFlow = caType !== CaType.INTERNAL; useOrderFlow = caType !== CaType.INTERNAL;
} }
if (attributes?.subjectAlternativeNames?.length || useOrderFlow) { if (useOrderFlow) {
let acmeAltNames: Array<{ type: ACMESANType; value: string }> | undefined = attributes?.subjectAlternativeNames;
if (useOrderFlow && !attributes?.subjectAlternativeNames && attributes?.altNames?.length) {
acmeAltNames = attributes.altNames.map((alt: { type: CertSubjectAlternativeNameType; value: string }) => ({
type: (alt.type === CertSubjectAlternativeNameType.DNS_NAME
? ACMESANType.DNS
: ACMESANType.IP) as ACMESANType,
value: alt.value
}));
}
const certificateOrderObject = { const certificateOrderObject = {
altNames: acmeAltNames || [], altNames: attributes?.altNames || [],
validity: { ttl: attributes?.ttl || "" }, validity: { ttl: attributes?.ttl || "" },
commonName: attributes?.commonName, commonName: attributes?.commonName,
keyUsages: attributes?.keyUsages, keyUsages: attributes?.keyUsages,
@@ -579,7 +552,7 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
profileId: z.string().uuid(), profileId: z.string().uuid(),
subjectAlternativeNames: z.array( subjectAlternativeNames: z.array(
z.object({ z.object({
type: z.nativeEnum(ACMESANType), type: z.nativeEnum(CertSubjectAlternativeNameType),
value: z value: z
.string() .string()
.trim() .trim()

View File

@@ -7,7 +7,7 @@ import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter"; import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { ACMESANType, CertKeyAlgorithm, CertSignatureAlgorithm } from "@app/services/certificate/certificate-types"; import { CertKeyAlgorithm, CertSignatureAlgorithm } from "@app/services/certificate/certificate-types";
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators"; import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
import { import {
CertExtendedKeyUsageType, CertExtendedKeyUsageType,
@@ -305,7 +305,7 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
profileId: z.string().uuid(), profileId: z.string().uuid(),
subjectAlternativeNames: z.array( subjectAlternativeNames: z.array(
z.object({ z.object({
type: z.nativeEnum(ACMESANType), type: z.nativeEnum(CertSubjectAlternativeNameType),
value: z value: z
.string() .string()
.trim() .trim()

View File

@@ -208,7 +208,7 @@ export const certificateIssuanceQueueFactory = ({
const [, generatedCsr] = await acme.crypto.createCsr( const [, generatedCsr] = await acme.crypto.createCsr(
{ {
altNames: altNames || [], altNames: altNames ? [...altNames] : [],
commonName: commonName || "" commonName: commonName || ""
}, },
skLeaf skLeaf

View File

@@ -11,7 +11,7 @@ import { TPkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-ac
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
import { ACMESANType, CertStatus } from "@app/services/certificate/certificate-types"; import { CertStatus } from "@app/services/certificate/certificate-types";
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums"; import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums";
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service"; import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
@@ -19,7 +19,8 @@ import {
CertExtendedKeyUsageType, CertExtendedKeyUsageType,
CertIncludeType, CertIncludeType,
CertKeyUsageType, CertKeyUsageType,
CertSubjectAttributeType CertSubjectAttributeType,
CertSubjectAlternativeNameType
} from "@app/services/certificate-common/certificate-constants"; } from "@app/services/certificate-common/certificate-constants";
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
import { EnrollmentType, IssuerType } from "@app/services/certificate-profile/certificate-profile-types"; import { EnrollmentType, IssuerType } from "@app/services/certificate-profile/certificate-profile-types";
@@ -853,7 +854,7 @@ describe("CertificateV3Service", () => {
describe("orderCertificateFromProfile", () => { describe("orderCertificateFromProfile", () => {
const mockCertificateOrder = { const mockCertificateOrder = {
altNames: [{ type: ACMESANType.DNS, value: "example.com" }], altNames: [{ type: CertSubjectAlternativeNameType.DNS_NAME, value: "example.com" }],
validity: { ttl: "30d" }, validity: { ttl: "30d" },
commonName: "example.com", commonName: "example.com",
keyUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], keyUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],

View File

@@ -1054,7 +1054,7 @@ export const certificateV3ServiceFactory = ({
tx tx
}); });
const certificateRecord = await certificateDAL.findById(certResult.certificateId); const certificateRecord = await certificateDAL.findById(certResult.certificateId, tx);
if (!certificateRecord) { if (!certificateRecord) {
throw new NotFoundError({ message: "Certificate was issued but could not be found in database" }); throw new NotFoundError({ message: "Certificate was issued but could not be found in database" });
} }
@@ -1307,25 +1307,7 @@ export const certificateV3ServiceFactory = ({
commonName: certificateOrder.commonName, commonName: certificateOrder.commonName,
keyUsages: certificateOrder.keyUsages, keyUsages: certificateOrder.keyUsages,
extendedKeyUsages: certificateOrder.extendedKeyUsages, extendedKeyUsages: certificateOrder.extendedKeyUsages,
subjectAlternativeNames: certificateOrder.altNames?.map((san) => { subjectAlternativeNames: certificateOrder.altNames,
let certType: CertSubjectAlternativeNameType;
switch (san.type) {
case "dns":
certType = CertSubjectAlternativeNameType.DNS_NAME;
break;
case "ip":
certType = CertSubjectAlternativeNameType.IP_ADDRESS;
break;
default:
throw new BadRequestError({
message: `Unsupported Subject Alternative Name type: ${san.type as string}`
});
}
return {
type: certType,
value: san.value
};
}),
validity: certificateOrder.validity, validity: certificateOrder.validity,
notBefore: certificateOrder.notBefore, notBefore: certificateOrder.notBefore,
notAfter: certificateOrder.notAfter, notAfter: certificateOrder.notAfter,

View File

@@ -1,6 +1,5 @@
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
import { ACMESANType } from "../certificate/certificate-types";
import { import {
CertExtendedKeyUsageType, CertExtendedKeyUsageType,
CertKeyUsageType, CertKeyUsageType,
@@ -45,7 +44,7 @@ export type TOrderCertificateFromProfileDTO = {
profileId: string; profileId: string;
certificateOrder: { certificateOrder: {
altNames: Array<{ altNames: Array<{
type: ACMESANType; type: CertSubjectAlternativeNameType;
value: string; value: string;
}>; }>;
validity: { validity: {

View File

@@ -7,4 +7,4 @@ export {
useRevokeCert, useRevokeCert,
useUpdateRenewalConfig useUpdateRenewalConfig
} from "./mutations"; } from "./mutations";
export { useGetCert, useGetCertBody, useGetCertificateRequest } from "./queries"; export { useGetCert, useGetCertBody } from "./queries";

View File

@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { TCertificate, TCertificateRequestDetails } from "./types"; import { TCertificate } from "./types";
export const certKeys = { export const certKeys = {
getCertById: (serialNumber: string) => [{ serialNumber }, "cert"], getCertById: (serialNumber: string) => [{ serialNumber }, "cert"],
@@ -59,23 +59,3 @@ export const useGetCertBundle = (serialNumber: string) => {
enabled: Boolean(serialNumber) enabled: Boolean(serialNumber)
}); });
}; };
export const useGetCertificateRequest = (requestId: string, projectSlug: string) => {
return useQuery({
queryKey: certKeys.getCertificateRequest(requestId, projectSlug),
queryFn: async () => {
const { data } = await apiRequest.get<TCertificateRequestDetails>(
`/api/v3/pki/certificates/requests/${requestId}`,
{
params: { projectSlug }
}
);
return data;
},
enabled: Boolean(requestId) && Boolean(projectSlug),
refetchInterval: (query) => {
// Only refetch if status is pending
return query.state.data?.status === "pending" ? 5000 : false;
}
});
};

View File

@@ -1,110 +0,0 @@
import { useEffect } from "react";
import { faCheck, faExclamationTriangle, faSpinner } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useProject } from "@app/context";
import { useGetCertificateRequest } from "@app/hooks/api/certificates";
type CertificateInfo = {
id: string;
serialNumber: string;
commonName: string;
notAfter: string;
[key: string]: unknown;
};
type Props = {
requestId: string;
onCertificateIssued?: (certificate: CertificateInfo) => void;
};
export const CertificateRequestTracker = ({ requestId, onCertificateIssued }: Props) => {
const { currentProject } = useProject();
const { data: requestData, isLoading } = useGetCertificateRequest(
requestId,
currentProject?.slug || ""
);
useEffect(() => {
if (requestData?.status === "issued" && requestData.certificate && onCertificateIssued) {
onCertificateIssued(requestData.certificate);
}
}, [requestData, onCertificateIssued]);
if (isLoading) {
return (
<div className="flex items-center space-x-2">
<FontAwesomeIcon icon={faSpinner} className="animate-spin text-primary" />
<span className="text-sm text-mineshaft-400">Loading request status...</span>
</div>
);
}
const getStatusIcon = () => {
switch (requestData?.status) {
case "pending":
return <FontAwesomeIcon icon={faSpinner} className="animate-spin text-yellow-500" />;
case "issued":
return <FontAwesomeIcon icon={faCheck} className="text-green-500" />;
case "failed":
return <FontAwesomeIcon icon={faExclamationTriangle} className="text-red-500" />;
default:
return null;
}
};
const getStatusMessage = () => {
switch (requestData?.status) {
case "pending":
return "Certificate request is being processed...";
case "issued":
return "Certificate has been issued successfully!";
case "failed":
return `Certificate request failed: ${requestData.errorMessage || "Unknown error"}`;
default:
return "Unknown status";
}
};
return (
<div className="space-y-4">
<div className="flex items-center space-x-2">
{getStatusIcon()}
<span className="text-sm font-medium text-mineshaft-300">
Certificate Request ID: {requestId}
</span>
</div>
<div className="text-sm text-mineshaft-400">
Status: <span className="font-medium capitalize">{requestData?.status || "Unknown"}</span>
</div>
<div className="text-sm text-mineshaft-400">{getStatusMessage()}</div>
{requestData?.status === "issued" && requestData.certificate && (
<div className="mt-4 rounded-md border border-green-500/30 bg-green-900/20 p-3">
<div className="text-sm text-green-400">
<strong>Certificate Details:</strong>
<br />
Serial Number: {requestData.certificate.serialNumber}
<br />
Common Name: {requestData.certificate.commonName}
<br />
Valid Until: {new Date(requestData.certificate.notAfter).toLocaleDateString()}
</div>
</div>
)}
{requestData?.status === "failed" && requestData.errorMessage && (
<div className="mt-4 rounded-md border border-red-500/30 bg-red-900/20 p-3">
<div className="text-sm text-red-400">
<strong>Error Details:</strong>
<br />
{requestData.errorMessage}
</div>
</div>
)}
</div>
);
};