mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 13:29:12 +00:00
Address greptile comments
This commit is contained in:
@@ -235,7 +235,7 @@ export type TOrderCertificateResponse = {
|
||||
url: string;
|
||||
token: string;
|
||||
validated?: string;
|
||||
error?: any;
|
||||
error?: string | Error;
|
||||
}>;
|
||||
}>;
|
||||
certificate?: string;
|
||||
|
||||
@@ -30,7 +30,7 @@ export type TCertificateProfileWithDetails = TCertificateProfile & {
|
||||
id: string;
|
||||
disableBootstrapCaValidation: boolean;
|
||||
hashedPassphrase: string;
|
||||
encryptedCaChain: any;
|
||||
encryptedCaChain: string;
|
||||
};
|
||||
apiConfig?: {
|
||||
id: string;
|
||||
@@ -108,10 +108,10 @@ export type TProfileCertificate = {
|
||||
serialNumber: string;
|
||||
cn: string;
|
||||
status: string;
|
||||
notBefore: Date;
|
||||
notAfter: Date;
|
||||
notBefore: string;
|
||||
notAfter: string;
|
||||
isRevoked: boolean;
|
||||
createdAt: Date;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type TGetProfileCertificatesDTO = {
|
||||
|
||||
@@ -146,8 +146,10 @@ export type TCertificateTemplateV2Policy = {
|
||||
denied?: string[];
|
||||
};
|
||||
algorithms?: {
|
||||
signature?: string[];
|
||||
keyAlgorithm?: string[];
|
||||
signature?: Array<
|
||||
"SHA256-RSA" | "SHA384-RSA" | "SHA512-RSA" | "SHA256-ECDSA" | "SHA384-ECDSA" | "SHA512-ECDSA"
|
||||
>;
|
||||
keyAlgorithm?: Array<"RSA-2048" | "RSA-3072" | "RSA-4096" | "ECDSA-P256" | "ECDSA-P384">;
|
||||
};
|
||||
validity?: {
|
||||
max?: string;
|
||||
|
||||
@@ -109,7 +109,7 @@ export const PkiManagerLayout = () => {
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faStamp} />
|
||||
</div>
|
||||
Certificates Authorities
|
||||
Certificate Authorities
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@@ -60,12 +60,7 @@ const schema = z
|
||||
commonName: z.string(),
|
||||
notAfter: z.string().trim().refine(isValidDate, { message: "Invalid date format" }),
|
||||
maxPathLength: z.string(),
|
||||
keyAlgorithm: z.enum([
|
||||
CertKeyAlgorithm.RSA_2048,
|
||||
CertKeyAlgorithm.RSA_4096,
|
||||
CertKeyAlgorithm.ECDSA_P256,
|
||||
CertKeyAlgorithm.ECDSA_P384
|
||||
])
|
||||
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm)
|
||||
})
|
||||
.required()
|
||||
})
|
||||
@@ -145,13 +140,11 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
maxPathLength: ca.configuration.maxPathLength
|
||||
? String(ca.configuration.maxPathLength)
|
||||
: "",
|
||||
keyAlgorithm:
|
||||
ca.configuration.keyAlgorithm === CertKeyAlgorithm.RSA_2048 ||
|
||||
ca.configuration.keyAlgorithm === CertKeyAlgorithm.RSA_4096 ||
|
||||
ca.configuration.keyAlgorithm === CertKeyAlgorithm.ECDSA_P256 ||
|
||||
ca.configuration.keyAlgorithm === CertKeyAlgorithm.ECDSA_P384
|
||||
? ca.configuration.keyAlgorithm
|
||||
: CertKeyAlgorithm.RSA_2048
|
||||
keyAlgorithm: (Object.values(CertKeyAlgorithm) as string[]).includes(
|
||||
ca.configuration.keyAlgorithm
|
||||
)
|
||||
? ca.configuration.keyAlgorithm
|
||||
: CertKeyAlgorithm.RSA_2048
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
+9
-8
@@ -112,9 +112,11 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
const [allowedKeyAlgorithms, setAllowedKeyAlgorithms] = useState<string[]>([]);
|
||||
const { currentProject } = useProject();
|
||||
|
||||
const { data: cert } = useGetCert(
|
||||
(popUp?.certificateIssuance?.data as { serialNumber: string })?.serialNumber || ""
|
||||
);
|
||||
const inputSerialNumber =
|
||||
(popUp?.certificateIssuance?.data as { serialNumber: string })?.serialNumber || "";
|
||||
const sanitizedSerialNumber = inputSerialNumber.replace(/[^a-fA-F0-9:]/g, "");
|
||||
|
||||
const { data: cert } = useGetCert(sanitizedSerialNumber);
|
||||
|
||||
const { data: profilesData } = useListCertificateProfiles({
|
||||
projectId: currentProject?.id || "",
|
||||
@@ -263,15 +265,14 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
|
||||
useEffect(() => {
|
||||
if (cert) {
|
||||
const subjectAttrs: Array<{ type: string; value: string }> = [];
|
||||
if (cert.commonName) subjectAttrs.push({ type: "common_name", value: cert.commonName });
|
||||
const subjectAttrs: Array<{ type: "common_name"; value: string }> = [];
|
||||
if (cert.commonName)
|
||||
subjectAttrs.push({ type: "common_name" as const, value: cert.commonName });
|
||||
|
||||
reset({
|
||||
profileId: "",
|
||||
subjectAttributes:
|
||||
subjectAttrs.length > 0
|
||||
? (subjectAttrs as any)
|
||||
: [{ type: "common_name" as const, value: "" }],
|
||||
subjectAttrs.length > 0 ? subjectAttrs : [{ type: "common_name" as const, value: "" }],
|
||||
subjectAltNames: cert.subjectAltNames
|
||||
? cert.subjectAltNames.split(",").map((name) => {
|
||||
const trimmed = name.trim();
|
||||
|
||||
@@ -2,15 +2,16 @@ import { useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { ContentLoader, PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { useProject } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
||||
|
||||
import { CertificateProfilesTab } from "./components/CertificateProfilesTab";
|
||||
import { CertificateTemplatesV2Tab } from "./components/CertificateTemplatesV2Tab";
|
||||
|
||||
enum TabSections {
|
||||
CertificateTemplatesV2 = "templates-v2",
|
||||
CertificateProfiles = "profiles"
|
||||
CertificateProfiles = "profiles",
|
||||
CertificateTemplatesV2 = "templates-v2"
|
||||
}
|
||||
|
||||
export const PoliciesPage = () => {
|
||||
@@ -23,34 +24,53 @@ export const PoliciesPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Certificate Policies" })}</title>
|
||||
</Helmet>
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Certificate Policies"
|
||||
description="Manage certificate templates and profiles for unified certificate issuance"
|
||||
/>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as TabSections)}>
|
||||
<TabList className="mb-6 w-full">
|
||||
<div className="flex w-full border-b border-mineshaft-600">
|
||||
<Tab value={TabSections.CertificateProfiles}>Certificate Profiles</Tab>
|
||||
<Tab value={TabSections.CertificateTemplatesV2}>Certificate Templates</Tab>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Read}
|
||||
a={ProjectPermissionSub.CertificateAuthorities}
|
||||
>
|
||||
{(isAllowed) => {
|
||||
if (!isAllowed) {
|
||||
return (
|
||||
<div className="container mx-auto flex h-full flex-col justify-center bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl text-center">
|
||||
<p>You don't have permission to access certificate policies.</p>
|
||||
</div>
|
||||
</div>
|
||||
</TabList>
|
||||
);
|
||||
}
|
||||
|
||||
<TabPanel value={TabSections.CertificateProfiles}>
|
||||
<CertificateProfilesTab />
|
||||
</TabPanel>
|
||||
return (
|
||||
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Certificate Policies" })}</title>
|
||||
</Helmet>
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Certificate Policies"
|
||||
description="Manage certificate templates and profiles for unified certificate issuance"
|
||||
/>
|
||||
|
||||
<TabPanel value={TabSections.CertificateTemplatesV2}>
|
||||
<CertificateTemplatesV2Tab />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as TabSections)}>
|
||||
<TabList className="mb-6 w-full">
|
||||
<div className="flex w-full border-b border-mineshaft-600">
|
||||
<Tab value={TabSections.CertificateProfiles}>Certificate Profiles</Tab>
|
||||
<Tab value={TabSections.CertificateTemplatesV2}>Certificate Templates</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
|
||||
<TabPanel value={TabSections.CertificateProfiles}>
|
||||
<CertificateProfilesTab />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={TabSections.CertificateTemplatesV2}>
|
||||
<CertificateTemplatesV2Tab />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ProjectPermissionCan>
|
||||
);
|
||||
};
|
||||
|
||||
+5
-2
@@ -60,7 +60,10 @@ export const CertificateProfilesTab = () => {
|
||||
type: "success"
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete profile:", error);
|
||||
console.error(
|
||||
`Failed to delete profile "${selectedProfile.slug}" (ID: ${selectedProfile.id}):`,
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,7 +81,7 @@ export const CertificateProfilesTab = () => {
|
||||
{canCreateProfile && (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
type="button"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={handleCreateProfile}
|
||||
>
|
||||
|
||||
+27
-6
@@ -18,6 +18,8 @@ import { useProject } from "@app/context";
|
||||
import { useListCasByProjectId } from "@app/hooks/api/ca/queries";
|
||||
import {
|
||||
TCertificateProfileWithDetails,
|
||||
TCreateCertificateProfileDTO,
|
||||
TUpdateCertificateProfileDTO,
|
||||
useCreateCertificateProfile,
|
||||
useUpdateCertificateProfile
|
||||
} from "@app/hooks/api/certificateProfiles";
|
||||
@@ -163,7 +165,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
if (!currentProject?.id && !isEdit) return;
|
||||
|
||||
if (isEdit) {
|
||||
const updateData: any = {
|
||||
const updateData: TUpdateCertificateProfileDTO = {
|
||||
profileId: profile.id,
|
||||
slug: data.slug,
|
||||
description: data.description
|
||||
@@ -177,8 +179,12 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
|
||||
await updateProfile.mutateAsync(updateData);
|
||||
} else {
|
||||
const createData: any = {
|
||||
projectId: currentProject!.id,
|
||||
if (!currentProject?.id) {
|
||||
throw new Error("Project ID is required for creating a profile");
|
||||
}
|
||||
|
||||
const createData: TCreateCertificateProfileDTO = {
|
||||
projectId: currentProject.id,
|
||||
slug: data.slug,
|
||||
description: data.description,
|
||||
enrollmentType: data.enrollmentType,
|
||||
@@ -273,9 +279,11 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
position="popper"
|
||||
isDisabled={Boolean(isEdit)}
|
||||
>
|
||||
{certificateAuthorities.map((ca: any) => (
|
||||
{certificateAuthorities.map((ca) => (
|
||||
<SelectItem key={ca.id} value={ca.id}>
|
||||
{ca.friendlyName || ca.name || ca.commonName}
|
||||
{ca.type === "internal" && ca.configuration.friendlyName
|
||||
? ca.configuration.friendlyName
|
||||
: ca.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
@@ -481,7 +489,20 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
max="365"
|
||||
className="w-full"
|
||||
isDisabled={!watchedAutoRenew}
|
||||
onChange={(e) => field.onChange(parseInt(e.target.value, 10) || 30)}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
if (value === "") {
|
||||
field.onChange("");
|
||||
} else {
|
||||
const parsed = parseInt(value, 10);
|
||||
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 || "");
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
+26
@@ -34,6 +34,32 @@ export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => {
|
||||
|
||||
const profiles = data?.certificateProfiles || [];
|
||||
|
||||
if (!currentProject?.id) {
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Enrollment Type</Th>
|
||||
<Th>Issuing CA</Th>
|
||||
<Th>Certificate Template</Th>
|
||||
<Th>Certificates</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
<Tr>
|
||||
<Td colSpan={6}>
|
||||
<EmptyState title="No Project Selected" />
|
||||
</Td>
|
||||
</Tr>
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
|
||||
+6
-3
@@ -62,7 +62,7 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return () => clearTimeout(timer);
|
||||
}, [isIdCopied]);
|
||||
}, [isIdCopied, setIsIdCopied]);
|
||||
|
||||
const { data: templateData } = useGetCertificateTemplateV2ById({
|
||||
templateId: profile.certificateTemplateId
|
||||
@@ -87,9 +87,12 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
|
||||
const config = {
|
||||
api: { variant: "success" as const, label: "API" },
|
||||
est: { variant: "primary" as const, label: "EST" }
|
||||
};
|
||||
} as const;
|
||||
|
||||
const { variant, label } = config[enrollmentType as keyof typeof config] || config.api;
|
||||
const configKey = Object.keys(config).includes(enrollmentType)
|
||||
? (enrollmentType as keyof typeof config)
|
||||
: "api";
|
||||
const { variant, label } = config[configKey];
|
||||
|
||||
return <Badge variant={variant}>{label}</Badge>;
|
||||
};
|
||||
|
||||
+6
-2
@@ -57,8 +57,12 @@ export const CertificateTemplatesV2Tab = () => {
|
||||
text: `Certificate template "${selectedTemplate.name}" deleted successfully`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
console.error("Failed to delete template:", error);
|
||||
createNotification({
|
||||
text: "Failed to delete certificate template",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,7 +80,7 @@ export const CertificateTemplatesV2Tab = () => {
|
||||
{canCreateTemplate && (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
type="button"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={handleCreateTemplate}
|
||||
>
|
||||
|
||||
+104
-57
@@ -27,12 +27,17 @@ import {
|
||||
useCreateCertificateTemplateV2New,
|
||||
useUpdateCertificateTemplateV2New
|
||||
} from "@app/hooks/api/certificateTemplates/mutations";
|
||||
import { TCertificateTemplateV2New } from "@app/hooks/api/certificateTemplates/types";
|
||||
import {
|
||||
TCertificateTemplateV2New,
|
||||
TCertificateTemplateV2Policy
|
||||
} from "@app/hooks/api/certificateTemplates/types";
|
||||
|
||||
import {
|
||||
CertDurationUnit,
|
||||
CertExtendedKeyUsageType,
|
||||
CertKeyUsageType,
|
||||
CertSubjectAlternativeNameType,
|
||||
CertSubjectAttributeType,
|
||||
SAN_INCLUDE_OPTIONS,
|
||||
SAN_TYPE_OPTIONS,
|
||||
SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS,
|
||||
@@ -42,6 +47,13 @@ import { KeyUsagesSection, TemplateFormData, templateSchema } from "./shared";
|
||||
|
||||
export type FormData = TemplateFormData;
|
||||
|
||||
type AttributeTransform = NonNullable<TCertificateTemplateV2Policy["subject"]>[0];
|
||||
type SanTransform = NonNullable<TCertificateTemplateV2Policy["sans"]>[0];
|
||||
type KeyUsagesTransform = TCertificateTemplateV2Policy["keyUsages"];
|
||||
type ExtendedKeyUsagesTransform = TCertificateTemplateV2Policy["extendedKeyUsages"];
|
||||
type AlgorithmsTransform = TCertificateTemplateV2Policy["algorithms"];
|
||||
type ValidityTransform = TCertificateTemplateV2Policy["validity"];
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@@ -100,13 +112,13 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
const isEdit = mode === "edit" && template;
|
||||
|
||||
const convertApiToUiFormat = (templateData: TCertificateTemplateV2New): FormData => {
|
||||
const attributes: any[] = [];
|
||||
const attributes: FormData["attributes"] = [];
|
||||
if (templateData.subject && Array.isArray(templateData.subject)) {
|
||||
templateData.subject.forEach((subj) => {
|
||||
if (subj.allowed && Array.isArray(subj.allowed)) {
|
||||
subj.allowed.forEach((allowedValue) => {
|
||||
attributes.push({
|
||||
type: subj.type,
|
||||
type: subj.type as CertSubjectAttributeType,
|
||||
include: "optional",
|
||||
value: [allowedValue]
|
||||
});
|
||||
@@ -115,7 +127,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
if (subj.denied && Array.isArray(subj.denied)) {
|
||||
subj.denied.forEach((deniedValue) => {
|
||||
attributes.push({
|
||||
type: subj.type,
|
||||
type: subj.type as CertSubjectAttributeType,
|
||||
include: "prohibit",
|
||||
value: [deniedValue]
|
||||
});
|
||||
@@ -124,13 +136,13 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
});
|
||||
}
|
||||
|
||||
const subjectAlternativeNames: any[] = [];
|
||||
const subjectAlternativeNames: FormData["subjectAlternativeNames"] = [];
|
||||
if (templateData.sans && Array.isArray(templateData.sans)) {
|
||||
templateData.sans.forEach((san) => {
|
||||
if (san.required && Array.isArray(san.required)) {
|
||||
san.required.forEach((requiredValue) => {
|
||||
subjectAlternativeNames.push({
|
||||
type: san.type,
|
||||
type: san.type as CertSubjectAlternativeNameType,
|
||||
include: "mandatory",
|
||||
value: [requiredValue]
|
||||
});
|
||||
@@ -139,7 +151,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
if (san.allowed && Array.isArray(san.allowed)) {
|
||||
san.allowed.forEach((allowedValue) => {
|
||||
subjectAlternativeNames.push({
|
||||
type: san.type,
|
||||
type: san.type as CertSubjectAlternativeNameType,
|
||||
include: "optional",
|
||||
value: [allowedValue]
|
||||
});
|
||||
@@ -148,7 +160,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
if (san.denied && Array.isArray(san.denied)) {
|
||||
san.denied.forEach((deniedValue) => {
|
||||
subjectAlternativeNames.push({
|
||||
type: san.type,
|
||||
type: san.type as CertSubjectAlternativeNameType,
|
||||
include: "prohibit",
|
||||
value: [deniedValue]
|
||||
});
|
||||
@@ -171,23 +183,30 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
const validity = templateData.validity?.max
|
||||
? (() => {
|
||||
const maxValue = templateData.validity.max;
|
||||
const match = maxValue.match(/^(\d+)([dmy])$/);
|
||||
if (match) {
|
||||
const value = parseInt(match[1], 10);
|
||||
const unitChar = match[2];
|
||||
let unit: CertDurationUnit = CertDurationUnit.DAYS;
|
||||
if (unitChar === "d") {
|
||||
unit = CertDurationUnit.DAYS;
|
||||
} else if (unitChar === "m") {
|
||||
unit = CertDurationUnit.MONTHS;
|
||||
} else {
|
||||
unit = CertDurationUnit.YEARS;
|
||||
}
|
||||
return {
|
||||
maxDuration: { value, unit }
|
||||
};
|
||||
if (maxValue.length < 2) return undefined;
|
||||
|
||||
const lastChar = maxValue.slice(-1);
|
||||
const numberPart = maxValue.slice(0, -1);
|
||||
const value = parseInt(numberPart, 10);
|
||||
|
||||
if (Number.isNaN(value) || value <= 0 || numberPart !== value.toString()) {
|
||||
return undefined;
|
||||
}
|
||||
return { maxDuration: { value: 365, unit: CertDurationUnit.DAYS } };
|
||||
|
||||
let unit: CertDurationUnit = CertDurationUnit.DAYS;
|
||||
if (lastChar === "d") {
|
||||
unit = CertDurationUnit.DAYS;
|
||||
} else if (lastChar === "m") {
|
||||
unit = CertDurationUnit.MONTHS;
|
||||
} else if (lastChar === "y") {
|
||||
unit = CertDurationUnit.YEARS;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
maxDuration: { value, unit }
|
||||
};
|
||||
})()
|
||||
: { maxDuration: { value: 365, unit: CertDurationUnit.DAYS } };
|
||||
|
||||
@@ -202,7 +221,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
};
|
||||
|
||||
return {
|
||||
slug: templateData.name || "",
|
||||
name: templateData.name || "",
|
||||
description: templateData.description || "",
|
||||
attributes,
|
||||
subjectAlternativeNames,
|
||||
@@ -215,7 +234,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
};
|
||||
|
||||
const getDefaultValues = (): FormData => ({
|
||||
slug: "",
|
||||
name: "",
|
||||
description: "",
|
||||
attributes: [],
|
||||
keyUsages: { requiredUsages: [], optionalUsages: [] },
|
||||
@@ -259,7 +278,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
const transformToNewApiFormat = (data: FormData) => {
|
||||
const subject =
|
||||
data.attributes?.map((attr) => {
|
||||
const result: any = { type: attr.type };
|
||||
const result: AttributeTransform = { type: attr.type };
|
||||
|
||||
if (attr.include === "optional" && attr.value && attr.value.length > 0) {
|
||||
result.allowed = attr.value;
|
||||
@@ -272,7 +291,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
|
||||
const sans =
|
||||
data.subjectAlternativeNames?.map((san) => {
|
||||
const result: any = { type: san.type };
|
||||
const result: SanTransform = { type: san.type };
|
||||
|
||||
if (san.include === "mandatory" && san.value && san.value.length > 0) {
|
||||
result.required = san.value;
|
||||
@@ -285,7 +304,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
return result;
|
||||
}) || [];
|
||||
|
||||
const keyUsages: any = {};
|
||||
const keyUsages: KeyUsagesTransform = {};
|
||||
if (data.keyUsages?.requiredUsages && data.keyUsages.requiredUsages.length > 0) {
|
||||
keyUsages.required = data.keyUsages.requiredUsages;
|
||||
}
|
||||
@@ -293,7 +312,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
keyUsages.allowed = data.keyUsages.optionalUsages;
|
||||
}
|
||||
|
||||
const extendedKeyUsages: any = {};
|
||||
const extendedKeyUsages: ExtendedKeyUsagesTransform = {};
|
||||
if (
|
||||
data.extendedKeyUsages?.requiredUsages &&
|
||||
data.extendedKeyUsages.requiredUsages.length > 0
|
||||
@@ -307,18 +326,27 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
extendedKeyUsages.allowed = data.extendedKeyUsages.optionalUsages;
|
||||
}
|
||||
|
||||
const algorithms: any = {};
|
||||
const algorithms: AlgorithmsTransform = {};
|
||||
if (
|
||||
data.signatureAlgorithm?.allowedAlgorithms &&
|
||||
data.signatureAlgorithm.allowedAlgorithms.length > 0
|
||||
) {
|
||||
algorithms.signature = data.signatureAlgorithm.allowedAlgorithms;
|
||||
algorithms.signature = data.signatureAlgorithm.allowedAlgorithms as Array<
|
||||
| "SHA256-RSA"
|
||||
| "SHA384-RSA"
|
||||
| "SHA512-RSA"
|
||||
| "SHA256-ECDSA"
|
||||
| "SHA384-ECDSA"
|
||||
| "SHA512-ECDSA"
|
||||
>;
|
||||
}
|
||||
if (data.keyAlgorithm?.allowedKeyTypes && data.keyAlgorithm.allowedKeyTypes.length > 0) {
|
||||
algorithms.keyAlgorithm = data.keyAlgorithm.allowedKeyTypes;
|
||||
algorithms.keyAlgorithm = data.keyAlgorithm.allowedKeyTypes as Array<
|
||||
"RSA-2048" | "RSA-3072" | "RSA-4096" | "ECDSA-P256" | "ECDSA-P384"
|
||||
>;
|
||||
}
|
||||
|
||||
const validity: any = {};
|
||||
const validity: ValidityTransform = {};
|
||||
if (data.validity?.maxDuration) {
|
||||
let unit = "d";
|
||||
if (data.validity.maxDuration.unit === CertDurationUnit.DAYS) {
|
||||
@@ -332,7 +360,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
}
|
||||
|
||||
return {
|
||||
name: data.slug,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
subject: subject.length > 0 ? subject : undefined,
|
||||
sans: sans.length > 0 ? sans : undefined,
|
||||
@@ -372,8 +400,12 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
};
|
||||
await updateTemplate.mutateAsync(updateData);
|
||||
} else {
|
||||
if (!currentProject?.id) {
|
||||
throw new Error("Project ID is required for creating a template");
|
||||
}
|
||||
|
||||
const createData = {
|
||||
projectId: currentProject!.id,
|
||||
projectId: currentProject.id,
|
||||
...transformedData
|
||||
};
|
||||
await createTemplate.mutateAsync(createData);
|
||||
@@ -424,22 +456,22 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
};
|
||||
|
||||
const handleKeyUsagesChange = (usages: {
|
||||
requiredUsages: string[];
|
||||
optionalUsages: string[];
|
||||
requiredUsages: CertKeyUsageType[];
|
||||
optionalUsages: CertKeyUsageType[];
|
||||
}) => {
|
||||
setValue("keyUsages", {
|
||||
requiredUsages: usages.requiredUsages as any,
|
||||
optionalUsages: usages.optionalUsages as any
|
||||
requiredUsages: usages.requiredUsages,
|
||||
optionalUsages: usages.optionalUsages
|
||||
});
|
||||
};
|
||||
|
||||
const handleExtendedKeyUsagesChange = (usages: {
|
||||
requiredUsages: string[];
|
||||
optionalUsages: string[];
|
||||
requiredUsages: CertExtendedKeyUsageType[];
|
||||
optionalUsages: CertExtendedKeyUsageType[];
|
||||
}) => {
|
||||
setValue("extendedKeyUsages", {
|
||||
requiredUsages: usages.requiredUsages as any,
|
||||
optionalUsages: usages.optionalUsages as any
|
||||
requiredUsages: usages.requiredUsages,
|
||||
optionalUsages: usages.optionalUsages
|
||||
});
|
||||
};
|
||||
|
||||
@@ -467,7 +499,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
<div className="space-y-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="slug"
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Template Name"
|
||||
@@ -528,7 +560,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
value={attr.type}
|
||||
onValueChange={(value) => {
|
||||
const newAttributes = [...watchedAttributes];
|
||||
newAttributes[index] = { ...attr, type: value as any };
|
||||
newAttributes[index] = {
|
||||
...attr,
|
||||
type: value as CertSubjectAttributeType
|
||||
};
|
||||
setValue("attributes", newAttributes);
|
||||
}}
|
||||
className="w-48"
|
||||
@@ -544,7 +579,11 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
value={attr.include}
|
||||
onValueChange={(value) => {
|
||||
const newAttributes = [...watchedAttributes];
|
||||
newAttributes[index] = { ...attr, include: value as any };
|
||||
newAttributes[index] = {
|
||||
...attr,
|
||||
include:
|
||||
value as (typeof SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS)[number]
|
||||
};
|
||||
setValue("attributes", newAttributes);
|
||||
}}
|
||||
className="w-32"
|
||||
@@ -627,7 +666,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
value={san.type}
|
||||
onValueChange={(value) => {
|
||||
const newSans = [...watchedSans];
|
||||
newSans[index] = { ...san, type: value as any };
|
||||
newSans[index] = {
|
||||
...san,
|
||||
type: value as CertSubjectAlternativeNameType
|
||||
};
|
||||
setValue("subjectAlternativeNames", newSans);
|
||||
}}
|
||||
className="w-24"
|
||||
@@ -643,7 +685,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
value={san.include}
|
||||
onValueChange={(value) => {
|
||||
const newSans = [...watchedSans];
|
||||
newSans[index] = { ...san, include: value as any };
|
||||
newSans[index] = {
|
||||
...san,
|
||||
include: value as (typeof SAN_INCLUDE_OPTIONS)[number]
|
||||
};
|
||||
setValue("subjectAlternativeNames", newSans);
|
||||
}}
|
||||
className="w-32"
|
||||
@@ -674,14 +719,16 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
required
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
ariaLabel="Remove SAN"
|
||||
variant="plain"
|
||||
size="sm"
|
||||
onClick={() => removeSan(index)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
{watchedSans.length > 1 && (
|
||||
<IconButton
|
||||
ariaLabel="Remove SAN"
|
||||
variant="plain"
|
||||
size="sm"
|
||||
onClick={() => removeSan(index)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
+8
-8
@@ -19,7 +19,7 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { useProject, useProjectPermission } from "@app/context";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionPkiTemplateActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/context/ProjectPermissionContext/types";
|
||||
import { useListCertificateTemplatesV2 } from "@app/hooks/api/certificateTemplates/queries";
|
||||
@@ -43,19 +43,21 @@ export const TemplateList = ({ onEditTemplate, onDeleteTemplate }: Props) => {
|
||||
const templates = data?.certificateTemplates || [];
|
||||
|
||||
const canEditTemplate = permission.can(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.CertificateAuthorities
|
||||
ProjectPermissionPkiTemplateActions.Edit,
|
||||
ProjectPermissionSub.CertificateTemplates
|
||||
);
|
||||
|
||||
const canDeleteTemplate = permission.can(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.CertificateAuthorities
|
||||
ProjectPermissionPkiTemplateActions.Delete,
|
||||
ProjectPermissionSub.CertificateTemplates
|
||||
);
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString();
|
||||
};
|
||||
|
||||
const hasTemplates = !isLoading && templates && templates.length > 0;
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
@@ -75,9 +77,7 @@ export const TemplateList = ({ onEditTemplate, onDeleteTemplate }: Props) => {
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{!isLoading &&
|
||||
templates &&
|
||||
templates.length > 0 &&
|
||||
{hasTemplates &&
|
||||
templates.map((template) => (
|
||||
<Tr
|
||||
key={template.id}
|
||||
|
||||
+53
-32
@@ -166,42 +166,63 @@ export const USAGE_STATES = {
|
||||
|
||||
export type UsageState = (typeof USAGE_STATES)[keyof typeof USAGE_STATES] | undefined;
|
||||
|
||||
export const TEMPLATE_SIGNATURE_ALGORITHMS = [
|
||||
"SHA256-RSA",
|
||||
"SHA384-RSA",
|
||||
"SHA512-RSA",
|
||||
"SHA256-ECDSA",
|
||||
"SHA384-ECDSA",
|
||||
"SHA512-ECDSA"
|
||||
] as const;
|
||||
export enum CertKeyAlgorithm {
|
||||
RSA_2048 = "RSA_2048",
|
||||
RSA_3072 = "RSA_3072",
|
||||
RSA_4096 = "RSA_4096",
|
||||
ECDSA_P256 = "EC_prime256v1",
|
||||
ECDSA_P384 = "EC_secp384r1"
|
||||
}
|
||||
|
||||
export const TEMPLATE_KEY_ALGORITHMS = [
|
||||
"RSA-2048",
|
||||
"RSA-3072",
|
||||
"RSA-4096",
|
||||
"ECDSA-P256",
|
||||
"ECDSA-P384"
|
||||
] as const;
|
||||
export enum CertSignatureAlgorithm {
|
||||
RSA_SHA256 = "RSA-SHA256",
|
||||
RSA_SHA384 = "RSA-SHA384",
|
||||
RSA_SHA512 = "RSA-SHA512",
|
||||
ECDSA_SHA256 = "ECDSA-SHA256",
|
||||
ECDSA_SHA384 = "ECDSA-SHA384",
|
||||
ECDSA_SHA512 = "ECDSA-SHA512"
|
||||
}
|
||||
|
||||
// API format algorithm constants
|
||||
export const API_SIGNATURE_ALGORITHMS = [
|
||||
"RSA-SHA256",
|
||||
"RSA-SHA384",
|
||||
"RSA-SHA512",
|
||||
"ECDSA-SHA256",
|
||||
"ECDSA-SHA384",
|
||||
"ECDSA-SHA512"
|
||||
] as const;
|
||||
export const SIGNATURE_ALGORITHM_OPTIONS = Object.values(CertSignatureAlgorithm);
|
||||
export const KEY_ALGORITHM_OPTIONS = Object.values(CertKeyAlgorithm);
|
||||
|
||||
export const API_KEY_ALGORITHMS = [
|
||||
"RSA_2048",
|
||||
"RSA_3072",
|
||||
"RSA_4096",
|
||||
"EC_prime256v1",
|
||||
"EC_secp384r1"
|
||||
] as const;
|
||||
// Display name mappings for UI
|
||||
export const getSignatureAlgorithmDisplayName = (algorithm: CertSignatureAlgorithm): string => {
|
||||
switch (algorithm) {
|
||||
case CertSignatureAlgorithm.RSA_SHA256:
|
||||
return "RSA with SHA-256";
|
||||
case CertSignatureAlgorithm.RSA_SHA384:
|
||||
return "RSA with SHA-384";
|
||||
case CertSignatureAlgorithm.RSA_SHA512:
|
||||
return "RSA with SHA-512";
|
||||
case CertSignatureAlgorithm.ECDSA_SHA256:
|
||||
return "ECDSA with SHA-256";
|
||||
case CertSignatureAlgorithm.ECDSA_SHA384:
|
||||
return "ECDSA with SHA-384";
|
||||
case CertSignatureAlgorithm.ECDSA_SHA512:
|
||||
return "ECDSA with SHA-512";
|
||||
default:
|
||||
return algorithm;
|
||||
}
|
||||
};
|
||||
|
||||
export const getKeyAlgorithmDisplayName = (algorithm: CertKeyAlgorithm): string => {
|
||||
switch (algorithm) {
|
||||
case CertKeyAlgorithm.RSA_2048:
|
||||
return "RSA 2048";
|
||||
case CertKeyAlgorithm.RSA_3072:
|
||||
return "RSA 3072";
|
||||
case CertKeyAlgorithm.RSA_4096:
|
||||
return "RSA 4096";
|
||||
case CertKeyAlgorithm.ECDSA_P256:
|
||||
return "ECDSA P-256";
|
||||
case CertKeyAlgorithm.ECDSA_P384:
|
||||
return "ECDSA P-384";
|
||||
default:
|
||||
return algorithm;
|
||||
}
|
||||
};
|
||||
|
||||
// Mapping functions between template and API formats
|
||||
export const mapTemplateSignatureAlgorithmToApi = (templateFormat: string): string => {
|
||||
const mapping: Record<string, string> = {
|
||||
"SHA256-RSA": "RSA-SHA256",
|
||||
|
||||
+4
-10
@@ -11,7 +11,7 @@ import {
|
||||
KEY_USAGE_OPTIONS
|
||||
} from "./certificate-constants";
|
||||
|
||||
type KeyUsagePolicy = "allow" | "require" | "deny" | "none";
|
||||
type KeyUsagePolicy = "allow" | "require" | "deny";
|
||||
|
||||
interface KeyUsagesSectionProps {
|
||||
watchedKeyUsages: {
|
||||
@@ -85,13 +85,7 @@ export const KeyUsagesSection: React.FC<KeyUsagesSectionProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const keyUsagePolicyOptions = [
|
||||
{ value: "deny", label: "Deny" },
|
||||
{ value: "allow", label: "Allow" },
|
||||
{ value: "require", label: "Require" }
|
||||
];
|
||||
|
||||
const extendedKeyUsagePolicyOptions = [
|
||||
const policyOptions = [
|
||||
{ value: "deny", label: "Deny" },
|
||||
{ value: "allow", label: "Allow" },
|
||||
{ value: "require", label: "Require" }
|
||||
@@ -116,7 +110,7 @@ export const KeyUsagesSection: React.FC<KeyUsagesSectionProps> = ({
|
||||
}
|
||||
className="w-32"
|
||||
>
|
||||
{keyUsagePolicyOptions.map((option) => (
|
||||
{policyOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
@@ -145,7 +139,7 @@ export const KeyUsagesSection: React.FC<KeyUsagesSectionProps> = ({
|
||||
}
|
||||
className="w-32"
|
||||
>
|
||||
{extendedKeyUsagePolicyOptions.map((option) => (
|
||||
{policyOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
|
||||
+12
-2
@@ -50,7 +50,7 @@ export const uiKeyAlgorithmSchema = z.object({
|
||||
});
|
||||
|
||||
export const templateSchema = z.object({
|
||||
slug: z.string().trim().min(1, "Template name is required"),
|
||||
name: z.string().trim().min(1, "Template name is required"),
|
||||
description: z.string().optional(),
|
||||
attributes: z.array(uiAttributeSchema).optional(),
|
||||
subjectAlternativeNames: z.array(uiSanSchema).optional(),
|
||||
@@ -114,7 +114,17 @@ export const apiTemplateSchema = z.object({
|
||||
.object({
|
||||
max: z
|
||||
.string()
|
||||
.regex(/^\d+[dhmy]$/, "Must be in format like '365d', '12m', '1y', or '24h'")
|
||||
.refine((val) => {
|
||||
if (!val) return true;
|
||||
if (val.length < 2 || val.length > 10) return false;
|
||||
|
||||
const lastChar = val.slice(-1);
|
||||
if (!["d", "h", "m", "y"].includes(lastChar)) return false;
|
||||
|
||||
const numberPart = val.slice(0, -1);
|
||||
const num = parseInt(numberPart, 10);
|
||||
return !Number.isNaN(num) && num > 0 && numberPart === num.toString();
|
||||
}, "Must be in format like '365d', '12m', '1y', or '24h'")
|
||||
.optional()
|
||||
})
|
||||
.optional()
|
||||
|
||||
+5
-9
@@ -8,15 +8,11 @@ import {
|
||||
} from "./certificate-constants";
|
||||
|
||||
export const formatUsageName = (usage: string): string => {
|
||||
try {
|
||||
if (Object.values(CertKeyUsageType).includes(usage as CertKeyUsageType)) {
|
||||
return formatKeyUsage(usage as CertKeyUsageType);
|
||||
}
|
||||
if (Object.values(CertExtendedKeyUsageType).includes(usage as CertExtendedKeyUsageType)) {
|
||||
return formatExtendedKeyUsage(usage as CertExtendedKeyUsageType);
|
||||
}
|
||||
} catch {
|
||||
// Handle any errors in type checking
|
||||
if (Object.values(CertKeyUsageType).includes(usage as CertKeyUsageType)) {
|
||||
return formatKeyUsage(usage as CertKeyUsageType);
|
||||
}
|
||||
if (Object.values(CertExtendedKeyUsageType).includes(usage as CertExtendedKeyUsageType)) {
|
||||
return formatExtendedKeyUsage(usage as CertExtendedKeyUsageType);
|
||||
}
|
||||
return usage.replace(/_/g, " ");
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user