import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, Checkbox, FormControl, FormLabel, Input, Modal, ModalContent, Select, SelectItem, Tooltip } from "@app/components/v2"; import { useProject } from "@app/context"; import { CaStatus, useCreateCertTemplate, useGetCaById, useGetCertTemplate, useListWorkspaceCas, useListWorkspacePkiCollections, useUpdateCertTemplate } from "@app/hooks/api"; import { caTypeToNameMap } from "@app/hooks/api/ca/constants"; import { EXTENDED_KEY_USAGES_OPTIONS, KEY_USAGES_OPTIONS } from "@app/hooks/api/certificates/constants"; import { CertExtendedKeyUsage, CertKeyUsage } from "@app/hooks/api/certificates/enums"; import { UsePopUpState } from "@app/hooks/usePopUp"; const validateTemplateRegexField = z.string().trim().min(1).max(100); const schema = z.object({ caId: z.string(), collectionId: z.string().optional(), name: z.string().min(1), commonName: validateTemplateRegexField, subjectAlternativeName: validateTemplateRegexField, ttl: z.string().trim().min(1), keyUsages: z.object({ [CertKeyUsage.DIGITAL_SIGNATURE]: z.boolean().optional(), [CertKeyUsage.KEY_ENCIPHERMENT]: z.boolean().optional(), [CertKeyUsage.NON_REPUDIATION]: z.boolean().optional(), [CertKeyUsage.DATA_ENCIPHERMENT]: z.boolean().optional(), [CertKeyUsage.KEY_AGREEMENT]: z.boolean().optional(), [CertKeyUsage.KEY_CERT_SIGN]: z.boolean().optional(), [CertKeyUsage.CRL_SIGN]: z.boolean().optional(), [CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(), [CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional() }), extendedKeyUsages: z.object({ [CertExtendedKeyUsage.CLIENT_AUTH]: z.boolean().optional(), [CertExtendedKeyUsage.CODE_SIGNING]: z.boolean().optional(), [CertExtendedKeyUsage.EMAIL_PROTECTION]: z.boolean().optional(), [CertExtendedKeyUsage.OCSP_SIGNING]: z.boolean().optional(), [CertExtendedKeyUsage.SERVER_AUTH]: z.boolean().optional(), [CertExtendedKeyUsage.TIMESTAMPING]: z.boolean().optional() }) }); export type FormData = z.infer; type Props = { caId: string; popUp: UsePopUpState<["certificateTemplate"]>; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["certificateTemplate"]>, state?: boolean ) => void; }; export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Props) => { const { currentProject } = useProject(); const { data: ca } = useGetCaById(caId); const { data: certTemplate } = useGetCertTemplate( (popUp?.certificateTemplate?.data as { id: string })?.id || "" ); const { data: cas } = useListWorkspaceCas({ projectId: currentProject?.id, status: CaStatus.ACTIVE }); const { data: collectionsData } = useListWorkspacePkiCollections({ projectId: currentProject?.id || "" }); const { mutateAsync: createCertTemplate } = useCreateCertTemplate(); const { mutateAsync: updateCertTemplate } = useUpdateCertTemplate(); const { control, handleSubmit, reset, formState: { isSubmitting } } = useForm({ resolver: zodResolver(schema), defaultValues: { keyUsages: { [CertKeyUsage.DIGITAL_SIGNATURE]: true, [CertKeyUsage.KEY_ENCIPHERMENT]: true } } }); useEffect(() => { if (certTemplate) { reset({ caId: certTemplate.caId, name: certTemplate.name, commonName: certTemplate.commonName, subjectAlternativeName: certTemplate.subjectAlternativeName, collectionId: certTemplate.pkiCollectionId ?? undefined, ttl: certTemplate.ttl, keyUsages: Object.fromEntries(certTemplate.keyUsages.map((name) => [name, true]) ?? []), extendedKeyUsages: Object.fromEntries( certTemplate.extendedKeyUsages.map((name) => [name, true]) ?? [] ) }); } else { reset({ caId, name: "", commonName: "", ttl: "", keyUsages: { [CertKeyUsage.DIGITAL_SIGNATURE]: true, [CertKeyUsage.KEY_ENCIPHERMENT]: true }, extendedKeyUsages: {} }); } }, [certTemplate, ca]); const onFormSubmit = async ({ collectionId, name, commonName, subjectAlternativeName, ttl, keyUsages, extendedKeyUsages }: FormData) => { if (!currentProject?.id) { return; } try { if (certTemplate) { await updateCertTemplate({ id: certTemplate.id, projectId: currentProject.id, pkiCollectionId: collectionId, caId, name, commonName, subjectAlternativeName, ttl, keyUsages: Object.entries(keyUsages) .filter(([, value]) => value) .map(([key]) => key as CertKeyUsage), extendedKeyUsages: Object.entries(extendedKeyUsages) .filter(([, value]) => value) .map(([key]) => key as CertExtendedKeyUsage) }); createNotification({ text: "Successfully updated certificate template", type: "success" }); } else { await createCertTemplate({ projectId: currentProject.id, pkiCollectionId: collectionId, caId, name, commonName, subjectAlternativeName, ttl, keyUsages: Object.entries(keyUsages) .filter(([, value]) => value) .map(([key]) => key as CertKeyUsage), extendedKeyUsages: Object.entries(extendedKeyUsages) .filter(([, value]) => value) .map(([key]) => key as CertExtendedKeyUsage) }); createNotification({ text: "Successfully created certificate template", type: "success" }); } reset(); handlePopUpToggle("certificateTemplate", false); } catch (err) { console.error(err); createNotification({ text: "Failed to save changes", type: "error" }); } }; return ( { handlePopUpToggle("certificateTemplate", isOpen); reset(); }} >
{certTemplate && ( )} ( )} /> ( )} /> ( )} /> ( This field accepts limited regular expressions: spaces, *, ., @, -, \ (for escaping), and alphanumeric characters only } > } /> } isError={Boolean(error)} errorText={error?.message} isRequired > )} /> ( This field accepts limited regular expressions: spaces, *, ., @, -, \ (for escaping), and alphanumeric characters only } > } /> } isError={Boolean(error)} errorText={error?.message} isRequired > )} /> ( )} />
Key Usage
{ return (
{KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => { return ( { onChange({ ...value, [optionValue]: state }); }} > {label} ); })}
); }} /> { return (
{EXTENDED_KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => { return ( { onChange({ ...value, [optionValue]: state }); }} > {label} ); })}
); }} />
); };