mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 19:28:09 +00:00
Merge branch 'main' of https://github.com/Infisical/infisical into feat/suborg-scope-support
This commit is contained in:
@@ -189,10 +189,12 @@ export const useGetCaCertTemplates = (caId: string) => {
|
||||
|
||||
export const useGetAzureAdcsTemplates = ({
|
||||
caId,
|
||||
projectId
|
||||
projectId,
|
||||
isAzureAdcsCa
|
||||
}: {
|
||||
caId: string;
|
||||
projectId: string;
|
||||
isAzureAdcsCa: boolean;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: caKeys.getAzureAdcsTemplates(caId, projectId),
|
||||
@@ -202,6 +204,6 @@ export const useGetAzureAdcsTemplates = ({
|
||||
}>(`/api/v1/cert-manager/ca/azure-ad-cs/${caId}/templates?projectId=${projectId}`);
|
||||
return data;
|
||||
},
|
||||
enabled: Boolean(caId && projectId)
|
||||
enabled: Boolean(caId && projectId && isAzureAdcsCa)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -22,14 +22,25 @@ export type TCertificateProfile = {
|
||||
apiConfigId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
externalConfigs?: Record<string, unknown> | null;
|
||||
certificateAuthority?: {
|
||||
id: string;
|
||||
projectId?: string;
|
||||
status: string;
|
||||
name: string;
|
||||
isExternal?: boolean;
|
||||
externalType?: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type TCertificateProfileWithDetails = TCertificateProfile & {
|
||||
certificateAuthority?: {
|
||||
id: string;
|
||||
projectId: string;
|
||||
projectId?: string;
|
||||
status: string;
|
||||
name: string;
|
||||
isExternal?: boolean;
|
||||
externalType?: string | null;
|
||||
};
|
||||
certificateTemplate?: {
|
||||
id: string;
|
||||
@@ -72,6 +83,7 @@ export type TCreateCertificateProfileDTO = {
|
||||
renewBeforeDays?: number;
|
||||
};
|
||||
acmeConfig?: unknown;
|
||||
externalConfigs?: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
export type TUpdateCertificateProfileDTO = {
|
||||
@@ -90,6 +102,7 @@ export type TUpdateCertificateProfileDTO = {
|
||||
renewBeforeDays?: number;
|
||||
};
|
||||
acmeConfig?: unknown;
|
||||
externalConfigs?: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
export type TDeleteCertificateProfileDTO = {
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
TRenewCertificateDTO,
|
||||
TRenewCertificateResponse,
|
||||
TRevokeCertDTO,
|
||||
TUnifiedCertificateIssuanceDTO,
|
||||
TUnifiedCertificateIssuanceResponse,
|
||||
TUpdateRenewalConfigDTO
|
||||
} from "./types";
|
||||
|
||||
@@ -185,3 +187,31 @@ export const useDownloadCertPkcs12 = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUnifiedCertificateIssuance = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TUnifiedCertificateIssuanceResponse, object, TUnifiedCertificateIssuanceDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const { projectSlug, ...requestData } = body;
|
||||
const { data } = await apiRequest.post<TUnifiedCertificateIssuanceResponse>(
|
||||
"/api/v1/cert-manager/certificates",
|
||||
requestData
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { projectSlug }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["certificate-profiles", "list"]
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.allProjectCertificates()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.forProjectCertificates(projectSlug)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -7,7 +7,11 @@ import { TCertificate } from "./types";
|
||||
export const certKeys = {
|
||||
getCertById: (serialNumber: string) => [{ serialNumber }, "cert"],
|
||||
getCertBody: (serialNumber: string) => [{ serialNumber }, "certBody"],
|
||||
getCertBundle: (serialNumber: string) => [{ serialNumber }, "certBundle"]
|
||||
getCertBundle: (serialNumber: string) => [{ serialNumber }, "certBundle"],
|
||||
getCertificateRequest: (requestId: string, projectSlug: string) => [
|
||||
{ requestId, projectSlug },
|
||||
"certificateRequest"
|
||||
]
|
||||
};
|
||||
|
||||
export const useGetCert = (serialNumber: string) => {
|
||||
|
||||
@@ -64,6 +64,7 @@ export type TRenewCertificateResponse = {
|
||||
serialNumber: string;
|
||||
certificateId: string;
|
||||
projectId: string;
|
||||
certificateRequestId?: string;
|
||||
};
|
||||
|
||||
export type TUpdateRenewalConfigDTO = {
|
||||
@@ -79,3 +80,59 @@ export type TDownloadPkcs12DTO = {
|
||||
password: string;
|
||||
alias: string;
|
||||
};
|
||||
|
||||
export type TUnifiedCertificateIssuanceDTO = {
|
||||
projectSlug: string;
|
||||
profileId: string;
|
||||
projectId: string;
|
||||
csr?: string;
|
||||
attributes?: {
|
||||
commonName?: string;
|
||||
keyUsages?: string[];
|
||||
extendedKeyUsages?: string[];
|
||||
altNames?: Array<{
|
||||
type: string;
|
||||
value: string;
|
||||
}>;
|
||||
signatureAlgorithm: string;
|
||||
keyAlgorithm: string;
|
||||
subjectAlternativeNames?: Array<{
|
||||
type: string;
|
||||
value: string;
|
||||
}>;
|
||||
ttl: string;
|
||||
notBefore?: string;
|
||||
notAfter?: string;
|
||||
};
|
||||
removeRootsFromChain?: boolean;
|
||||
};
|
||||
|
||||
export type TUnifiedCertificateResponse = {
|
||||
certificate: {
|
||||
certificate: string;
|
||||
issuingCaCertificate: string;
|
||||
certificateChain: string;
|
||||
privateKey?: string;
|
||||
serialNumber: string;
|
||||
certificateId: string;
|
||||
};
|
||||
certificateRequestId: string;
|
||||
};
|
||||
|
||||
export type TCertificateRequestResponse = {
|
||||
certificateRequestId: string;
|
||||
status: "pending" | "issued" | "failed";
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TUnifiedCertificateIssuanceResponse =
|
||||
| TUnifiedCertificateResponse
|
||||
| TCertificateRequestResponse;
|
||||
|
||||
export type TCertificateRequestDetails = {
|
||||
status: "pending" | "issued" | "failed";
|
||||
certificate: TCertificate | null;
|
||||
errorMessage: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
+75
-49
@@ -20,9 +20,9 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { useProject } from "@app/context";
|
||||
import { useGetCert } from "@app/hooks/api";
|
||||
import { useCreateCertificateV3 } from "@app/hooks/api/ca";
|
||||
import { EnrollmentType, useListCertificateProfiles } from "@app/hooks/api/certificateProfiles";
|
||||
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/hooks/api/certificates/enums";
|
||||
import { useUnifiedCertificateIssuance } from "@app/hooks/api/certificates/mutations";
|
||||
import { useGetCertificateTemplateV2ById } from "@app/hooks/api/certificateTemplates/queries";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
import { CertSubjectAlternativeNameType } from "@app/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants";
|
||||
@@ -103,10 +103,11 @@ type Props = {
|
||||
};
|
||||
|
||||
type TCertificateDetails = {
|
||||
serialNumber: string;
|
||||
certificate: string;
|
||||
certificateChain: string;
|
||||
privateKey: string;
|
||||
serialNumber?: string;
|
||||
certificate?: string;
|
||||
certificateChain?: string;
|
||||
privateKey?: string;
|
||||
issuingCaCertificate?: string;
|
||||
};
|
||||
|
||||
export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }: Props) => {
|
||||
@@ -122,12 +123,11 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
|
||||
const { data: profilesData } = useListCertificateProfiles({
|
||||
projectId: currentProject?.id || "",
|
||||
enrollmentType: EnrollmentType.API
|
||||
enrollmentType: EnrollmentType.API,
|
||||
includeConfigs: true
|
||||
});
|
||||
|
||||
const { mutateAsync: createCertificate } = useCreateCertificateV3({
|
||||
projectId: currentProject?.id
|
||||
});
|
||||
const { mutateAsync: issueCertificate } = useUnifiedCertificateIssuance();
|
||||
|
||||
const formResolver = useMemo(() => {
|
||||
return zodResolver(createSchema(shouldShowSubjectSection));
|
||||
@@ -243,7 +243,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
keyUsages,
|
||||
extendedKeyUsages
|
||||
}: FormData) => {
|
||||
if (!currentProject?.slug) {
|
||||
if (!currentProject?.slug || !currentProject?.id) {
|
||||
createNotification({
|
||||
text: "Project not found. Please refresh and try again.",
|
||||
type: "error"
|
||||
@@ -275,44 +275,70 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
}
|
||||
}
|
||||
|
||||
const certificateRequest: any = {
|
||||
profileId: formProfileId,
|
||||
projectSlug: currentProject.slug,
|
||||
ttl,
|
||||
signatureAlgorithm,
|
||||
keyAlgorithm,
|
||||
keyUsages: filterUsages(keyUsages) as CertKeyUsage[],
|
||||
extendedKeyUsages: filterUsages(extendedKeyUsages) as CertExtendedKeyUsage[]
|
||||
};
|
||||
try {
|
||||
// Prepare unified request
|
||||
const request: any = {
|
||||
profileId: formProfileId,
|
||||
projectSlug: currentProject.slug,
|
||||
projectId: currentProject.id,
|
||||
attributes: {
|
||||
ttl,
|
||||
signatureAlgorithm: signatureAlgorithm || "",
|
||||
keyAlgorithm: keyAlgorithm || "",
|
||||
keyUsages: filterUsages(keyUsages) as CertKeyUsage[],
|
||||
extendedKeyUsages: filterUsages(extendedKeyUsages) as CertExtendedKeyUsage[]
|
||||
}
|
||||
};
|
||||
|
||||
if (constraints.shouldShowSubjectSection && commonName) {
|
||||
certificateRequest.commonName = commonName;
|
||||
}
|
||||
if (constraints.shouldShowSanSection && subjectAltNames && subjectAltNames.length > 0) {
|
||||
const formattedSans = formatSubjectAltNames(subjectAltNames);
|
||||
if (formattedSans && formattedSans.length > 0) {
|
||||
certificateRequest.altNames = formattedSans;
|
||||
if (constraints.shouldShowSubjectSection && commonName) {
|
||||
request.attributes.commonName = commonName;
|
||||
}
|
||||
|
||||
if (constraints.shouldShowSanSection && subjectAltNames && subjectAltNames.length > 0) {
|
||||
const formattedSans = formatSubjectAltNames(subjectAltNames);
|
||||
if (formattedSans && formattedSans.length > 0) {
|
||||
request.attributes.altNames = formattedSans;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await issueCertificate(request);
|
||||
|
||||
// Handle certificate issuance response
|
||||
|
||||
if ("certificate" in response && response.certificate) {
|
||||
const certData = response.certificate;
|
||||
const certificateDetailsToSet = {
|
||||
serialNumber: certData.serialNumber || "",
|
||||
certificate: certData.certificate || "",
|
||||
certificateChain: certData.certificateChain || "",
|
||||
privateKey: certData.privateKey || "",
|
||||
issuingCaCertificate: certData.issuingCaCertificate || ""
|
||||
};
|
||||
|
||||
setCertificateDetails(certificateDetailsToSet);
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created certificate",
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
// Certificate request - async processing
|
||||
createNotification({
|
||||
text: `Certificate request submitted successfully. This may take a few minutes to process. Certificate Request ID: ${response.certificateRequestId}`,
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpToggle("issueCertificate", false);
|
||||
}
|
||||
} catch (error) {
|
||||
createNotification({
|
||||
text: `Failed to request certificate: ${(error as Error)?.message || "Unknown error"}`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
const { serialNumber, certificate, certificateChain, privateKey } =
|
||||
await createCertificate(certificateRequest);
|
||||
|
||||
setCertificateDetails({
|
||||
serialNumber,
|
||||
certificate,
|
||||
certificateChain,
|
||||
privateKey
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created certificate",
|
||||
type: "success"
|
||||
});
|
||||
},
|
||||
[
|
||||
currentProject?.slug,
|
||||
createCertificate,
|
||||
issueCertificate,
|
||||
constraints.shouldShowSubjectSection,
|
||||
constraints.shouldShowSanSection
|
||||
]
|
||||
@@ -321,13 +347,13 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
const getModalTitle = () => {
|
||||
if (certificateDetails) return "Certificate Created Successfully";
|
||||
if (cert) return "Certificate Details";
|
||||
return "Issue New Certificate";
|
||||
return "Request New Certificate";
|
||||
};
|
||||
|
||||
const getModalSubTitle = () => {
|
||||
if (certificateDetails) return "Certificate has been successfully created and is ready for use";
|
||||
if (cert) return "View certificate information";
|
||||
return "Issue a new certificate using a certificate profile";
|
||||
return "Request a new certificate using a certificate profile";
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -343,10 +369,10 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
<ModalContent title={getModalTitle()} subTitle={getModalSubTitle()}>
|
||||
{certificateDetails && (
|
||||
<CertificateContent
|
||||
serialNumber={certificateDetails.serialNumber}
|
||||
certificate={certificateDetails.certificate}
|
||||
certificateChain={certificateDetails.certificateChain}
|
||||
privateKey={certificateDetails.privateKey}
|
||||
serialNumber={certificateDetails.serialNumber!}
|
||||
certificate={certificateDetails.certificate!}
|
||||
certificateChain={certificateDetails.certificateChain!}
|
||||
privateKey={certificateDetails.privateKey!}
|
||||
/>
|
||||
)}
|
||||
{cert && (
|
||||
@@ -498,7 +524,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || (!actualSelectedProfile && !profileId)}
|
||||
>
|
||||
{cert ? "Update" : "Issue Certificate"}
|
||||
{cert ? "Update" : "Request Certificate"}
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
|
||||
+6
-2
@@ -20,12 +20,16 @@ export const CertificateRenewalModal = ({ popUp, handlePopUpToggle }: Props) =>
|
||||
const onRenewConfirm = async () => {
|
||||
const { certificateId } = popUp.renewCertificate.data as { certificateId: string };
|
||||
|
||||
await renewCertificate({
|
||||
const result = await renewCertificate({
|
||||
certificateId
|
||||
});
|
||||
|
||||
const notificationText = result.certificateRequestId
|
||||
? `Certificate renewal initiated successfully. Certificate Request ID: ${result.certificateRequestId}`
|
||||
: "Certificate renewed successfully";
|
||||
|
||||
createNotification({
|
||||
text: "Certificate renewed successfully",
|
||||
text: notificationText,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ export const CertificatesSection = () => {
|
||||
onClick={() => handlePopUpOpen("issueCertificate")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Issue
|
||||
Request
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+2
-1
@@ -213,7 +213,8 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
// Fetch Azure ADCS templates when Azure CA is selected
|
||||
const { data: azureTemplates } = useGetAzureAdcsTemplates({
|
||||
caId: selectedCa?.type === CaType.AZURE_AD_CS ? selectedCaId : "",
|
||||
projectId
|
||||
projectId,
|
||||
isAzureAdcsCa: true
|
||||
});
|
||||
|
||||
// Initialize form with ALL subscriber data including template
|
||||
|
||||
+176
-25
@@ -9,6 +9,7 @@ import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FilterableSelect,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
@@ -19,7 +20,8 @@ import {
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { useProject, useSubscription } from "@app/context";
|
||||
import { useListCasByProjectId } from "@app/hooks/api/ca/queries";
|
||||
import { CaType } from "@app/hooks/api/ca/enums";
|
||||
import { useGetAzureAdcsTemplates, useListCasByProjectId } from "@app/hooks/api/ca/queries";
|
||||
import {
|
||||
EnrollmentType,
|
||||
IssuerType,
|
||||
@@ -77,7 +79,12 @@ const createSchema = z
|
||||
renewBeforeDays: z.number().min(1).max(365).optional()
|
||||
})
|
||||
.optional(),
|
||||
acmeConfig: z.object({}).optional()
|
||||
acmeConfig: z.object({}).optional(),
|
||||
externalConfigs: z
|
||||
.object({
|
||||
template: z.string().min(1, "Azure ADCS template is required")
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -212,7 +219,12 @@ const editSchema = z
|
||||
renewBeforeDays: z.number().min(1).max(365).optional()
|
||||
})
|
||||
.optional(),
|
||||
acmeConfig: z.object({}).optional()
|
||||
acmeConfig: z.object({}).optional(),
|
||||
externalConfigs: z
|
||||
.object({
|
||||
template: z.string().optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -339,7 +351,7 @@ export const CreateProfileModal = ({
|
||||
const { currentProject } = useProject();
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { data: caData } = useListCasByProjectId(currentProject?.id || "");
|
||||
const { data: allCaData } = useListCasByProjectId(currentProject?.id || "");
|
||||
const { data: templateData } = useListCertificateTemplatesV2({
|
||||
projectId: currentProject?.id || "",
|
||||
limit: 100,
|
||||
@@ -351,9 +363,23 @@ export const CreateProfileModal = ({
|
||||
|
||||
const isEdit = mode === "edit" && profile;
|
||||
|
||||
const certificateAuthorities = caData || [];
|
||||
const certificateAuthorities = (allCaData || []).map((ca) => ({
|
||||
...ca,
|
||||
groupType: ca.type === "internal" ? "internal" : "external"
|
||||
}));
|
||||
const certificateTemplates = templateData?.certificateTemplates || [];
|
||||
|
||||
const getGroupHeaderLabel = (groupType: "internal" | "external") => {
|
||||
switch (groupType) {
|
||||
case "internal":
|
||||
return "Internal CAs";
|
||||
case "external":
|
||||
return "External CAs";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<FormData>({
|
||||
resolver: zodResolver(isEdit ? editSchema : createSchema),
|
||||
defaultValues: isEdit
|
||||
@@ -380,7 +406,17 @@ export const CreateProfileModal = ({
|
||||
renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30
|
||||
}
|
||||
: undefined,
|
||||
acmeConfig: profile.enrollmentType === EnrollmentType.ACME ? {} : undefined
|
||||
acmeConfig: profile.enrollmentType === EnrollmentType.ACME ? {} : undefined,
|
||||
externalConfigs: profile.externalConfigs
|
||||
? {
|
||||
template:
|
||||
typeof profile.externalConfigs === "object" &&
|
||||
profile.externalConfigs !== null &&
|
||||
typeof profile.externalConfigs.template === "string"
|
||||
? profile.externalConfigs.template
|
||||
: ""
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
: {
|
||||
slug: "",
|
||||
@@ -393,15 +429,28 @@ export const CreateProfileModal = ({
|
||||
autoRenew: false,
|
||||
renewBeforeDays: 30
|
||||
},
|
||||
acmeConfig: {}
|
||||
acmeConfig: {},
|
||||
externalConfigs: undefined
|
||||
}
|
||||
});
|
||||
|
||||
const watchedEnrollmentType = watch("enrollmentType");
|
||||
const watchedIssuerType = watch("issuerType");
|
||||
const watchedCertificateAuthorityId = watch("certificateAuthorityId");
|
||||
const watchedDisableBootstrapValidation = watch("estConfig.disableBootstrapCaValidation");
|
||||
const watchedAutoRenew = watch("apiConfig.autoRenew");
|
||||
|
||||
// Get the selected CA to check if it's Azure ADCS
|
||||
const selectedCa = certificateAuthorities.find((ca) => ca.id === watchedCertificateAuthorityId);
|
||||
const isAzureAdcsCa = selectedCa?.type === CaType.AZURE_AD_CS;
|
||||
|
||||
// Fetch Azure ADCS templates if needed
|
||||
const { data: azureAdcsTemplatesData } = useGetAzureAdcsTemplates({
|
||||
caId: watchedCertificateAuthorityId || "",
|
||||
projectId: currentProject?.id || "",
|
||||
isAzureAdcsCa
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isEdit && profile) {
|
||||
reset({
|
||||
@@ -427,10 +476,38 @@ export const CreateProfileModal = ({
|
||||
renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30
|
||||
}
|
||||
: undefined,
|
||||
acmeConfig: profile.enrollmentType === EnrollmentType.ACME ? {} : undefined
|
||||
acmeConfig: profile.enrollmentType === EnrollmentType.ACME ? {} : undefined,
|
||||
externalConfigs: profile.externalConfigs
|
||||
? {
|
||||
template:
|
||||
typeof profile.externalConfigs === "object" &&
|
||||
profile.externalConfigs !== null &&
|
||||
typeof profile.externalConfigs.template === "string"
|
||||
? profile.externalConfigs.template
|
||||
: ""
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
}
|
||||
}, [isEdit, profile, reset]);
|
||||
}, [isEdit, profile, reset, allCaData]);
|
||||
|
||||
// Additional effect to reset external configs when Azure ADCS templates are loaded
|
||||
useEffect(() => {
|
||||
if (
|
||||
isEdit &&
|
||||
profile &&
|
||||
isAzureAdcsCa &&
|
||||
azureAdcsTemplatesData?.templates &&
|
||||
profile.externalConfigs &&
|
||||
typeof profile.externalConfigs === "object" &&
|
||||
profile.externalConfigs !== null &&
|
||||
typeof profile.externalConfigs.template === "string"
|
||||
) {
|
||||
// Re-set the external configs to ensure the template value is properly set
|
||||
// after the Azure ADCS templates have been loaded
|
||||
setValue("externalConfigs.template", profile.externalConfigs.template);
|
||||
}
|
||||
}, [isEdit, profile, isAzureAdcsCa, azureAdcsTemplatesData, setValue]);
|
||||
|
||||
const onFormSubmit = async (data: FormData) => {
|
||||
if (!isEdit && !subscription?.pkiAcme && data.enrollmentType === EnrollmentType.ACME) {
|
||||
@@ -444,6 +521,18 @@ export const CreateProfileModal = ({
|
||||
|
||||
if (!currentProject?.id && !isEdit) return;
|
||||
|
||||
// Validate Azure ADCS template requirement
|
||||
if (
|
||||
isAzureAdcsCa &&
|
||||
(!data.externalConfigs?.template || data.externalConfigs.template.trim() === "")
|
||||
) {
|
||||
createNotification({
|
||||
text: "Azure ADCS Certificate Authority requires a template to be specified",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEdit) {
|
||||
const updateData: TUpdateCertificateProfileDTO = {
|
||||
profileId: profile.id,
|
||||
@@ -460,6 +549,11 @@ export const CreateProfileModal = ({
|
||||
updateData.acmeConfig = data.acmeConfig;
|
||||
}
|
||||
|
||||
// Add external configs if present
|
||||
if (data.externalConfigs) {
|
||||
updateData.externalConfigs = data.externalConfigs;
|
||||
}
|
||||
|
||||
await updateProfile.mutateAsync(updateData);
|
||||
} else {
|
||||
if (!currentProject?.id) {
|
||||
@@ -491,6 +585,11 @@ export const CreateProfileModal = ({
|
||||
createData.acmeConfig = data.acmeConfig;
|
||||
}
|
||||
|
||||
// Add external configs if present
|
||||
if (data.externalConfigs) {
|
||||
createData.externalConfigs = data.externalConfigs;
|
||||
}
|
||||
|
||||
await createProfile.mutateAsync(createData);
|
||||
}
|
||||
|
||||
@@ -587,30 +686,82 @@ export const CreateProfileModal = ({
|
||||
<Controller
|
||||
control={control}
|
||||
name="certificateAuthorityId"
|
||||
render={({ field: { onChange, value, ...field }, fieldState: { error } }) => (
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Issuing CA"
|
||||
isRequired
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Select
|
||||
{...field}
|
||||
value={value || undefined}
|
||||
onValueChange={onChange}
|
||||
<FilterableSelect
|
||||
value={certificateAuthorities.find((ca) => ca.id === value) || null}
|
||||
onChange={(selectedCaValue) => {
|
||||
if (Array.isArray(selectedCaValue)) {
|
||||
onChange(selectedCaValue[0]?.id || "");
|
||||
} else if (
|
||||
selectedCaValue &&
|
||||
typeof selectedCaValue === "object" &&
|
||||
"id" in selectedCaValue
|
||||
) {
|
||||
onChange(selectedCaValue.id || "");
|
||||
} else {
|
||||
onChange("");
|
||||
}
|
||||
}}
|
||||
getOptionLabel={(ca) =>
|
||||
ca.type === "internal" && ca.configuration.friendlyName
|
||||
? ca.configuration.friendlyName
|
||||
: ca.name
|
||||
}
|
||||
getOptionValue={(ca) => ca.id}
|
||||
options={certificateAuthorities}
|
||||
groupBy="groupType"
|
||||
getGroupHeaderLabel={getGroupHeaderLabel}
|
||||
placeholder="Select a certificate authority"
|
||||
className="w-full"
|
||||
position="popper"
|
||||
isDisabled={Boolean(isEdit)}
|
||||
>
|
||||
{certificateAuthorities.map((ca) => (
|
||||
<SelectItem key={ca.id} value={ca.id}>
|
||||
{ca.type === "internal" && ca.configuration.friendlyName
|
||||
? ca.configuration.friendlyName
|
||||
: ca.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
className="w-full"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Azure ADCS Template Selection */}
|
||||
{isAzureAdcsCa && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="externalConfigs.template"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Windows ADCS Template"
|
||||
isRequired
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
value={
|
||||
azureAdcsTemplatesData?.templates.find((template) => template.id === value) ||
|
||||
null
|
||||
}
|
||||
onChange={(selectedTemplate) => {
|
||||
if (Array.isArray(selectedTemplate)) {
|
||||
onChange(selectedTemplate[0]?.id || "");
|
||||
} else if (
|
||||
selectedTemplate &&
|
||||
typeof selectedTemplate === "object" &&
|
||||
"id" in selectedTemplate
|
||||
) {
|
||||
onChange(selectedTemplate.id || "");
|
||||
} else {
|
||||
onChange("");
|
||||
}
|
||||
}}
|
||||
getOptionLabel={(template) => template.name}
|
||||
getOptionValue={(template) => template.id}
|
||||
options={azureAdcsTemplatesData?.templates || []}
|
||||
placeholder="Select an Azure ADCS certificate template"
|
||||
className="w-full"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
+7
-4
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { useCallback } from "react";
|
||||
import {
|
||||
faCheck,
|
||||
@@ -123,9 +124,11 @@ export const ProfileRow = ({
|
||||
<span className="text-sm text-mineshaft-300">
|
||||
{profile.issuerType === IssuerType.SELF_SIGNED
|
||||
? "Self-signed"
|
||||
: caData?.configuration.friendlyName ||
|
||||
caData?.configuration.commonName ||
|
||||
profile.caId}
|
||||
: profile.certificateAuthority?.isExternal
|
||||
? profile.certificateAuthority.name
|
||||
: caData?.configuration.friendlyName ||
|
||||
caData?.configuration.commonName ||
|
||||
profile.caId}
|
||||
</span>
|
||||
</Td>
|
||||
<Td>
|
||||
@@ -179,7 +182,7 @@ export const ProfileRow = ({
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faPlus} className="w-3" />}
|
||||
>
|
||||
Issue Certificate
|
||||
Request Certificate
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{canDeleteProfile && (
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { CopyButton } from "@app/components/v2/CopyButton";
|
||||
import {
|
||||
OrgPermissionIdentityActions,
|
||||
OrgPermissionSubjects,
|
||||
@@ -153,6 +154,7 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
<CopyButton value={id} size="xs" variant="plain" name="Token ID" />
|
||||
<VariablePermissionCan
|
||||
type={projectId ? "project" : "org"}
|
||||
I={
|
||||
|
||||
Reference in New Issue
Block a user