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
@@ -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>
@@ -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;
};
@@ -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
});
@@ -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;
};
@@ -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) => {
@@ -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"
@@ -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 />;
@@ -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`}
@@ -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>
)}
/>
</>
);
};
@@ -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."
}
)
})
});