From d5065af7e9bc65f7e21113dff7f9417919d68ae9 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 25 Apr 2025 15:26:34 -0300 Subject: [PATCH 1/7] feat(secrets-ui): add secret syncs to referenced secret warning --- .../src/server/routes/v1/dashboard-router.ts | 43 ++++++++++++ .../secret-sync/secret-sync-service.ts | 30 +++++++++ .../services/secret-sync/secret-sync-types.ts | 7 ++ frontend/src/hooks/api/dashboard/types.ts | 2 + .../OverviewPage/OverviewPage.tsx | 4 +- .../SelectionPanel/SelectionPanel.tsx | 11 ++- .../SecretDashboardPage.tsx | 3 + .../components/ActionBar/ActionBar.tsx | 5 +- .../CollapsibleSecretImports.tsx | 67 +++++++++++++++++-- .../SecretListView/SecretListView.tsx | 3 + 10 files changed, 165 insertions(+), 10 deletions(-) diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index 5a52d4748..846e25eb8 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -166,6 +166,14 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { }) .array() .optional(), + usedBySecretSyncs: z + .object({ + name: z.string(), + destination: z.string(), + environment: z.string() + }) + .array() + .optional(), totalFolderCount: z.number().optional(), totalDynamicSecretCount: z.number().optional(), totalSecretCount: z.number().optional(), @@ -500,6 +508,21 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { } } + const usedBySecretSyncs: { name: string; destination: string; environment: string }[] = []; + for await (const environment of environments) { + const secretSyncs = await server.services.secretSync.listSecretSyncsBySecretPath( + { projectId, secretPath, environment }, + req.permission + ); + secretSyncs.forEach((sync) => { + usedBySecretSyncs.push({ + name: sync.name, + destination: sync.destination, + environment + }); + }); + } + return { folders, dynamicSecrets, @@ -512,6 +535,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { totalSecretCount, totalSecretRotationCount, importedByEnvs, + usedBySecretSyncs, totalCount: (totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + @@ -605,6 +629,14 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { totalFolderCount: z.number().optional(), totalDynamicSecretCount: z.number().optional(), totalSecretCount: z.number().optional(), + usedBySecretSyncs: z + .object({ + name: z.string(), + destination: z.string(), + environment: z.string() + }) + .array() + .optional(), importedBy: z .object({ environment: z.object({ @@ -898,6 +930,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { secrets }); + const secretSyncs = await server.services.secretSync.listSecretSyncsBySecretPath( + { projectId, secretPath, environment }, + req.permission + ); + const usedBySecretSyncs = secretSyncs.map((sync) => ({ + name: sync.name, + destination: sync.destination, + environment: environment + })); + if (secrets?.length || secretRotations?.length) { const secretCount = (secrets?.length ?? 0) + @@ -944,6 +986,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { totalSecretCount, totalSecretRotationCount, importedBy, + usedBySecretSyncs, totalCount: (totalImportCount ?? 0) + (totalFolderCount ?? 0) + diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 14a1a1cf0..a19ebe6f7 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -23,6 +23,7 @@ import { TDeleteSecretSyncDTO, TFindSecretSyncByIdDTO, TFindSecretSyncByNameDTO, + TListSecretSyncsByFolderId, TListSecretSyncsByProjectId, TSecretSync, TTriggerSecretSyncImportSecretsByIdDTO, @@ -85,6 +86,34 @@ export const secretSyncServiceFactory = ({ return secretSyncs as TSecretSync[]; }; + const listSecretSyncsBySecretPath = async ( + { projectId, secretPath, environment, destination }: TListSecretSyncsByFolderId, + actor: OrgServiceActor + ) => { + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.SecretManager, + projectId + }); + + if (permission.cannot(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs)) { + return []; + } + + const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); + if (!folder) return []; + + const secretSyncs = await secretSyncDAL.find({ + ...(destination && { destination }), + folderId: folder.id + }); + + return secretSyncs as TSecretSync[]; + }; + const findSecretSyncById = async ({ destination, syncId }: TFindSecretSyncByIdDTO, actor: OrgServiceActor) => { const secretSync = await secretSyncDAL.findById(syncId); @@ -518,6 +547,7 @@ export const secretSyncServiceFactory = ({ return { listSecretSyncOptions, listSecretSyncsByProjectId, + listSecretSyncsBySecretPath, findSecretSyncById, findSecretSyncByName, createSecretSync, diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index 03e92a57a..7787fe166 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -134,6 +134,13 @@ export type TListSecretSyncsByProjectId = { destination?: SecretSync; }; +export type TListSecretSyncsByFolderId = { + projectId: string; + secretPath: string; + environment: string; + destination?: SecretSync; +}; + export type TFindSecretSyncByIdDTO = { syncId: string; destination: SecretSync; diff --git a/frontend/src/hooks/api/dashboard/types.ts b/frontend/src/hooks/api/dashboard/types.ts index e80874f8e..4315faecc 100644 --- a/frontend/src/hooks/api/dashboard/types.ts +++ b/frontend/src/hooks/api/dashboard/types.ts @@ -25,6 +25,7 @@ export type DashboardProjectSecretsOverviewResponse = { totalUniqueFoldersInPage: number; totalUniqueSecretImportsInPage: number; importedByEnvs?: { environment: string; importedBy: ProjectSecretsImportedBy[] }[]; + usedBySecretSyncs?: { name: string; destination: string; environment: string }[]; totalUniqueSecretRotationsInPage: number; }; @@ -43,6 +44,7 @@ export type DashboardProjectSecretsDetailsResponse = { totalSecretRotationCount?: number; totalCount: number; importedBy?: ProjectSecretsImportedBy[]; + usedBySecretSyncs?: { name: string; destination: string; environment: string }[]; }; export type ProjectSecretsImportedBy = { diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index f974a7f1a..6d30af10b 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -274,7 +274,8 @@ export const OverviewPage = () => { totalUniqueSecretImportsInPage, totalUniqueDynamicSecretsInPage, totalUniqueSecretRotationsInPage, - importedByEnvs + importedByEnvs, + usedBySecretSyncs } = overview ?? {}; const secretImportsShaped = secretImports @@ -1045,6 +1046,7 @@ export const OverviewPage = () => { selectedEntries={selectedEntries} resetSelectedEntries={resetSelectedEntries} importedByEnvs={importedByEnvs} + usedBySecretSyncs={usedBySecretSyncs} />
>; }; importedByEnvs?: { environment: string; importedBy: ProjectSecretsImportedBy[] }[]; + usedBySecretSyncs?: { name: string; destination: string; environment: string }[]; }; export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntries, - importedByEnvs + importedByEnvs, + usedBySecretSyncs }: Props) => { const { permission } = useProjectPermission(); @@ -156,6 +158,12 @@ export const SelectionPanel = ({ ); }, [importedByEnvs, selectedEntries, selectedKeysCount]); + const usedBySecretSyncsFiltered = useMemo(() => { + if (selectedKeysCount === 0 || !usedBySecretSyncs) return null; + const envs = Object.values(selectedEntries.secret).flatMap((entries) => Object.keys(entries)); + return usedBySecretSyncs.filter((syncItem) => envs.includes(syncItem.environment)); + }, [selectedEntries, usedBySecretSyncs, selectedKeysCount]); + const getDeleteModalTitle = () => { if (selectedFolderCount > 0 && selectedKeysCount > 0) { return "Do you want to delete the selected secrets and folders across environments?"; @@ -331,6 +339,7 @@ export const SelectionPanel = ({ ) } diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index 63398d152..d28f28392 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -220,6 +220,7 @@ const Page = () => { totalSecretCount = 0, totalCount = 0, importedBy, + usedBySecretSyncs, totalSecretRotationCount = 0 } = data ?? {}; @@ -441,6 +442,7 @@ const Page = () => { onClickRollbackMode={() => handlePopUpToggle("snapshots", true)} protectedBranchPolicyName={boardPolicy?.name} importedBy={importedBy} + usedBySecretSyncs={usedBySecretSyncs} />
@@ -530,6 +532,7 @@ const Page = () => { secretPath={secretPath} isProtectedBranch={isProtectedBranch} importedBy={importedBy} + usedBySecretSyncs={usedBySecretSyncs} /> )} {noAccessSecretCount > 0 && } diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index 4ac4ac804..92dca2023 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -113,6 +113,7 @@ type Props = { onVisibilityToggle: () => void; onToggleRowType: (rowType: RowType) => void; onClickRollbackMode: () => void; + usedBySecretSyncs?: { environment: string; name: string; destination: string }[]; importedBy?: { environment: { name: string; slug: string }; folders: { @@ -139,7 +140,8 @@ export const ActionBar = ({ onClickRollbackMode, onToggleRowType, protectedBranchPolicyName, - importedBy + importedBy, + usedBySecretSyncs }: Props) => { const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "addFolder", @@ -1076,6 +1078,7 @@ export const ActionBar = ({ s.key)} + usedBySecretSyncs={usedBySecretSyncs} /> ) } diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports.tsx index 9a66b23f5..7cb035e79 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports.tsx @@ -1,5 +1,6 @@ +/* eslint-disable no-nested-ternary */ import React, { useMemo } from "react"; -import { faFileImport, faKey, faWarning } from "@fortawesome/free-solid-svg-icons"; +import { faFileImport, faKey, faSync, faWarning } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Table, TBody, Td, Th, THead, Tr } from "@app/components/v2"; @@ -7,7 +8,8 @@ import { useWorkspace } from "@app/context"; enum ItemType { Folder = "Folder", - Secret = "Secret" + Secret = "Secret", + SecretSync = "SecretSync" } interface FlatItem { @@ -28,11 +30,19 @@ interface CollapsibleSecretImportsProps { isImported: boolean; }[]; }[]; + usedBySecretSyncs?: + | { + name: string; + destination: string; + environment: string; + }[] + | null; secretsToDelete: string[]; } export const CollapsibleSecretImports: React.FC = ({ importedBy = [], + usedBySecretSyncs = [], secretsToDelete }) => { const { currentWorkspace } = useWorkspace(); @@ -51,6 +61,14 @@ export const CollapsibleSecretImports: React.FC = }; const handlePathClick = (item: FlatItem) => { + if (item.type === ItemType.SecretSync) { + window.open( + `/secret-manager/${currentWorkspace.id}/integrations?selectedTab=secret-syncs`, + "_blank" + ); + return; + } + let pathToNavigate; if (item.type === ItemType.Folder) { pathToNavigate = item.path; @@ -103,7 +121,24 @@ export const CollapsibleSecretImports: React.FC = }); }); + // Add secret sync items + usedBySecretSyncs?.forEach((syncItem) => { + items.push({ + type: ItemType.SecretSync, + path: syncItem.destination, + id: `secret-sync-${syncItem.name}-${syncItem.destination}`, + reference: "Secret Sync", + environment: { name: syncItem.environment, slug: "" } + }); + }); + return items.sort((a, b) => { + if (a.type === ItemType.SecretSync && b.type !== ItemType.SecretSync) return 1; + if (a.type !== ItemType.SecretSync && b.type === ItemType.SecretSync) return -1; + + if (a.type === ItemType.SecretSync && b.type === ItemType.SecretSync) { + return a.path.localeCompare(b.path); + } const envCompare = a.environment.name.localeCompare(b.environment.name); if (envCompare !== 0) return envCompare; @@ -119,7 +154,7 @@ export const CollapsibleSecretImports: React.FC = return aPath.localeCompare(bPath); }); - }, [importedBy]); + }, [importedBy, usedBySecretSyncs, secretsToDelete]); const hasImportedItems = importedBy.some((element) => { if (element.folders && element.folders.length > 0) { @@ -135,7 +170,9 @@ export const CollapsibleSecretImports: React.FC = return false; }); - if (!hasImportedItems) { + const hasSecretSyncItems = usedBySecretSyncs && usedBySecretSyncs.length > 0; + + if (!hasImportedItems && !hasSecretSyncItems) { return null; } @@ -168,12 +205,28 @@ export const CollapsibleSecretImports: React.FC = key={item.id} onClick={() => handlePathClick(item)} className="cursor-pointer hover:bg-mineshaft-700" - title={`Navigate to ${item.path}`} + title={ + item.type === ItemType.SecretSync + ? "Navigate to Secret Syncs" + : `Navigate to ${item.path}` + } >
+ handlePopUpToggle("editSecret", isOpen)} + onDeleteApproved={() => handleEditSecret(popUp?.editSecret?.data)} + formContent={ + importedBy && + importedBy.length > 0 && ( + + ) + } + /> ); } diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx index a08c2aa18..e991e9109 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx @@ -34,7 +34,7 @@ type Props = { environment: { name: string; slug: string }; folders: { name: string; - secrets?: { secretId: string; referencedSecretKey: string }[]; + secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[]; isImported: boolean; }[]; }[]; @@ -354,6 +354,7 @@ export const SecretListView = ({ onSaveSecret={handleSaveSecret} onDeleteSecret={onDeleteSecret} onDetailViewSecret={onDetailViewSecret} + importedBy={importedBy} onCreateTag={onCreateTag} handleSecretShare={() => handlePopUpOpen("createSharedSecret", { From f345801bd6a621652fd9588d1b21fdda1bd7addb Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 25 Apr 2025 18:17:33 -0300 Subject: [PATCH 3/7] feat(secrets-ui): improve types and code quality --- backend/src/server/routes/v1/dashboard-router.ts | 2 +- .../components/SelectionPanel/SelectionPanel.tsx | 4 ++-- .../SecretDashboardPage/components/ActionBar/ActionBar.tsx | 4 ++-- .../components/SecretListView/CollapsibleSecretImports.tsx | 3 ++- .../components/SecretListView/SecretItem.tsx | 6 +++--- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index 8db2f112c..2b4336e28 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -939,7 +939,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { const usedBySecretSyncs = secretSyncs.map((sync) => ({ name: sync.name, destination: sync.destination, - environment: environment + environment })); if (secrets?.length || secretRotations?.length) { diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/SelectionPanel.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/SelectionPanel.tsx index 549d63679..464c19dcb 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/SelectionPanel.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/SelectionPanel.tsx @@ -48,7 +48,7 @@ export const SelectionPanel = ({ selectedEntries, importedBy, secretsToDeleteKeys, - usedBySecretSyncs + usedBySecretSyncs = [] }: Props) => { const { permission } = useProjectPermission(); @@ -86,7 +86,7 @@ export const SelectionPanel = ({ ); const usedBySecretSyncsFiltered = useMemo(() => { - if (selectedKeysCount === 0 || !usedBySecretSyncs) return null; + if (selectedKeysCount === 0 || usedBySecretSyncs.length === 0) return null; const envs = Object.values(selectedEntries.secret).flatMap((entries) => Object.keys(entries)); return usedBySecretSyncs.filter((syncItem) => envs.includes(syncItem.environment)); }, [selectedEntries, usedBySecretSyncs, selectedKeysCount]); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index 93a619b4c..a1f940530 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -1073,8 +1073,8 @@ export const ActionBar = ({ onChange={(isOpen) => handlePopUpToggle("bulkDeleteSecrets", isOpen)} onDeleteApproved={handleSecretBulkDelete} formContent={ - importedBy && - importedBy.length > 0 && ( + ((importedBy && importedBy.length > 0) || + (usedBySecretSyncs && usedBySecretSyncs?.length > 0)) && ( s.key)} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports.tsx index 8e910221a..053d70148 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports.tsx @@ -66,7 +66,8 @@ export const CollapsibleSecretImports: React.FC = if (item.type === ItemType.SecretSync) { window.open( `/secret-manager/${currentWorkspace.id}/integrations?selectedTab=secret-syncs`, - "_blank" + "_blank", + "noopener,noreferrer" ); return; } diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx index e5944fc44..a4c30a3e0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx @@ -208,14 +208,14 @@ export const SecretItem = memo( }; const handleFormSubmit = async (data: TFormSchema) => { - if ( + const hasDirectReferences = importedBy && importedBy.some(({ folders }) => folders?.some(({ secrets }) => secrets?.some(({ referencedSecretId }) => referencedSecretId === secret.id) ) - ) - ) { + ); + if (hasDirectReferences) { handlePopUpOpen("editSecret", data); return; } From e959ed7fabaa355230b96de7705219d5a8a26e78 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Tue, 29 Apr 2025 10:15:53 -0300 Subject: [PATCH 4/7] feat(secrets-ui): improve warning message and logic for secret-sync on secret imports --- backend/src/server/routes/index.ts | 1 + .../src/server/routes/v1/dashboard-router.ts | 6 ++--- .../secret-import/secret-import-dal.ts | 26 ++++++++++++++----- .../secret-import/secret-import-service.ts | 2 +- .../secret-import/secret-import-types.ts | 4 +-- .../secret-sync/secret-sync-service.ts | 12 ++++++--- .../DeleteActionModal/DeleteActionModal.tsx | 4 +-- frontend/src/hooks/api/dashboard/types.ts | 2 +- .../OverviewPage/OverviewPage.tsx | 2 +- .../SecretOverviewTableRow/SecretEditRow.tsx | 7 +++-- .../SecretOverviewTableRow.tsx | 2 +- .../components/ActionBar/ActionBar.tsx | 2 +- .../CollapsibleSecretImports.tsx | 20 +++++++++++--- .../components/SecretListView/SecretItem.tsx | 15 +++++------ .../SecretListView/SecretListView.tsx | 2 +- 15 files changed, 71 insertions(+), 36 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 8ceeba648..a71a69c20 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1541,6 +1541,7 @@ export const registerRoutes = async ( const secretSyncService = secretSyncServiceFactory({ secretSyncDAL, + secretImportDAL, permissionService, appConnectionService, folderDAL, diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index bd5029acf..146a31cf2 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -155,7 +155,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { .object({ secretId: z.string(), referencedSecretKey: z.string(), - referencedSecretId: z.string() + referencedSecretEnv: z.string() }) .array() .optional() @@ -658,7 +658,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { .object({ secretId: z.string(), referencedSecretKey: z.string(), - referencedSecretId: z.string() + referencedSecretEnv: z.string() }) .array() .optional() @@ -945,7 +945,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { const usedBySecretSyncs = secretSyncs.map((sync) => ({ name: sync.name, destination: sync.destination, - environment + environment: sync.environment?.name || environment })); if (secrets?.length || secretRotations?.length) { diff --git a/backend/src/services/secret-import/secret-import-dal.ts b/backend/src/services/secret-import/secret-import-dal.ts index 7ea276268..dbe2f6a84 100644 --- a/backend/src/services/secret-import/secret-import-dal.ts +++ b/backend/src/services/secret-import/secret-import-dal.ts @@ -171,6 +171,19 @@ export const secretImportDALFactory = (db: TDbClient) => { } }; + const getFolderImports = async (secretPath: string, environmentId: string, tx?: Knex) => { + try { + const folderImports = await (tx || db.replicaNode())(TableName.SecretImport) + .where({ importPath: secretPath, importEnv: environmentId }) + .join(TableName.SecretFolder, `${TableName.SecretImport}.folderId`, `${TableName.SecretFolder}.id`) + .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) + .select(db.ref("id").withSchema(TableName.SecretFolder).as("folderId")); + return folderImports; + } catch (error) { + throw new DatabaseError({ error, name: "get secret imports" }); + } + }; + const getFolderIsImportedBy = async ( secretPath: string, environmentId: string, @@ -204,7 +217,7 @@ export const secretImportDALFactory = (db: TDbClient) => { db.ref("slug").withSchema(TableName.Environment).as("envSlug"), db.ref("id").withSchema(TableName.SecretFolder).as("folderId"), db.ref("secretKey").withSchema(TableName.SecretReferenceV2).as("referencedSecretKey"), - db.ref("id").withSchema(TableName.SecretV2).as("referencedSecretId") + db.ref("environment").withSchema(TableName.SecretReferenceV2).as("referencedSecretEnv") ); const folderResults = folderImports.map(({ envName, envSlug, folderName, folderId }) => ({ @@ -215,14 +228,14 @@ export const secretImportDALFactory = (db: TDbClient) => { })); const secretResults = secretReferences.map( - ({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey, referencedSecretId }) => ({ + ({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey, referencedSecretEnv }) => ({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey, - referencedSecretId + referencedSecretEnv }) ); @@ -237,7 +250,7 @@ export const secretImportDALFactory = (db: TDbClient) => { secrets: { secretId: string; referencedSecretKey: string; - referencedSecretId: string; + referencedSecretEnv: string; }[]; folderId: string; folderImported: boolean; @@ -270,7 +283,7 @@ export const secretImportDALFactory = (db: TDbClient) => { { secretId: item.secretId, referencedSecretKey: item.referencedSecretKey, - referencedSecretId: item.referencedSecretId + referencedSecretEnv: item.referencedSecretEnv } ]; } else { @@ -316,6 +329,7 @@ export const secretImportDALFactory = (db: TDbClient) => { findLastImportPosition, updateAllPosition, getProjectImportCount, - getFolderIsImportedBy + getFolderIsImportedBy, + getFolderImports }; }; diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index 998d8682a..5078496d6 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -878,7 +878,7 @@ export const secretImportServiceFactory = ({ .map((otherSecret) => ({ secretId: secret.secretKey, referencedSecretKey: otherSecret.secretKey, - referencedSecretId: otherSecret.id + referencedSecretEnv: environment })); }) || []; if (locallyReferenced.length > 0) { diff --git a/backend/src/services/secret-import/secret-import-types.ts b/backend/src/services/secret-import/secret-import-types.ts index 407372267..41ddbc9e2 100644 --- a/backend/src/services/secret-import/secret-import-types.ts +++ b/backend/src/services/secret-import/secret-import-types.ts @@ -56,12 +56,12 @@ export type FolderResult = { export type SecretResult = { secretId: string; referencedSecretKey: string; - referencedSecretId: string; + referencedSecretEnv: string; } & FolderResult; export type FolderInfo = { folderName: string; - secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[]; + secrets?: { secretId: string; referencedSecretKey: string; referencedSecretEnv: string }[]; folderId: string; folderImported: boolean; envSlug?: string; diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index a19ebe6f7..db350f785 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -32,12 +32,14 @@ import { TUpdateSecretSyncDTO } from "@app/services/secret-sync/secret-sync-types"; +import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretSyncDALFactory } from "./secret-sync-dal"; import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "./secret-sync-maps"; import { TSecretSyncQueueFactory } from "./secret-sync-queue"; type TSecretSyncServiceFactoryDep = { secretSyncDAL: TSecretSyncDALFactory; + secretImportDAL: TSecretImportDALFactory; appConnectionService: Pick; permissionService: Pick; projectBotService: Pick; @@ -54,6 +56,7 @@ export type TSecretSyncServiceFactory = ReturnType { const { permission } = await permissionService.getProjectPermission({ @@ -106,9 +109,12 @@ export const secretSyncServiceFactory = ({ const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); if (!folder) return []; + const folderImports = await secretImportDAL.getFolderImports(secretPath, folder.envId); + const secretSyncs = await secretSyncDAL.find({ - ...(destination && { destination }), - folderId: folder.id + $in: { + folderId: folderImports.map((folderImport) => folderImport.folderId).concat(folder.id) + } }); return secretSyncs as TSecretSync[]; diff --git a/frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx b/frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx index 0b0dab278..2fdb56c8c 100644 --- a/frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx +++ b/frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx @@ -33,7 +33,7 @@ export const DeleteActionModal = ({ buttonText = "Delete", formContent, deletionMessage, - buttonColorSchema, + buttonColorSchema = "danger", children }: Props): JSX.Element => { const [inputData, setInputData] = useState(""); @@ -69,7 +69,7 @@ export const DeleteActionModal = ({