Add warning on same destination secret sync

This commit is contained in:
Carlos Monastyrski
2025-09-29 17:30:16 -03:00
parent 83755d0bba
commit 67f2072756
10 changed files with 401 additions and 25 deletions
+31
View File
@@ -53,3 +53,34 @@ export const titleCaseToCamelCase = (obj: unknown): unknown => {
return result;
};
export const deepEqual = (obj1: unknown, obj2: unknown): boolean => {
if (obj1 === obj2) return true;
if (obj1 === null || obj2 === null || obj1 === undefined || obj2 === undefined) {
return obj1 === obj2;
}
if (typeof obj1 !== typeof obj2) return false;
if (typeof obj1 !== "object") return obj1 === obj2;
if (Array.isArray(obj1) !== Array.isArray(obj2)) return false;
if (Array.isArray(obj1)) {
const arr1 = obj1 as unknown[];
const arr2 = obj2 as unknown[];
if (arr1.length !== arr2.length) return false;
return arr1.every((val, idx) => deepEqual(val, arr2[idx]));
}
const keys1 = Object.keys(obj1 as Record<string, unknown>).sort();
const keys2 = Object.keys(obj2 as Record<string, unknown>).sort();
if (keys1.length !== keys2.length) return false;
if (keys1.some((key, idx) => key !== keys2[idx])) return false;
return keys1.every((key) =>
deepEqual((obj1 as Record<string, unknown>)[key], (obj2 as Record<string, unknown>)[key])
);
};
@@ -425,4 +425,39 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
return { secretSync };
}
});
server.route({
method: "POST",
url: "/check-destination",
config: {
rateLimit: readLimit
},
schema: {
tags: [ApiDocsTags.SecretSyncs],
body: z.object({
destinationConfig: z.unknown(),
excludeSyncId: z.string().uuid().optional(),
projectId: z.string().uuid()
}),
response: {
200: z.object({ hasDuplicate: z.boolean() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { destinationConfig, excludeSyncId, projectId } = req.body;
const result = await server.services.secretSync.checkDuplicateDestination(
{
destinationConfig,
destination,
excludeSyncId,
projectId
},
req.permission
);
return result;
}
});
};
@@ -12,6 +12,7 @@ import {
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
import { DatabaseErrorCode } from "@app/lib/error-codes";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
import { deepEqual } from "@app/lib/fn/object";
import { OrgServiceActor } from "@app/lib/types";
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
@@ -20,6 +21,7 @@ import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { enterpriseSyncCheck, listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns";
import {
SecretSyncStatus,
TCheckDuplicateDestinationDTO,
TCreateSecretSyncDTO,
TDeleteSecretSyncDTO,
TFindSecretSyncByIdDTO,
@@ -696,6 +698,51 @@ export const secretSyncServiceFactory = ({
return updatedSecretSync as TSecretSync;
};
const checkDuplicateDestination = async (
{ destination, destinationConfig, excludeSyncId, projectId }: TCheckDuplicateDestinationDTO,
actor: OrgServiceActor
) => {
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSub.SecretSyncs
);
if (!destinationConfig || typeof destinationConfig !== "object") {
return { hasDuplicate: false };
}
try {
const existingSyncs = await secretSyncDAL.find({
destination
});
const duplicates = existingSyncs.filter((sync) => {
if (sync.id === excludeSyncId) {
return false;
}
try {
return deepEqual(sync.destinationConfig, destinationConfig);
} catch {
return false;
}
});
return { hasDuplicate: duplicates.length > 0 };
} catch (error) {
return { hasDuplicate: false };
}
};
return {
listSecretSyncOptions,
listSecretSyncsByProjectId,
@@ -707,6 +754,7 @@ export const secretSyncServiceFactory = ({
deleteSecretSync,
triggerSecretSyncSyncSecretsById,
triggerSecretSyncImportSecretsById,
triggerSecretSyncRemoveSecretsById
triggerSecretSyncRemoveSecretsById,
checkDuplicateDestination
};
};
@@ -324,6 +324,13 @@ export type TDeleteSecretSyncDTO = {
removeSecrets: boolean;
};
export type TCheckDuplicateDestinationDTO = {
destination: SecretSync;
destinationConfig: unknown;
excludeSyncId?: string;
projectId: string;
};
export enum SecretSyncStatus {
Pending = "pending",
Running = "running",