Address PR comments

This commit is contained in:
Carlos Monastyrski
2025-09-19 03:43:22 -03:00
parent 8130be5e2f
commit 27dbe5b013
72 changed files with 2913 additions and 1367 deletions

View File

@@ -15,7 +15,7 @@ type ContentProps = {
};
const Content = ({ pkiSync, onComplete }: ContentProps) => {
const { id: syncId, destination, projectId } = pkiSync;
const { id: syncId, destination } = pkiSync;
const destinationName = PKI_SYNC_MAP[destination].name;
const triggerImportCertificates = useTriggerPkiSyncImportCertificates();
@@ -24,7 +24,6 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => {
try {
await triggerImportCertificates.mutateAsync({
syncId,
projectId,
destination
});
@@ -57,7 +56,7 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => {
</p>
<p className="mb-6 text-xs text-bunker-300">
This operation will retrieve certificates from {destinationName} and make them available in
your PKI collection. Only certificates that are not already imported will be processed.
your PKI subscriber. Only certificates that are not already imported will be processed.
</p>
<div className="mt-8 flex w-full items-center justify-between gap-2">
<ModalClose asChild>

View File

@@ -13,10 +13,10 @@ import { twMerge } from "tailwind-merge";
import { Badge, Tooltip } from "@app/components/v2";
import { BadgeProps } from "@app/components/v2/Badge/Badge";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { PkiSyncData, PkiSyncStatus } from "@app/hooks/api/pkiSyncs";
import { PkiSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs";
type Props = {
pkiSync: PkiSyncData;
pkiSync: TPkiSync;
className?: string;
mini?: boolean;
};

View File

@@ -15,7 +15,7 @@ type ContentProps = {
};
const Content = ({ pkiSync, onComplete }: ContentProps) => {
const { id: syncId, destination, projectId } = pkiSync;
const { id: syncId, destination } = pkiSync;
const destinationName = PKI_SYNC_MAP[destination].name;
const triggerRemoveCertificates = useTriggerPkiSyncRemoveCertificates();
@@ -24,7 +24,6 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => {
try {
await triggerRemoveCertificates.mutateAsync({
syncId,
projectId,
destination
});

View File

@@ -13,10 +13,10 @@ import { twMerge } from "tailwind-merge";
import { Badge, Tooltip } from "@app/components/v2";
import { BadgeProps } from "@app/components/v2/Badge/Badge";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { PkiSyncData, PkiSyncStatus } from "@app/hooks/api/pkiSyncs";
import { PkiSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs";
type Props = {
pkiSync: PkiSyncData;
pkiSync: TPkiSync;
className?: string;
mini?: boolean;
};

View File

@@ -13,13 +13,13 @@ import {
THead,
Tr
} from "@app/components/v2";
import { PkiSyncData } from "@app/hooks/api/pkiSyncs";
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
type Props = {
pkiSyncs: PkiSyncData[];
onEdit: (pkiSync: PkiSyncData) => void;
onDelete: (pkiSync: PkiSyncData) => void;
onTrigger: (pkiSync: PkiSyncData) => void;
pkiSyncs: TPkiSync[];
onEdit: (pkiSync: TPkiSync) => void;
onDelete: (pkiSync: TPkiSync) => void;
onTrigger: (pkiSync: TPkiSync) => void;
};
const getSyncStatusBadge = (status?: string) => {

View File

@@ -50,8 +50,9 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props)
destination,
isAutoSyncEnabled: true,
syncOptions: {
canImportCertificates: syncOption?.canImportCertificates ?? true,
canRemoveCertificates: syncOption?.canRemoveCertificates ?? true
canImportCertificates: syncOption?.canImportCertificates ?? false,
canRemoveCertificates: syncOption?.canRemoveCertificates ?? false,
certificateNameSchema: syncOption?.defaultCertificateNameSchema
}
} as Partial<TPkiSyncForm>,
reValidateMode: "onChange"
@@ -129,10 +130,11 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props)
Certificate Sync Behavior
</div>
<p className="mt-1 text-sm text-bunker-200">
Certificate Syncs are the source of truth for connected third-party services. Any
certificate, including associated data, not present or imported in Infisical before
syncing will be overwritten, and changes made directly in the connected service outside
of infisical may also be overwritten by future syncs.
Certificate Syncs manage certificates that are prefixed with &quot;Infisical-&quot; in
the destination. Only certificates managed by Infisical will be affected during sync
operations. Certificates not created or managed by Infisical will remain untouched, and
changes made to Infisical-managed certificates directly in the destination service may
be overwritten by future syncs.
</p>
</div>
<div className="mt-4 flex gap-4">
@@ -190,7 +192,7 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props)
<PkiSyncDestinationFields />
</Tab.Panel>
<Tab.Panel>
<PkiSyncOptionsFields />
<PkiSyncOptionsFields destination={destination} />
<Controller
control={control}
name="isAutoSyncEnabled"

View File

@@ -69,7 +69,7 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => {
Component = <PkiSyncDestinationFields />;
break;
case PkiSyncEditFields.Options:
Component = <PkiSyncOptionsFields />;
Component = <PkiSyncOptionsFields destination={pkiSync.destination} />;
break;
case PkiSyncEditFields.Source:
Component = <PkiSyncSourceFields />;

View File

@@ -4,8 +4,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "@tanstack/react-router";
import { FilterableSelect, FormControl } from "@app/components/v2";
import { OrgPermissionSubjects, useOrgPermission, useProject } from "@app/context";
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context";
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
import { PKI_SYNC_CONNECTION_MAP } from "@app/helpers/pkiSyncs";
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
@@ -17,7 +17,7 @@ type Props = {
};
export const PkiSyncConnectionField = ({ onChange: callback }: Props) => {
const { permission } = useOrgPermission();
const { permission } = useProjectPermission();
const { control, watch } = useFormContext<TPkiSyncForm>();
const { currentProject } = useProject();
@@ -32,8 +32,8 @@ export const PkiSyncConnectionField = ({ onChange: callback }: Props) => {
const connectionName = APP_CONNECTION_MAP[app].name;
const canCreateConnection = permission.can(
OrgPermissionAppConnectionActions.Create,
OrgPermissionSubjects.AppConnections
ProjectPermissionAppConnectionActions.Create,
ProjectPermissionSub.AppConnections
);
const appName = APP_CONNECTION_MAP[PKI_SYNC_CONNECTION_MAP[destination]].name;
@@ -47,7 +47,6 @@ export const PkiSyncConnectionField = ({ onChange: callback }: Props) => {
<Controller
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipText="App Connections can be created from the Organization Settings page."
isError={Boolean(error)}
errorText={error?.message}
label={`${connectionName} Connection`}

View File

@@ -2,12 +2,19 @@ import { Controller, useFormContext } from "react-hook-form";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FormControl, Switch, Tooltip } from "@app/components/v2";
import { FormControl, Input, Switch, Tooltip } from "@app/components/v2";
import { PkiSync, usePkiSyncOption } from "@app/hooks/api/pkiSyncs";
import { TPkiSyncForm } from "../schemas";
export const PkiSyncOptionsFields = () => {
const { control } = useFormContext<TPkiSyncForm>();
type Props = {
destination?: PkiSync;
};
export const PkiSyncOptionsFields = ({ destination }: Props) => {
const { control, watch } = useFormContext<TPkiSyncForm>();
const currentDestination = destination || watch("destination");
const { syncOption } = usePkiSyncOption(currentDestination);
return (
<>
@@ -87,6 +94,66 @@ export const PkiSyncOptionsFields = () => {
</FormControl>
)}
/>
<Controller
control={control}
name="syncOptions.certificateNameSchema"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipClassName="max-w-md"
tooltipText={
<div className="flex flex-col gap-3">
<span>
When a certificate is synced, values will be injected into the certificate name
schema before it reaches the destination. This is useful for organization.
</span>
<div className="flex flex-col">
<span>Available placeholders:</span>
<ul className="list-disc pl-4 text-sm">
<li>
<code>{"{{certificateId}}"}</code> - The unique ID of the certificate
</li>
<li>
<code>{"{{environment}}"}</code> - The environment which the certificate is in
(e.g. dev, staging, prod)
</li>
</ul>
</div>
{syncOption?.forbiddenCharacters && syncOption.forbiddenCharacters.length > 0 && (
<div className="flex flex-col">
<span className="text-yellow">
Character restrictions for {syncOption.name}:
</span>
<div className="text-xs text-bunker-300">
The following characters are not allowed:{" "}
{syncOption.forbiddenCharacters.split("").join(" ")}
</div>
{syncOption.allowedCharacterPattern && (
<div className="mt-1 text-xs text-bunker-300">
Only alphanumeric characters and hyphens are allowed (a-z, A-Z, 0-9, -)
</div>
)}
</div>
)}
</div>
}
isError={Boolean(error)}
isOptional
errorText={error?.message}
label="Certificate Name Schema"
helperText="Infisical strongly advises setting a Certificate Name Schema to ensure that Infisical only manages the specific certificates you intend, keeping everything else untouched."
>
<Input
value={value || ""}
onChange={(e) => onChange(e.target.value || undefined)}
placeholder={
syncOption?.defaultCertificateNameSchema || "INFISICAL_{{certificateId}}"
}
/>
</FormControl>
)}
/>
</>
);
};

View File

@@ -21,7 +21,38 @@ export const PkiSyncFormSchema = z.object({
}),
syncOptions: z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(true)
canRemoveCertificates: z.boolean().default(false),
certificateNameSchema: z
.string()
.optional()
.refine(
(val) => {
if (!val) return true;
const allowedOptionalPlaceholders = ["{{environment}}"];
const allowedPlaceholdersRegexPart = ["{{certificateId}}", ...allowedOptionalPlaceholders]
.map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")) // Escape regex special characters
.join("|");
const allowedContentRegex = new RegExp(
`^([a-zA-Z0-9_\\-/]|${allowedPlaceholdersRegexPart})*$`
);
const contentIsValid = allowedContentRegex.test(val);
if (val.trim()) {
const certificateIdRegex = /\{\{certificateId\}\}/;
const certificateIdIsPresent = certificateIdRegex.test(val);
return contentIsValid && certificateIdIsPresent;
}
return contentIsValid;
},
{
message:
"Certificate name schema must include exactly one {{certificateId}} placeholder. It can also include {{environment}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), dashes (-), underscores (_), and slashes (/) are allowed besides the placeholders."
}
)
})
});

View File

@@ -2,6 +2,3 @@ export * from "./enums";
export * from "./mutations";
export * from "./queries";
export * from "./types";
// Legacy alias for backward compatibility
export type PkiSyncData = import("./types").TPkiSync;

View File

@@ -5,7 +5,7 @@ import { pkiSyncKeys } from "@app/hooks/api/pkiSyncs/queries";
import {
TCreatePkiSyncDTO,
TDeletePkiSyncDTO,
TPkiSyncResponse,
TPkiSync,
TTriggerPkiSyncImportCertificatesDTO,
TTriggerPkiSyncRemoveCertificatesDTO,
TTriggerPkiSyncSyncCertificatesDTO,
@@ -16,12 +16,9 @@ export const useCreatePkiSync = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ destination, ...params }: TCreatePkiSyncDTO) => {
const { data } = await apiRequest.post<TPkiSyncResponse>(
`/api/v1/pki-syncs/${destination}`,
params
);
const { data } = await apiRequest.post<TPkiSync>(`/api/v1/pki/syncs/${destination}`, params);
return data.pkiSync;
return data;
},
onSuccess: (_, { projectId }) =>
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.list(projectId) })
@@ -32,13 +29,13 @@ export const useUpdatePkiSync = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, projectId, destination, ...params }: TUpdatePkiSyncDTO) => {
const { data } = await apiRequest.patch<TPkiSyncResponse>(
`/api/v1/pki-syncs/${destination}/${syncId}`,
const { data } = await apiRequest.patch<TPkiSync>(
`/api/v1/pki/syncs/${destination}/${syncId}`,
params,
{ params: { projectId } }
);
return data.pkiSync;
return data;
},
onSuccess: (_, { syncId, projectId }) => {
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.list(projectId) });
@@ -51,7 +48,7 @@ export const useDeletePkiSync = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, projectId, destination }: TDeletePkiSyncDTO) => {
const { data } = await apiRequest.delete(`/api/v1/pki-syncs/${destination}/${syncId}`, {
const { data } = await apiRequest.delete(`/api/v1/pki/syncs/${destination}/${syncId}`, {
params: { projectId }
});
@@ -67,20 +64,15 @@ export const useDeletePkiSync = () => {
export const useTriggerPkiSyncSyncCertificates = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, projectId, destination }: TTriggerPkiSyncSyncCertificatesDTO) => {
const { data } = await apiRequest.post(
`/api/v1/pki-syncs/${destination}/${syncId}/sync`,
{},
{
params: { projectId }
}
);
mutationFn: async ({ syncId, destination }: TTriggerPkiSyncSyncCertificatesDTO) => {
const { data } = await apiRequest.post(`/api/v1/pki/syncs/${destination}/${syncId}/sync`);
return data;
},
onSuccess: (_, { syncId, projectId }) => {
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.list(projectId) });
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.byId(syncId, projectId) });
onSuccess: (_, { syncId }) => {
// Invalidate all PKI sync queries since we don't have projectId here
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.all });
queryClient.invalidateQueries({ queryKey: ["pkiSync", syncId] });
}
});
};
@@ -88,24 +80,15 @@ export const useTriggerPkiSyncSyncCertificates = () => {
export const useTriggerPkiSyncImportCertificates = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
syncId,
projectId,
destination
}: TTriggerPkiSyncImportCertificatesDTO) => {
const { data } = await apiRequest.post(
`/api/v1/pki-syncs/${destination}/${syncId}/import`,
{},
{
params: { projectId }
}
);
mutationFn: async ({ syncId, destination }: TTriggerPkiSyncImportCertificatesDTO) => {
const { data } = await apiRequest.post(`/api/v1/pki/syncs/${destination}/${syncId}/import`);
return data;
},
onSuccess: (_, { syncId, projectId }) => {
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.list(projectId) });
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.byId(syncId, projectId) });
onSuccess: (_, { syncId }) => {
// Invalidate all PKI sync queries since we don't have projectId here
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.all });
queryClient.invalidateQueries({ queryKey: ["pkiSync", syncId] });
}
});
};
@@ -113,24 +96,15 @@ export const useTriggerPkiSyncImportCertificates = () => {
export const useTriggerPkiSyncRemoveCertificates = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
syncId,
projectId,
destination
}: TTriggerPkiSyncRemoveCertificatesDTO) => {
const { data } = await apiRequest.post(
`/api/v1/pki-syncs/${destination}/${syncId}/remove`,
{},
{
params: { projectId }
}
);
mutationFn: async ({ syncId, destination }: TTriggerPkiSyncRemoveCertificatesDTO) => {
const { data } = await apiRequest.post(`/api/v1/pki/syncs/${destination}/${syncId}/remove`);
return data;
},
onSuccess: (_, { syncId, projectId }) => {
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.list(projectId) });
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.byId(syncId, projectId) });
onSuccess: (_, { syncId }) => {
// Invalidate all PKI sync queries since we don't have projectId here
queryClient.invalidateQueries({ queryKey: pkiSyncKeys.all });
queryClient.invalidateQueries({ queryKey: ["pkiSync", syncId] });
}
});
};

