Add new option on legacy templates to issue new certificates

This commit is contained in:
Carlos Monastyrski
2025-11-05 22:01:38 -03:00
parent f77bdc5347
commit 283d7a702b
5 changed files with 78 additions and 26 deletions
@@ -90,7 +90,12 @@ export const useCreateCertTemplateV2 = () => {
return data.certificateTemplate; return data.certificateTemplate;
}, },
onSuccess: (_, { projectId }) => { onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); queryClient.invalidateQueries({
predicate: (query) => {
const [firstKey, queryProjectId] = query.queryKey;
return firstKey === "list-template" && queryProjectId === projectId;
}
});
} }
}); });
}; };
@@ -107,7 +112,12 @@ export const useUpdateCertTemplateV2 = () => {
return data.certificateTemplate; return data.certificateTemplate;
}, },
onSuccess: (_, { projectId }) => { onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); queryClient.invalidateQueries({
predicate: (query) => {
const [firstKey, queryProjectId] = query.queryKey;
return firstKey === "list-template" && queryProjectId === projectId;
}
});
} }
}); });
}; };
@@ -127,7 +137,12 @@ export const useDeleteCertTemplateV2 = () => {
return data.certificateTemplate; return data.certificateTemplate;
}, },
onSuccess: (_, { projectId }) => { onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); queryClient.invalidateQueries({
predicate: (query) => {
const [firstKey, queryProjectId] = query.queryKey;
return firstKey === "list-template" && queryProjectId === projectId;
}
});
} }
}); });
}; };
@@ -75,6 +75,7 @@ export type FormData = z.infer<typeof schema>;
type Props = { type Props = {
popUp: UsePopUpState<["certificate"]>; popUp: UsePopUpState<["certificate"]>;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["certificate"]>, state?: boolean) => void; handlePopUpToggle: (popUpName: keyof UsePopUpState<["certificate"]>, state?: boolean) => void;
preselectedTemplate?: { id: string; name: string };
}; };
type TCertificateDetails = { type TCertificateDetails = {
@@ -86,7 +87,7 @@ type TCertificateDetails = {
const CERT_TEMPLATE_NONE_VALUE = "none"; const CERT_TEMPLATE_NONE_VALUE = "none";
export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { export const CertificateModal = ({ popUp, handlePopUpToggle, preselectedTemplate }: Props) => {
const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null); const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null);
const { currentProject } = useProject(); const { currentProject } = useProject();
const { data: cert } = useGetCert( const { data: cert } = useGetCert(
@@ -147,13 +148,15 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
(cert.extendedKeyUsages || []).map((name) => [name, true]) (cert.extendedKeyUsages || []).map((name) => [name, true])
) )
}); });
} else { } else if (popUp?.certificate?.isOpen) {
const templateId = preselectedTemplate?.id || CERT_TEMPLATE_NONE_VALUE;
reset({ reset({
caId: "", caId: "",
commonName: "", commonName: "",
subjectAltNames: "", subjectAltNames: "",
ttl: "", ttl: "",
certificateTemplateId: CERT_TEMPLATE_NONE_VALUE, certificateTemplateId: templateId,
keyUsages: { keyUsages: {
[CertKeyUsage.DIGITAL_SIGNATURE]: true, [CertKeyUsage.DIGITAL_SIGNATURE]: true,
[CertKeyUsage.KEY_ENCIPHERMENT]: true [CertKeyUsage.KEY_ENCIPHERMENT]: true
@@ -161,7 +164,7 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
extendedKeyUsages: {} extendedKeyUsages: {}
}); });
} }
}, [cert]); }, [cert, preselectedTemplate, popUp?.certificate?.isOpen]);
useEffect(() => { useEffect(() => {
if (!cert && selectedCertTemplate) { if (!cert && selectedCertTemplate) {
@@ -269,15 +272,25 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
isRequired isRequired
> >
<Select <Select
defaultValue={field.value} value={field.value}
{...field}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full" className="w-full"
isDisabled={Boolean(cert)} isDisabled={Boolean(cert) || Boolean(preselectedTemplate)}
> >
<SelectItem value={CERT_TEMPLATE_NONE_VALUE} key="cert-template-none"> <SelectItem value={CERT_TEMPLATE_NONE_VALUE} key="cert-template-none">
None None
</SelectItem> </SelectItem>
{preselectedTemplate &&
!templatesData?.certificateTemplates?.find(
(t) => t.id === preselectedTemplate.id
) && (
<SelectItem
value={preselectedTemplate.id}
key={`cert-template-preselected-${preselectedTemplate.id}`}
>
{preselectedTemplate.name}
</SelectItem>
)}
{(templatesData?.certificateTemplates || []).map(({ id, name }) => ( {(templatesData?.certificateTemplates || []).map(({ id, name }) => (
<SelectItem value={id} key={`cert-template-${id}`}> <SelectItem value={id} key={`cert-template-${id}`}>
{name} {name}
@@ -17,7 +17,6 @@ import { CertificateImportModal } from "./CertificateImportModal";
import { CertificateIssuanceModal } from "./CertificateIssuanceModal"; import { CertificateIssuanceModal } from "./CertificateIssuanceModal";
import { CertificateManagePkiSyncsModal } from "./CertificateManagePkiSyncsModal"; import { CertificateManagePkiSyncsModal } from "./CertificateManagePkiSyncsModal";
import { CertificateManageRenewalModal } from "./CertificateManageRenewalModal"; import { CertificateManageRenewalModal } from "./CertificateManageRenewalModal";
import { CertificateModal } from "./CertificateModal";
import { CertificateRenewalModal } from "./CertificateRenewalModal"; import { CertificateRenewalModal } from "./CertificateRenewalModal";
import { CertificateRevocationModal } from "./CertificateRevocationModal"; import { CertificateRevocationModal } from "./CertificateRevocationModal";
import { CertificatesTable } from "./CertificatesTable"; import { CertificatesTable } from "./CertificatesTable";
@@ -26,12 +25,8 @@ export const CertificatesSection = () => {
const { currentProject } = useProject(); const { currentProject } = useProject();
const { mutateAsync: deleteCert } = useDeleteCert(); const { mutateAsync: deleteCert } = useDeleteCert();
// TODO: Use subscription.pkiLegacyTemplates to block legacy templates creation
const isLegacyTemplatesEnabled = true;
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"certificateIssuance", "certificateIssuance",
"certificate",
"certificateImport", "certificateImport",
"certificateCert", "certificateCert",
"deleteCertificate", "deleteCertificate",
@@ -76,9 +71,7 @@ export const CertificatesSection = () => {
colorSchema="primary" colorSchema="primary"
type="submit" type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => onClick={() => handlePopUpOpen("certificateIssuance")}
handlePopUpOpen(isLegacyTemplatesEnabled ? "certificate" : "certificateIssuance")
}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
Issue Issue
@@ -88,11 +81,7 @@ export const CertificatesSection = () => {
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>
<CertificatesTable handlePopUpOpen={handlePopUpOpen} /> <CertificatesTable handlePopUpOpen={handlePopUpOpen} />
{isLegacyTemplatesEnabled ? ( <CertificateIssuanceModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
) : (
<CertificateIssuanceModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
)}
<CertificateImportModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <CertificateImportModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateCertModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <CertificateCertModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateManageRenewalModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <CertificateManageRenewalModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
@@ -64,7 +64,7 @@ type Props = {
handlePopUpOpen: ( handlePopUpOpen: (
popUpName: keyof UsePopUpState< popUpName: keyof UsePopUpState<
[ [
"certificate", "certificateIssuance",
"deleteCertificate", "deleteCertificate",
"revokeCertificate", "revokeCertificate",
"certificateCert", "certificateCert",
@@ -297,7 +297,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50" !isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)} )}
onClick={async () => onClick={async () =>
handlePopUpOpen("certificate", { handlePopUpOpen("certificateIssuance", {
serialNumber: certificate.serialNumber serialNumber: certificate.serialNumber
}) })
} }
@@ -5,6 +5,7 @@ import {
faCertificate, faCertificate,
faCog, faCog,
faEllipsis, faEllipsis,
faFileContract,
faPencil, faPencil,
faPlus, faPlus,
faTrash faTrash
@@ -40,6 +41,7 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { import {
ProjectPermissionCertificateActions,
ProjectPermissionPkiTemplateActions, ProjectPermissionPkiTemplateActions,
ProjectPermissionSub, ProjectPermissionSub,
useProject, useProject,
@@ -50,6 +52,7 @@ import { useDeleteCertTemplateV2 } from "@app/hooks/api";
import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries"; import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries";
import { ProjectType } from "@app/hooks/api/projects/types"; import { ProjectType } from "@app/hooks/api/projects/types";
import { CertificateModal } from "../CertificatesPage/components/CertificateModal";
import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal"; import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal";
import { PkiTemplateForm } from "./components/PkiTemplateForm"; import { PkiTemplateForm } from "./components/PkiTemplateForm";
@@ -64,7 +67,8 @@ export const PkiTemplateListPage = () => {
"certificateTemplate", "certificateTemplate",
"deleteTemplate", "deleteTemplate",
"enrollmentOptions", "enrollmentOptions",
"estUpgradePlan" "estUpgradePlan",
"certificateFromTemplate"
] as const); ] as const);
const { subscription } = useSubscription(); const { subscription } = useSubscription();
@@ -160,6 +164,27 @@ export const PkiTemplateListPage = () => {
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="start" className="p-1">
<ProjectPermissionCan
I={ProjectPermissionCertificateActions.Create}
a={ProjectPermissionSub.Certificates}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed &&
"pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("certificateFromTemplate", template);
}}
disabled={!isAllowed}
icon={<FontAwesomeIcon icon={faFileContract} />}
>
Issue Certificate
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionPkiTemplateActions.Edit} I={ProjectPermissionPkiTemplateActions.Edit}
a={ProjectPermissionSub.CertificateTemplates} a={ProjectPermissionSub.CertificateTemplates}
@@ -284,6 +309,16 @@ export const PkiTemplateListPage = () => {
</ModalContent> </ModalContent>
</Modal> </Modal>
<CertificateTemplateEnrollmentModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <CertificateTemplateEnrollmentModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<CertificateModal
popUp={{
certificate: {
isOpen: popUp.certificateFromTemplate.isOpen,
data: popUp.certificateFromTemplate.data
}
}}
handlePopUpToggle={(_, state) => handlePopUpToggle("certificateFromTemplate", state)}
preselectedTemplate={popUp.certificateFromTemplate.data}
/>
</div> </div>
<UpgradePlanModal <UpgradePlanModal
isOpen={popUp.estUpgradePlan.isOpen} isOpen={popUp.estUpgradePlan.isOpen}