diff --git a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts
index d649b3ba5..9da2f4b69 100644
--- a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts
+++ b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts
@@ -440,7 +440,10 @@ export const registerSyncSecretsEndpoints = ,
+ newConfig: Record
+ ): boolean => {
+ switch (destination) {
+ case SecretSync.GitLab: {
+ const existingTargetEnv = existingConfig.targetEnvironment as string | undefined;
+ const newTargetEnv = newConfig.targetEnvironment as string | undefined;
+
+ // If either has wildcard '*', it conflicts with any targetEnvironment
+ if (existingTargetEnv === "*" || newTargetEnv === "*") {
+ return true;
+ }
+
+ return (
+ existingTargetEnv === newTargetEnv &&
+ ((newConfig.scope as string) === "group"
+ ? existingConfig.groupId === newConfig.groupId
+ : existingConfig.projectId === newConfig.projectId)
+ );
+ }
+ default:
+ // For other sync types, no special handling needed
+ return true;
+ }
+ };
+
const checkDuplicateDestination = async (
{ destination, destinationConfig, excludeSyncId, projectId }: TCheckDuplicateDestinationDTO,
actor: OrgServiceActor
@@ -753,7 +790,7 @@ export const secretSyncServiceFactory = ({
);
if (!destinationConfig || Object.keys(destinationConfig).length === 0) {
- return { hasDuplicate: false };
+ return { hasDuplicate: false, duplicateProjectId: undefined };
}
try {
@@ -765,15 +802,27 @@ export const secretSyncServiceFactory = ({
}
try {
- return deepEqualSkipFields(sync.destinationConfig, destinationConfig, skipFields);
+ const baseFieldsMatch = deepEqualSkipFields(sync.destinationConfig, destinationConfig, skipFields);
+ if (baseFieldsMatch) {
+ return handleSpecialCaseDuplicateCheck(
+ destination,
+ sync.destinationConfig as Record,
+ destinationConfig
+ );
+ }
+ return false;
} catch {
return false;
}
});
- return { hasDuplicate: duplicates.length > 0 };
+ const hasDuplicate = duplicates.length > 0;
+ return {
+ hasDuplicate,
+ duplicateProjectId: hasDuplicate ? duplicates[0].projectId : undefined
+ };
} catch (error) {
- return { hasDuplicate: false };
+ return { hasDuplicate: false, duplicateProjectId: undefined };
}
};
diff --git a/frontend/src/components/secret-syncs/forms/DuplicateDestinationConfirmationModal.tsx b/frontend/src/components/secret-syncs/forms/DuplicateDestinationConfirmationModal.tsx
index 45ec2c1be..82ab06038 100644
--- a/frontend/src/components/secret-syncs/forms/DuplicateDestinationConfirmationModal.tsx
+++ b/frontend/src/components/secret-syncs/forms/DuplicateDestinationConfirmationModal.tsx
@@ -5,13 +5,15 @@ type Props = {
onOpenChange: (isOpen: boolean) => void;
onConfirm: () => void;
isLoading?: boolean;
+ duplicateProjectId?: string;
};
export const DuplicateDestinationConfirmationModal = ({
isOpen,
onOpenChange,
onConfirm,
- isLoading
+ isLoading,
+ duplicateProjectId
}: Props) => {
return (
@@ -21,6 +23,14 @@ export const DuplicateDestinationConfirmationModal = ({
Another secret sync in your organization is already configured with the same
destination. Proceeding may cause conflicts or overwrite existing data.
+ {duplicateProjectId && (
+
+ Duplicate found in project ID:{" "}
+
+ {duplicateProjectId}
+
+
+ )}
Are you sure you want to continue?
diff --git a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx
index 2b313b3ff..3c0e4ea17 100644
--- a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx
+++ b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx
@@ -44,8 +44,9 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
const [destinationConfigToCheck, setDestinationConfigToCheck] = useState(null);
const [checkDuplicateEnabled, setCheckDuplicateEnabled] = useState(false);
+ const [storedDuplicateProjectId, setStoredDuplicateProjectId] = useState();
- const { data: hasDuplicate, isLoading: isCheckingDuplicate } = useCheckDuplicateDestination(
+ const { data: duplicateData, isLoading: isCheckingDuplicate } = useCheckDuplicateDestination(
secretSync.destination,
destinationConfigToCheck,
secretSync.projectId,
@@ -84,7 +85,8 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
useEffect(() => {
if (checkDuplicateEnabled && !isCheckingDuplicate && destinationConfigToCheck) {
- if (hasDuplicate) {
+ if (duplicateData?.hasDuplicate) {
+ setStoredDuplicateProjectId(duplicateData.duplicateProjectId);
setShowDuplicateConfirmation(true);
} else if (pendingFormData) {
performUpdate(pendingFormData);
@@ -96,7 +98,8 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
}, [
checkDuplicateEnabled,
isCheckingDuplicate,
- hasDuplicate,
+ duplicateData?.hasDuplicate,
+ duplicateData?.duplicateProjectId,
destinationConfigToCheck,
pendingFormData,
performUpdate
@@ -197,9 +200,15 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
{
+ setShowDuplicateConfirmation(open);
+ if (!open) {
+ setStoredDuplicateProjectId(undefined);
+ }
+ }}
onConfirm={handleConfirmDuplicate}
isLoading={updateSecretSync.isPending}
+ duplicateProjectId={storedDuplicateProjectId}
/>
>
);
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
index 76a885172..84801b000 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
@@ -67,7 +67,7 @@ export const SecretSyncReviewFields = () => {
const destinationName = SECRET_SYNC_MAP[destination].name;
- const { hasDuplicate, isChecking } = useDuplicateDestinationCheck({
+ const { hasDuplicate, duplicateProjectId, isChecking } = useDuplicateDestinationCheck({
destination,
projectId: currentProject?.id || "",
enabled: true,
@@ -192,10 +192,17 @@ export const SecretSyncReviewFields = () => {
-
- Another secret sync in your organization is already configured with the same
- destination. This may lead to conflicts or unexpected behavior.
-
+
+
+ Another secret sync in your organization is already configured with the same
+ destination. This may lead to conflicts or unexpected behavior.
+
+ {duplicateProjectId && (
+
+ Duplicate found in project ID: {duplicateProjectId}
+
+ )}
+
)}
diff --git a/frontend/src/hooks/api/secretSyncs/queries.tsx b/frontend/src/hooks/api/secretSyncs/queries.tsx
index 627ff15f3..250d02ff8 100644
--- a/frontend/src/hooks/api/secretSyncs/queries.tsx
+++ b/frontend/src/hooks/api/secretSyncs/queries.tsx
@@ -103,23 +103,28 @@ export const useCheckDuplicateDestination = (
projectId: string,
excludeSyncId?: string,
options?: Omit<
- UseQueryOptions>,
+ UseQueryOptions<
+ { hasDuplicate: boolean; duplicateProjectId?: string },
+ unknown,
+ { hasDuplicate: boolean; duplicateProjectId?: string },
+ ReturnType
+ >,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: secretSyncKeys.duplicateCheck(destination, destinationConfig, excludeSyncId),
queryFn: async () => {
- const { data } = await apiRequest.post<{ hasDuplicate: boolean }>(
- `/api/v1/secret-syncs/${destination}/check-destination`,
- {
- destinationConfig,
- excludeSyncId,
- projectId
- }
- );
+ const { data } = await apiRequest.post<{
+ hasDuplicate: boolean;
+ duplicateProjectId?: string;
+ }>(`/api/v1/secret-syncs/${destination}/check-destination`, {
+ destinationConfig,
+ excludeSyncId,
+ projectId
+ });
- return data.hasDuplicate;
+ return data;
},
enabled: Boolean(destinationConfig) && Object.keys(destinationConfig || {}).length > 0,
...options
diff --git a/frontend/src/hooks/api/secretSyncs/useDuplicateDestinationCheck.ts b/frontend/src/hooks/api/secretSyncs/useDuplicateDestinationCheck.ts
index c570e15a6..97985fba9 100644
--- a/frontend/src/hooks/api/secretSyncs/useDuplicateDestinationCheck.ts
+++ b/frontend/src/hooks/api/secretSyncs/useDuplicateDestinationCheck.ts
@@ -30,7 +30,7 @@ export const useDuplicateDestinationCheck = ({
const shouldCheck = enabled && hasValidConfig;
const {
- data: hasDuplicate,
+ data: duplicateData,
isLoading,
error,
refetch
@@ -41,7 +41,8 @@ export const useDuplicateDestinationCheck = ({
});
return {
- hasDuplicate: shouldCheck ? Boolean(hasDuplicate) : false,
+ hasDuplicate: shouldCheck ? Boolean(duplicateData?.hasDuplicate) : false,
+ duplicateProjectId: duplicateData?.duplicateProjectId,
isChecking: shouldCheck && isLoading,
hasError: Boolean(error),
hasValidConfig,