View File

@@ -2,12 +2,7 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { PkiSync, TPkiSyncOption } from "@app/hooks/api/pkiSyncs";
import {
TListPkiSyncOptions,
TListPkiSyncs,
TPkiSync,
TPkiSyncResponse
} from "@app/hooks/api/pkiSyncs/types";
import { TListPkiSyncOptions, TListPkiSyncs, TPkiSync } from "@app/hooks/api/pkiSyncs/types";
export const pkiSyncKeys = {
all: ["pki-sync"] as const,
@@ -31,7 +26,7 @@ export const usePkiSyncOptions = (
return useQuery({
queryKey: pkiSyncKeys.options(),
queryFn: async () => {
const { data } = await apiRequest.get<TListPkiSyncOptions>("/api/v1/pki-syncs/options");
const { data } = await apiRequest.get<TListPkiSyncOptions>("/api/v1/pki/syncs/options");
return data.pkiSyncOptions;
},
@@ -47,7 +42,7 @@ export const usePkiSyncOption = (destination: PkiSync) => {
};
export const fetchPkiSyncsByProjectId = async (projectId: string) => {
const { data } = await apiRequest.get<TListPkiSyncs>("/api/v1/pki-syncs", {
const { data } = await apiRequest.get<TListPkiSyncs>("/api/v1/pki/syncs", {
params: { projectId }
});
@@ -78,11 +73,11 @@ export const useGetPkiSync = (
return useQuery({
queryKey: pkiSyncKeys.byId(syncId, projectId),
queryFn: async () => {
const { data } = await apiRequest.get<TPkiSyncResponse>(`/api/v1/pki-syncs/${syncId}`, {
const { data } = await apiRequest.get<TPkiSync>(`/api/v1/pki/syncs/${syncId}`, {
params: { projectId }
});
return data.pkiSync;
return data;
},
...options
});

View File

@@ -5,6 +5,8 @@ import { PkiSyncStatus } from "../enums";
export type RootPkiSyncOptions = {
canImportCertificates: boolean;
canRemoveCertificates: boolean;
certificateNamePrefix?: string;
certificateNameSchema?: string;
};
export type TRootPkiSync = {

View File

@@ -9,6 +9,11 @@ export type TPkiSyncOption = {
canImportCertificates: boolean;
canRemoveCertificates: boolean;
enterprise?: boolean;
defaultCertificateNameSchema?: string;
forbiddenCharacters?: string;
allowedCharacterPattern?: string;
maxCertificateNameLength?: number;
minCertificateNameLength?: number;
};
export type TPkiSync = TAzureKeyVaultPkiSync;
@@ -16,7 +21,6 @@ export type TPkiSync = TAzureKeyVaultPkiSync;
export type TListPkiSyncs = { pkiSyncs: TPkiSync[] };
export type TListPkiSyncOptions = { pkiSyncOptions: TPkiSyncOption[] };
export type TPkiSyncResponse = { pkiSync: TPkiSync };
export type TCreatePkiSyncDTO = DiscriminativePick<
TPkiSync,
@@ -43,19 +47,16 @@ export type TDeletePkiSyncDTO = {
export type TTriggerPkiSyncSyncCertificatesDTO = {
syncId: string;
projectId: string;
destination: PkiSync;
};
export type TTriggerPkiSyncImportCertificatesDTO = {
syncId: string;
projectId: string;
destination: PkiSync;
};
export type TTriggerPkiSyncRemoveCertificatesDTO = {
syncId: string;
projectId: string;
destination: PkiSync;
};

View File

@@ -1,10 +1,10 @@
import { PkiSyncData } from "@app/hooks/api/pkiSyncs";
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
import { getPkiSyncDestinationColValues } from "../helpers";
import { PkiSyncTableCell } from "../PkiSyncTableCell";
type Props = {
pkiSync: PkiSyncData;
pkiSync: TPkiSync;
};
export const PkiSyncDestinationCol = ({ pkiSync }: Props) => {

View File

@@ -44,18 +44,18 @@ import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { useToggle } from "@app/hooks";
import { PkiSyncData, PkiSyncStatus } from "@app/hooks/api/pkiSyncs";
import { PkiSyncStatus, TPkiSync, usePkiSyncOption } from "@app/hooks/api/pkiSyncs";
import { PkiSyncDestinationCol } from "./PkiSyncDestinationCol";
import { PkiSyncTableCell } from "./PkiSyncTableCell";
type Props = {
pkiSync: PkiSyncData;
onDelete: (pkiSync: PkiSyncData) => void;
onTriggerSyncCertificates: (pkiSync: PkiSyncData) => void;
onTriggerImportCertificates: (pkiSync: PkiSyncData) => void;
onTriggerRemoveCertificates: (pkiSync: PkiSyncData) => void;
onToggleEnable: (pkiSync: PkiSyncData) => void;
pkiSync: TPkiSync;
onDelete: (pkiSync: TPkiSync) => void;
onTriggerSyncCertificates: (pkiSync: TPkiSync) => void;
onTriggerImportCertificates: (pkiSync: TPkiSync) => void;
onTriggerRemoveCertificates: (pkiSync: TPkiSync) => void;
onToggleEnable: (pkiSync: TPkiSync) => void;
};
export const PkiSyncRow = ({
@@ -82,6 +82,8 @@ export const PkiSyncRow = ({
const destinationName = PKI_SYNC_MAP[destination].name;
const { syncOption } = usePkiSyncOption(destination);
const [isIdCopied, setIsIdCopied] = useToggle(false);
const handleCopyId = useCallback(() => {
@@ -227,7 +229,7 @@ export const PkiSyncRow = ({
{!isAutoSyncEnabled && (
<Tooltip
className="text-xs"
content="Auto-Sync is disabled. Changes to the PKI subscriber will not be automatically synced to the destination."
content="Auto-Sync is disabled. Certificate changes in the PKI subscriber will not be automatically synced to the destination."
>
<div>
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
@@ -237,7 +239,7 @@ export const PkiSyncRow = ({
</div>
</Tooltip>
)}
<PkiSyncImportStatusBadge mini pkiSync={pkiSync} />
{syncOption?.canImportCertificates && <PkiSyncImportStatusBadge mini pkiSync={pkiSync} />}
<PkiSyncRemoveStatusBadge mini pkiSync={pkiSync} />
</div>
</Td>
@@ -294,36 +296,38 @@ export const PkiSyncRow = ({
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionPkiSyncActions.ImportCertificates}
a={permissionSubject}
>
{(isAllowed: boolean) => (
<DropdownMenuItem
icon={<FontAwesomeIcon icon={faDownload} />}
onClick={(e) => {
e.stopPropagation();
onTriggerImportCertificates(pkiSync);
}}
isDisabled={!isAllowed}
>
<Tooltip
position="left"
sideOffset={42}
content={`Import certificates from this ${destinationName} destination into Infisical.`}
{syncOption?.canImportCertificates && (
<ProjectPermissionCan
I={ProjectPermissionPkiSyncActions.ImportCertificates}
a={permissionSubject}
>
{(isAllowed: boolean) => (
<DropdownMenuItem
icon={<FontAwesomeIcon icon={faDownload} />}
onClick={(e) => {
e.stopPropagation();
onTriggerImportCertificates(pkiSync);
}}
isDisabled={!isAllowed}
>
<div className="flex h-full w-full items-center justify-between gap-1">
<span>Import Certificates</span>
<FontAwesomeIcon
className="text-bunker-300"
size="sm"
icon={faInfoCircle}
/>
</div>
</Tooltip>
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<Tooltip
position="left"
sideOffset={42}
content={`Import certificates from this ${destinationName} destination into Infisical.`}
>
<div className="flex h-full w-full items-center justify-between gap-1">
<span>Import Certificates</span>
<FontAwesomeIcon
className="text-bunker-300"
size="sm"
icon={faInfoCircle}
/>
</div>
</Tooltip>
</DropdownMenuItem>
)}
</ProjectPermissionCan>
)}
<ProjectPermissionCan
I={ProjectPermissionPkiSyncActions.RemoveCertificates}
a={permissionSubject}

View File

@@ -46,8 +46,8 @@ import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import {
PkiSync,
PkiSyncData,
PkiSyncStatus,
TPkiSync,
useTriggerPkiSyncSyncCertificates,
useUpdatePkiSync
} from "@app/hooks/api/pkiSyncs";
@@ -82,7 +82,7 @@ const getSyncStatusOrderValue = (syncStatus: PkiSyncStatus | null) => {
};
type Props = {
pkiSyncs: PkiSyncData[];
pkiSyncs: TPkiSync[];
};
const STATUS_ICON_MAP = {
@@ -213,15 +213,15 @@ export const PkiSyncsTable = ({ pkiSyncs }: Props) => {
const isTableFiltered = Boolean(filters.destinations.length || filters.status.length);
const handleDelete = (pkiSync: PkiSyncData) => handlePopUpOpen("deleteSync", pkiSync);
const handleDelete = (pkiSync: TPkiSync) => handlePopUpOpen("deleteSync", pkiSync);
const handleTriggerImportCertificates = (pkiSync: PkiSyncData) =>
const handleTriggerImportCertificates = (pkiSync: TPkiSync) =>
handlePopUpOpen("importCertificates", pkiSync);
const handleTriggerRemoveCertificates = (pkiSync: PkiSyncData) =>
const handleTriggerRemoveCertificates = (pkiSync: TPkiSync) =>
handlePopUpOpen("removeCertificates", pkiSync);
const handleToggleEnableSync = async (pkiSync: PkiSyncData) => {
const handleToggleEnableSync = async (pkiSync: TPkiSync) => {
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
const isAutoSyncEnabled = !pkiSync.isAutoSyncEnabled;
@@ -246,13 +246,12 @@ export const PkiSyncsTable = ({ pkiSyncs }: Props) => {
}
};
const handleTriggerSync = async (pkiSync: PkiSyncData) => {
const handleTriggerSync = async (pkiSync: TPkiSync) => {
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
try {
await triggerSync.mutateAsync({
syncId: pkiSync.id,
projectId: pkiSync.projectId,
destination: pkiSync.destination
});
@@ -462,11 +461,13 @@ export const PkiSyncsTable = ({ pkiSyncs }: Props) => {
isOpen={popUp.deleteSync.isOpen}
pkiSync={popUp.deleteSync.data}
/>
<PkiSyncImportCertificatesModal
onOpenChange={(isOpen) => handlePopUpToggle("importCertificates", isOpen)}
isOpen={popUp.importCertificates.isOpen}
pkiSync={popUp.importCertificates.data}
/>
{popUp.importCertificates.data && (
<PkiSyncImportCertificatesModal
onOpenChange={(isOpen) => handlePopUpToggle("importCertificates", isOpen)}
isOpen={popUp.importCertificates.isOpen}
pkiSync={popUp.importCertificates.data}
/>
)}
<PkiSyncRemoveCertificatesModal
onOpenChange={(isOpen) => handlePopUpToggle("removeCertificates", isOpen)}
isOpen={popUp.removeCertificates.isOpen}

View File

@@ -1,6 +1,6 @@
import { PkiSyncData } from "@app/hooks/api/pkiSyncs";
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
export const getPkiSyncDestinationColValues = (pkiSync: PkiSyncData) => {
export const getPkiSyncDestinationColValues = (pkiSync: TPkiSync) => {
const { destination } = pkiSync;
switch (destination) {

View File

@@ -42,6 +42,7 @@ import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { usePopUp, useToggle } from "@app/hooks";
import {
TPkiSync,
usePkiSyncOption,
useTriggerPkiSyncSyncCertificates,
useUpdatePkiSync
} from "@app/hooks/api/pkiSyncs";
@@ -67,6 +68,8 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
const triggerSyncMutation = useTriggerPkiSyncSyncCertificates();
const updatePkiSyncMutation = useUpdatePkiSync();
const { syncOption } = usePkiSyncOption(destination);
const destinationName = PKI_SYNC_MAP[destination].name;
const handleCopyId = useCallback(() => {
@@ -86,7 +89,6 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
try {
await triggerSyncMutation.mutateAsync({
syncId: id,
projectId,
destination
});
createNotification({
@@ -100,7 +102,7 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
type: "error"
});
}
}, [triggerSyncMutation, id, projectId]);
}, [triggerSyncMutation, id, destination]);
const handleToggleAutoSync = useCallback(async () => {
try {
@@ -123,14 +125,14 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
}
}, [updatePkiSyncMutation, id, projectId, pkiSync.isAutoSyncEnabled]);
const permissionSubject = subscriberId
? subject(ProjectPermissionSub.PkiSyncs, { subscriberId })
: ProjectPermissionSub.PkiSyncs;
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: subscriberId || ""
});
return (
<>
<div className="ml-auto mt-4 flex flex-wrap items-center justify-end gap-2">
<PkiSyncImportStatusBadge pkiSync={pkiSync} />
{syncOption?.canImportCertificates && <PkiSyncImportStatusBadge pkiSync={pkiSync} />}
<PkiSyncRemoveStatusBadge pkiSync={pkiSync} />
{pkiSync.isAutoSyncEnabled ? (
<Badge
@@ -143,7 +145,7 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
) : (
<Tooltip
className="text-xs"
content="Auto-Sync is disabled. Changes to the PKI subscriber will not be automatically synced to the destination."
content="Auto-Sync is disabled. Certificate changes in the PKI subscriber will not be automatically synced to the destination."
>
<div>
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
@@ -192,33 +194,35 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
Copy Sync ID
</DropdownMenuItem>
<ProjectPermissionCan
I={ProjectPermissionPkiSyncActions.ImportCertificates}
a={permissionSubject}
>
{(isAllowed: boolean) => (
<DropdownMenuItem
icon={<FontAwesomeIcon icon={faDownload} />}
onClick={() => handlePopUpOpen("importCertificates")}
isDisabled={!isAllowed}
>
<Tooltip
position="left"
sideOffset={42}
content={`Import certificates from this ${destinationName} destination into Infisical.`}
{syncOption?.canImportCertificates && (
<ProjectPermissionCan
I={ProjectPermissionPkiSyncActions.ImportCertificates}
a={permissionSubject}
>
{(isAllowed: boolean) => (
<DropdownMenuItem
icon={<FontAwesomeIcon icon={faDownload} />}
onClick={() => handlePopUpOpen("importCertificates")}
isDisabled={!isAllowed}
>
<div className="flex h-full w-full items-center justify-between gap-1">
<span>Import Certificates</span>
<FontAwesomeIcon
className="text-bunker-300"
size="sm"
icon={faInfoCircle}
/>
</div>
</Tooltip>
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<Tooltip
position="left"
sideOffset={42}
content={`Import certificates from this ${destinationName} destination into Infisical.`}
>
<div className="flex h-full w-full items-center justify-between gap-1">
<span>Import Certificates</span>
<FontAwesomeIcon
className="text-bunker-300"
size="sm"
icon={faInfoCircle}
/>
</div>
</Tooltip>
</DropdownMenuItem>
)}
</ProjectPermissionCan>
)}
<ProjectPermissionCan
I={ProjectPermissionPkiSyncActions.RemoveCertificates}
@@ -283,11 +287,13 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
</div>
</div>
<PkiSyncImportCertificatesModal
onOpenChange={(isOpen) => handlePopUpToggle("importCertificates", isOpen)}
isOpen={popUp.importCertificates.isOpen}
pkiSync={pkiSync}
/>
{syncOption?.canImportCertificates && (
<PkiSyncImportCertificatesModal
onOpenChange={(isOpen) => handlePopUpToggle("importCertificates", isOpen)}
isOpen={popUp.importCertificates.isOpen}
pkiSync={pkiSync}
/>
)}
<PkiSyncRemoveCertificatesModal
onOpenChange={(isOpen) => handlePopUpToggle("removeCertificates", isOpen)}
isOpen={popUp.removeCertificates.isOpen}

View File

@@ -1752,7 +1752,6 @@ const SecretsManagerPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.SecretApproval]: enabled,
[ProjectPermissionSub.Integrations]: enabled,
[ProjectPermissionSub.SecretSyncs]: enabled,
[ProjectPermissionSub.PkiSyncs]: enabled,
[ProjectPermissionSub.Kms]: enabled,
[ProjectPermissionSub.Environments]: enabled,
[ProjectPermissionSub.Tags]: enabled,
@@ -1774,6 +1773,7 @@ const CertificateManagerPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.PkiCollections]: enabled,
[ProjectPermissionSub.PkiAlerts]: enabled,
[ProjectPermissionSub.PkiSubscribers]: enabled,
[ProjectPermissionSub.PkiSyncs]: enabled,
[ProjectPermissionSub.CertificateAuthorities]: enabled,
[ProjectPermissionSub.CertificateTemplates]: enabled,
[ProjectPermissionSub.Certificates]: enabled
@@ -2021,6 +2021,10 @@ export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.ReadPrivateKey
]
},
{
subject: ProjectPermissionSub.PkiSyncs,
actions: [ProjectPermissionPkiSyncActions.Read]
}
]
},
@@ -2048,6 +2052,10 @@ export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
{
subject: ProjectPermissionSub.Certificates,
actions: Object.values(ProjectPermissionCertificateActions)
},
{
subject: ProjectPermissionSub.PkiSyncs,
actions: Object.values(ProjectPermissionPkiSyncActions)
}
]
},
@@ -2148,10 +2156,6 @@ export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
subject: ProjectPermissionSub.SecretSyncs,
actions: [ProjectPermissionSecretSyncActions.Read]
},
{
subject: ProjectPermissionSub.PkiSyncs,
actions: [ProjectPermissionPkiSyncActions.Read]
},
{
subject: ProjectPermissionSub.Commits,
actions: [ProjectPermissionCommitsActions.Read]
@@ -2213,10 +2217,6 @@ export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
subject: ProjectPermissionSub.SecretSyncs,
actions: Object.values(ProjectPermissionSecretSyncActions)
},
{
subject: ProjectPermissionSub.PkiSyncs,
actions: Object.values(ProjectPermissionPkiSyncActions)
},
{
subject: ProjectPermissionSub.Commits,
actions: Object.values(ProjectPermissionCommitsActions)