Fix cert profile edit missing enrollment config fields

This commit is contained in:
Carlos Monastyrski
2025-10-19 00:16:47 -03:00
parent 0df3181308
commit 2b169e9c95
12 changed files with 406 additions and 119 deletions
@@ -21,6 +21,7 @@ export const certificateProfileKeys = {
offset?: number;
search?: string;
includeMetrics?: boolean;
includeConfigs?: boolean;
expiringDays?: number;
}) => ["certificate-profiles", "list", params],
getById: (profileId: string) => ["certificate-profiles", "get-by-id", profileId],
@@ -50,6 +51,7 @@ export const useListCertificateProfiles = ({
offset = 0,
search,
includeMetrics = false,
includeConfigs = false,
expiringDays = 7
}: TListCertificateProfilesDTO) => {
return useQuery({
@@ -59,6 +61,7 @@ export const useListCertificateProfiles = ({
offset,
search,
includeMetrics,
includeConfigs,
expiringDays
}),
queryFn: async () => {
@@ -72,6 +75,7 @@ export const useListCertificateProfiles = ({
offset,
search,
includeMetrics,
includeConfigs,
expiringDays
}
});
@@ -23,14 +23,14 @@ export type TCertificateProfileWithDetails = TCertificateProfile & {
certificateTemplate?: {
id: string;
projectId: string;
slug: string;
name: string;
description?: string;
};
estConfig?: {
id: string;
disableBootstrapCaValidation: boolean;
hashedPassphrase: string;
encryptedCaChain: string;
passphrase: string;
caChain: string;
};
apiConfig?: {
id: string;
@@ -48,7 +48,7 @@ export type TCreateCertificateProfileDTO = {
enrollmentType: "api" | "est";
estConfig?: {
disableBootstrapCaValidation?: boolean;
passphraseInput: string;
passphrase: string;
caChain?: string;
};
apiConfig?: {
@@ -82,6 +82,7 @@ export type TListCertificateProfilesDTO = {
offset?: number;
search?: string;
includeMetrics?: boolean;
includeConfigs?: boolean;
expiringDays?: number;
};
@@ -10,7 +10,7 @@ import {
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import {
TCertificateProfile,
TCertificateProfileWithDetails,
useDeleteCertificateProfile
} from "@app/hooks/api/certificateProfiles";
@@ -23,7 +23,9 @@ export const CertificateProfilesTab = () => {
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedProfile, setSelectedProfile] = useState<TCertificateProfile | null>(null);
const [selectedProfile, setSelectedProfile] = useState<TCertificateProfileWithDetails | null>(
null
);
const deleteProfile = useDeleteCertificateProfile();
@@ -36,12 +38,12 @@ export const CertificateProfilesTab = () => {
setIsCreateModalOpen(true);
};
const handleEditProfile = (profile: TCertificateProfile) => {
const handleEditProfile = (profile: TCertificateProfileWithDetails) => {
setSelectedProfile(profile);
setIsEditModalOpen(true);
};
const handleDeleteProfile = (profile: TCertificateProfile) => {
const handleDeleteProfile = (profile: TCertificateProfileWithDetails) => {
setSelectedProfile(profile);
setIsDeleteModalOpen(true);
};
@@ -111,7 +113,9 @@ export const CertificateProfilesTab = () => {
title={`Delete Certificate Profile ${selectedProfile.slug}?`}
onChange={(isOpen) => {
setIsDeleteModalOpen(isOpen);
if (!isOpen) setSelectedProfile(null);
if (!isOpen) {
setSelectedProfile(null);
}
}}
deleteKey={selectedProfile.slug}
onDeleteApproved={handleDeleteConfirm}
@@ -1,3 +1,4 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
@@ -47,7 +48,7 @@ const createSchema = z
estConfig: z
.object({
disableBootstrapCaValidation: z.boolean().optional(),
passphraseInput: z.string().min(1, "EST passphrase is required"),
passphrase: z.string().min(1, "EST passphrase is required"),
caChain: z.string().min(1, "EST CA chain is required").optional()
})
.refine(
@@ -107,7 +108,7 @@ const editSchema = z
estConfig: z
.object({
disableBootstrapCaValidation: z.boolean().optional(),
passphraseInput: z.string().optional(),
passphrase: z.string().optional(),
caChain: z.string().optional()
})
.optional(),
@@ -174,8 +175,8 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
? {
disableBootstrapCaValidation:
profile.estConfig?.disableBootstrapCaValidation || false,
passphraseInput: "",
caChain: profile.estConfig?.encryptedCaChain || ""
passphrase: profile.estConfig?.passphrase || "",
caChain: profile.estConfig?.caChain || ""
}
: undefined,
apiConfig:
@@ -203,6 +204,34 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
const watchedDisableBootstrapValidation = watch("estConfig.disableBootstrapCaValidation");
const watchedAutoRenew = watch("apiConfig.autoRenew");
useEffect(() => {
if (isEdit && profile) {
reset({
slug: profile.slug,
description: profile.description || "",
enrollmentType: profile.enrollmentType,
certificateAuthorityId: profile.caId,
certificateTemplateId: profile.certificateTemplateId,
estConfig:
profile.enrollmentType === "est"
? {
disableBootstrapCaValidation:
profile.estConfig?.disableBootstrapCaValidation || false,
passphrase: profile.estConfig?.passphrase || "",
caChain: profile.estConfig?.caChain || ""
}
: undefined,
apiConfig:
profile.enrollmentType === "api"
? {
autoRenew: profile.apiConfig?.autoRenew || false,
autoRenewDays: profile.apiConfig?.autoRenewDays || 30
}
: undefined
});
}
}, [isEdit, profile, reset]);
const onFormSubmit = async (data: FormData) => {
try {
if (!currentProject?.id && !isEdit) return;
@@ -237,7 +266,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
if (data.enrollmentType === "est" && data.estConfig) {
createData.estConfig = {
passphraseInput: data.estConfig.passphraseInput,
passphrase: data.estConfig.passphrase,
caChain: data.estConfig.caChain || undefined,
disableBootstrapCaValidation: data.estConfig.disableBootstrapCaValidation
};
@@ -354,7 +383,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
if (watchedEnrollmentType === "est") {
setValue("estConfig", {
disableBootstrapCaValidation: false,
passphraseInput: ""
passphrase: ""
});
setValue("apiConfig", undefined);
} else {
@@ -398,7 +427,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
setValue("apiConfig", undefined);
setValue("estConfig", {
disableBootstrapCaValidation: false,
passphraseInput: ""
passphrase: ""
});
} else {
setValue("estConfig", undefined);
@@ -450,7 +479,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
<Controller
control={control}
name="estConfig.passphraseInput"
name="estConfig.passphrase"
render={({ field, fieldState: { error } }) => (
<FormControl
label="EST Passphrase"
@@ -543,7 +572,6 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 365) {
field.onChange(parsed);
} else {
// Preserve the original field value instead of defaulting to 30
field.onChange(field.value || "");
}
}
@@ -11,15 +11,15 @@ import {
} from "@app/components/v2";
import { useProject } from "@app/context";
import {
TCertificateProfile,
TCertificateProfileWithDetails,
useListCertificateProfiles
} from "@app/hooks/api/certificateProfiles";
import { ProfileRow } from "./ProfileRow";
interface Props {
onEditProfile: (profile: TCertificateProfile) => void;
onDeleteProfile: (profile: TCertificateProfile) => void;
onEditProfile: (profile: TCertificateProfileWithDetails) => void;
onDeleteProfile: (profile: TCertificateProfileWithDetails) => void;
}
export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => {
@@ -29,6 +29,7 @@ export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => {
projectId: currentProject?.id || "",
limit: 100,
offset: 0,
includeConfigs: true,
includeMetrics: true
});