From e461787c78b20129d3c0e9744f6216969b1d0b50 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 8 May 2024 23:24:33 +0800 Subject: [PATCH 01/10] feat: added support for renaming folders in the overview page --- .../SecretOverviewPage/SecretOverviewPage.tsx | 81 ++++++++++++++++++- .../SecretOverviewFolderRow.tsx | 18 ++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index fab7ebecf..e7517b37e 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -47,6 +47,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useOrganization, + useProjectPermission, useWorkspace } from "@app/context"; import { usePopUp } from "@app/hooks"; @@ -59,8 +60,10 @@ import { useGetImportedSecretsAllEnvs, useGetProjectSecretsAllEnv, useGetUserWsKey, + useUpdateFolder, useUpdateSecretV3 } from "@app/hooks/api"; +import { TSecretFolder } from "@app/hooks/api/types"; import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { FolderForm } from "../SecretMainPage/components/ActionBar/FolderForm"; @@ -87,6 +90,7 @@ export const SecretOverviewPage = () => { const parentTableRef = useRef(null); const [expandableTableWidth, setExpandableTableWidth] = useState(0); const [sortDir, setSortDir] = useState<"asc" | "desc">("asc"); + const { permission } = useProjectPermission(); useEffect(() => { if (parentTableRef.current) { @@ -201,11 +205,13 @@ export const SecretOverviewPage = () => { const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3(); const { mutateAsync: createFolder } = useCreateFolder(); + const { mutateAsync: updateFolder } = useUpdateFolder(); const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "addSecretsInAllEnvs", "addFolder", - "misc" + "misc", + "updateFolder" ] as const); const handleFolderCreate = async (folderName: string) => { @@ -236,6 +242,64 @@ export const SecretOverviewPage = () => { } }; + const handleFolderUpdate = async (newFolderName: string) => { + const { name: oldFolderName } = popUp.updateFolder.data as TSecretFolder; + let processedUpdates = 0; + let totalFolderMatchCount = 0; + + await Promise.allSettled( + userAvailableEnvs.map(async (env) => { + if ( + permission.cannot( + ProjectPermissionActions.Edit, + subject(ProjectPermissionSub.Secrets, { environment: env.slug, secretPath }) + ) + ) { + return; + } + + const folder = getFolderByNameAndEnv(oldFolderName, env.slug); + if (folder) { + totalFolderMatchCount += 1; + + await updateFolder({ + folderId: folder.id, + name: newFolderName, + path: secretPath, + environment: env.slug, + projectId: workspaceId + }); + + processedUpdates += 1; + } + }) + ); + + if (totalFolderMatchCount === 0) { + createNotification({ + type: "info", + text: "You don't have access to rename selected folder" + }); + } else if (processedUpdates === totalFolderMatchCount) { + createNotification({ + type: "success", + text: "Successfully renamed folder across environments" + }); + } else if (processedUpdates > 0) { + createNotification({ + type: "warning", + text: "Partially renamed folder across environments" + }); + } else { + createNotification({ + type: "error", + text: "Failed to rename folder across environments" + }); + } + + handlePopUpClose("updateFolder"); + }; + const handleSecretCreate = async (env: string, key: string, value: string) => { try { // create folder if not existing @@ -726,6 +790,9 @@ export const SecretOverviewPage = () => { environments={visibleEnvs} key={`overview-${folderName}-${index + 1}`} onClick={handleFolderClick} + onToggleFolderEdit={(name: string) => + handlePopUpOpen("updateFolder", { name }) + } /> ))} {!isTableLoading && @@ -800,6 +867,18 @@ export const SecretOverviewPage = () => { + handlePopUpToggle("updateFolder", isOpen)} + > + + )?.name} + onUpdateFolder={handleFolderUpdate} + /> + + ); }; diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx index c60aff912..2625d14c6 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx @@ -1,8 +1,8 @@ -import { faCheck, faFolder, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faFolder, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; -import { Checkbox, Td, Tr } from "@app/components/v2"; +import { Checkbox, IconButton, Td, Tr } from "@app/components/v2"; type Props = { folderName: string; @@ -11,6 +11,7 @@ type Props = { onClick: (path: string) => void; isSelected: boolean; onToggleFolderSelect: (folderName: string) => void; + onToggleFolderEdit: (name: string) => void; }; export const SecretOverviewFolderRow = ({ @@ -19,6 +20,7 @@ export const SecretOverviewFolderRow = ({ isFolderPresentInEnv, isSelected, onToggleFolderSelect, + onToggleFolderEdit, onClick }: Props) => { return ( @@ -43,6 +45,18 @@ export const SecretOverviewFolderRow = ({ />
{folderName}
+ { + onToggleFolderEdit(folderName); + e.stopPropagation(); + }} + > + + {environments.map(({ slug }, i) => { From 522dd0836e455c36981d6f1bd77fd639eaec1632 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 8 May 2024 23:25:33 +0800 Subject: [PATCH 02/10] feat: added validation for folder name duplicates --- .../secret-folder/secret-folder-service.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index c925d2587..9f602172f 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -151,6 +151,21 @@ export const secretFolderServiceFactory = ({ .catch(() => folderDAL.findOne({ envId: env.id, name: id, parentId: parentFolder.id })); if (!folder) throw new BadRequestError({ message: "Folder not found" }); + if (name !== folder.name) { + // ensure that new folder name is unique + const folderToCheck = await folderDAL.findOne({ + name, + envId: env.id, + parentId: parentFolder.id + }); + + if (folderToCheck) { + throw new BadRequestError({ + message: "Folder with specified name already exists", + name: "Update folder" + }); + } + } const newFolder = await folderDAL.transaction(async (tx) => { const [doc] = await folderDAL.update({ envId: env.id, id: folder.id, parentId: parentFolder.id }, { name }, tx); From a6f750fafbb5e4c7d8dc1511ff02b2249f5e7d3c Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 10 May 2024 13:57:00 +0800 Subject: [PATCH 03/10] feat: added batch update endpoint for folders --- .../server/routes/v1/secret-folder-router.ts | 65 +++++++++++++ .../secret-folder/secret-folder-service.ts | 96 ++++++++++++++++++- .../secret-folder/secret-folder-types.ts | 9 ++ 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 3b8d0988f..1ca97c030 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -127,6 +127,71 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => } }); + server.route({ + url: "/batch", + method: "PATCH", + config: { + rateLimit: secretsLimit + }, + schema: { + description: "Update folders by batch", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId), + folders: z + .object({ + id: z.string().describe(FOLDERS.UPDATE.folderId), + environment: z.string().trim().describe(FOLDERS.UPDATE.environment), + name: z.string().trim().describe(FOLDERS.UPDATE.name), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.path) + }) + .array() + .min(1) + }), + response: { + 200: z.object({ + folders: SecretFoldersSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { newFolders, oldFolders } = await server.services.folder.updateManyFolders({ + folders: req.body.folders, + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.body.workspaceId + }); + + await Promise.all( + req.body.folders.map(async (folder, ind) => { + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.UPDATE_FOLDER, + metadata: { + environment: oldFolders[ind].envId, + folderId: oldFolders[ind].id, + folderPath: folder.path, + newFolderName: newFolders[ind].name, + oldFolderName: oldFolders[ind].name + } + } + }); + }) + ); + + return { folders: newFolders }; + } + }); + // TODO(daniel): Expose this route in api reference and write docs for it. server.route({ method: "DELETE", diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 9f602172f..7693ab92c 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -10,7 +10,13 @@ import { BadRequestError } from "@app/lib/errors"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TSecretFolderDALFactory } from "./secret-folder-dal"; -import { TCreateFolderDTO, TDeleteFolderDTO, TGetFolderDTO, TUpdateFolderDTO } from "./secret-folder-types"; +import { + TCreateFolderDTO, + TDeleteFolderDTO, + TGetFolderDTO, + TUpdateFolderDTO, + TUpdateManyFoldersDTO +} from "./secret-folder-types"; import { TSecretFolderVersionDALFactory } from "./secret-folder-version-dal"; type TSecretFolderServiceFactoryDep = { @@ -116,6 +122,93 @@ export const secretFolderServiceFactory = ({ return folder; }; + const updateManyFolders = async ({ + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId, + folders + }: TUpdateManyFoldersDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); + + folders.forEach(({ environment, path: secretPath }) => { + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Edit, + subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + ); + }); + + const result = await folderDAL.transaction(async (tx) => + Promise.all( + folders.map(async (newFolder) => { + const { environment, path: secretPath, id, name } = newFolder; + + const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath); + if (!parentFolder) throw new BadRequestError({ message: "Secret path not found" }); + + const env = await projectEnvDAL.findOne({ projectId, slug: environment }); + if (!env) throw new BadRequestError({ message: "Environment not found", name: "Update folder" }); + const folder = await folderDAL + .findOne({ envId: env.id, id, parentId: parentFolder.id }) + // now folder api accepts id based change + // this is for cli backward compatiability and when cli removes this, we will remove this logic + .catch(() => folderDAL.findOne({ envId: env.id, name: id, parentId: parentFolder.id })); + + if (!folder) { + throw new BadRequestError({ message: "Folder not found" }); + } + if (name !== folder.name) { + // ensure that new folder name is unique + const folderToCheck = await folderDAL.findOne({ + name, + envId: env.id, + parentId: parentFolder.id + }); + + if (folderToCheck) { + throw new BadRequestError({ + message: "Folder with specified name already exists", + name: "Batch update folder" + }); + } + } + + const [doc] = await folderDAL.update( + { envId: env.id, id: folder.id, parentId: parentFolder.id }, + { name }, + tx + ); + await folderVersionDAL.create( + { + name: doc.name, + envId: doc.envId, + version: doc.version, + folderId: doc.id + }, + tx + ); + if (!doc) { + throw new BadRequestError({ message: "Folder not found", name: "Batch update folder" }); + } + + return { oldFolder: folder, newFolder: doc }; + }) + ) + ); + + return { + newFolders: result.map((res) => res.newFolder), + oldFolders: result.map((res) => res.oldFolder) + }; + }; + const updateFolder = async ({ projectId, actor, @@ -254,6 +347,7 @@ export const secretFolderServiceFactory = ({ return { createFolder, updateFolder, + updateManyFolders, deleteFolder, getFolders }; diff --git a/backend/src/services/secret-folder/secret-folder-types.ts b/backend/src/services/secret-folder/secret-folder-types.ts index 88b7b1017..18c677b49 100644 --- a/backend/src/services/secret-folder/secret-folder-types.ts +++ b/backend/src/services/secret-folder/secret-folder-types.ts @@ -13,6 +13,15 @@ export type TUpdateFolderDTO = { name: string; } & TProjectPermission; +export type TUpdateManyFoldersDTO = { + folders: { + environment: string; + path: string; + id: string; + name: string; + }[]; +} & TProjectPermission; + export type TDeleteFolderDTO = { environment: string; path: string; From f32abbdc250f95fe411b4399c0123147dc98086c Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 10 May 2024 14:00:49 +0800 Subject: [PATCH 04/10] feat: integrate overview folder rename with new batch endpoint --- .../src/hooks/api/secretFolders/queries.tsx | 41 ++++++++++++ frontend/src/hooks/api/secretFolders/types.ts | 10 +++ .../SecretOverviewPage/SecretOverviewPage.tsx | 67 +++++++++---------- 3 files changed, 82 insertions(+), 36 deletions(-) diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index 71c63f3eb..9bf266ccf 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -16,6 +16,7 @@ import { TGetFoldersByEnvDTO, TGetProjectFoldersDTO, TSecretFolder, + TUpdateFolderBatchDTO, TUpdateFolderDTO } from "./types"; @@ -190,3 +191,43 @@ export const useDeleteFolder = () => { } }); }; + +export const useUpdateFolderBatch = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, TUpdateFolderBatchDTO>({ + mutationFn: async ({ projectId, folders }) => { + const { data } = await apiRequest.patch("/api/v1/folders/batch", { + folders, + workspaceId: projectId + }); + + return data; + }, + onSuccess: (_, { projectId, folders }) => { + folders.forEach((folder) => { + queryClient.invalidateQueries( + folderQueryKeys.getSecretFolders({ + projectId, + environment: folder.environment, + path: folder.path + }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.list({ + workspaceId: projectId, + environment: folder.environment, + directory: folder.path + }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.count({ + workspaceId: projectId, + environment: folder.environment, + directory: folder.path + }) + ); + }); + } + }); +}; diff --git a/frontend/src/hooks/api/secretFolders/types.ts b/frontend/src/hooks/api/secretFolders/types.ts index eac202389..0ae2cfef2 100644 --- a/frontend/src/hooks/api/secretFolders/types.ts +++ b/frontend/src/hooks/api/secretFolders/types.ts @@ -36,3 +36,13 @@ export type TDeleteFolderDTO = { folderId: string; path?: string; }; + +export type TUpdateFolderBatchDTO = { + projectId: string; + folders: { + name: string; + environment: string; + id: string; + path?: string; + }[]; +}; diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index e7517b37e..af1ef5d7d 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -60,9 +60,10 @@ import { useGetImportedSecretsAllEnvs, useGetProjectSecretsAllEnv, useGetUserWsKey, - useUpdateFolder, useUpdateSecretV3 } from "@app/hooks/api"; +import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; +import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; import { TSecretFolder } from "@app/hooks/api/types"; import { ProjectVersion } from "@app/hooks/api/workspace/types"; @@ -205,7 +206,7 @@ export const SecretOverviewPage = () => { const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3(); const { mutateAsync: createFolder } = useCreateFolder(); - const { mutateAsync: updateFolder } = useUpdateFolder(); + const { mutateAsync: updateFolderBatch } = useUpdateFolderBatch(); const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "addSecretsInAllEnvs", @@ -244,60 +245,54 @@ export const SecretOverviewPage = () => { const handleFolderUpdate = async (newFolderName: string) => { const { name: oldFolderName } = popUp.updateFolder.data as TSecretFolder; - let processedUpdates = 0; - let totalFolderMatchCount = 0; - - await Promise.allSettled( - userAvailableEnvs.map(async (env) => { - if ( - permission.cannot( - ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment: env.slug, secretPath }) - ) - ) { - return; - } + const updatedFolders: TUpdateFolderBatchDTO["folders"] = []; + userAvailableEnvs.forEach((env) => { + if ( + permission.can( + ProjectPermissionActions.Edit, + subject(ProjectPermissionSub.Secrets, { environment: env.slug, secretPath }) + ) + ) { const folder = getFolderByNameAndEnv(oldFolderName, env.slug); if (folder) { - totalFolderMatchCount += 1; - - await updateFolder({ - folderId: folder.id, - name: newFolderName, - path: secretPath, + updatedFolders.push({ environment: env.slug, - projectId: workspaceId + name: newFolderName, + id: folder.id, + path: secretPath }); - - processedUpdates += 1; } - }) - ); + } + }); - if (totalFolderMatchCount === 0) { + if (updatedFolders.length === 0) { createNotification({ type: "info", text: "You don't have access to rename selected folder" }); - } else if (processedUpdates === totalFolderMatchCount) { + + handlePopUpClose("updateFolder"); + return; + } + + try { + await updateFolderBatch({ + projectId: workspaceId, + folders: updatedFolders + }); createNotification({ type: "success", text: "Successfully renamed folder across environments" }); - } else if (processedUpdates > 0) { - createNotification({ - type: "warning", - text: "Partially renamed folder across environments" - }); - } else { + } catch (err) { createNotification({ type: "error", text: "Failed to rename folder across environments" }); + } finally { + handlePopUpClose("updateFolder"); } - - handlePopUpClose("updateFolder"); }; const handleSecretCreate = async (env: string, key: string, value: string) => { From 2559809eac46e8419d4190a58886d5c16323fb4a Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 10 May 2024 14:41:35 +0800 Subject: [PATCH 05/10] misc: addressed formatting issues --- .../src/services/secret-folder/secret-folder-service.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 7693ab92c..6e1d9f745 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -151,10 +151,14 @@ export const secretFolderServiceFactory = ({ const { environment, path: secretPath, id, name } = newFolder; const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath); - if (!parentFolder) throw new BadRequestError({ message: "Secret path not found" }); + if (!parentFolder) { + throw new BadRequestError({ message: "Secret path not found", name: "Batch update folder" }); + } const env = await projectEnvDAL.findOne({ projectId, slug: environment }); - if (!env) throw new BadRequestError({ message: "Environment not found", name: "Update folder" }); + if (!env) { + throw new BadRequestError({ message: "Environment not found", name: "Batch update folder" }); + } const folder = await folderDAL .findOne({ envId: env.id, id, parentId: parentFolder.id }) // now folder api accepts id based change From 3fff272cb3406d038ca9b89490b6e5998a297c88 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 10 May 2024 15:46:31 +0800 Subject: [PATCH 06/10] feat: added snapshot for batch --- backend/src/services/secret-folder/secret-folder-service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 6e1d9f745..66e96e013 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -207,6 +207,8 @@ export const secretFolderServiceFactory = ({ ) ); + await Promise.all(result.map(async (res) => snapshotService.performSnapshot(res.newFolder.parentId as string))); + return { newFolders: result.map((res) => res.newFolder), oldFolders: result.map((res) => res.oldFolder) From 0866a90c8e074401ee19c58ad18aa89cb012ee3d Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 10 May 2024 16:29:12 +0800 Subject: [PATCH 07/10] misc: updated documentation for github integration --- docs/integrations/cicd/githubactions.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/integrations/cicd/githubactions.mdx b/docs/integrations/cicd/githubactions.mdx index 10caabc2f..936c8974a 100644 --- a/docs/integrations/cicd/githubactions.mdx +++ b/docs/integrations/cicd/githubactions.mdx @@ -3,9 +3,15 @@ title: "GitHub Actions" description: "How to sync secrets from Infisical to GitHub Actions" --- + + Alternatively, you can use Infisical's official Github Action + [here](https://github.com/Infisical/secrets-action). + + Infisical lets you sync secrets to GitHub at the organization-level, repository-level, and repository environment-level. Prerequisites: + - Set up and add envars to [Infisical Cloud](https://app.infisical.com) - Ensure that you have admin privileges to the repository you want to sync secrets to. From b93aaffe7708393c902b87753e929469d3405b6d Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 10 May 2024 22:34:16 +0800 Subject: [PATCH 08/10] adjustment: updated to use project slug --- backend/src/lib/api-docs/constants.ts | 1 + backend/src/server/routes/index.ts | 4 +++- .../src/server/routes/v1/secret-folder-router.ts | 11 +++++------ .../secret-folder/secret-folder-service.ts | 15 +++++++++++++-- .../services/secret-folder/secret-folder-types.ts | 3 ++- frontend/src/hooks/api/secretFolders/queries.tsx | 6 +++--- frontend/src/hooks/api/secretFolders/types.ts | 1 + .../SecretOverviewPage/SecretOverviewPage.tsx | 5 +++-- 8 files changed, 31 insertions(+), 15 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 336385472..6f9cbac34 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -252,6 +252,7 @@ export const FOLDERS = { name: "The new name of the folder.", path: "The path of the folder to update.", directory: "The new directory of the folder to update. (Deprecated in favor of path)", + projectSlug: "The slug of the project where the folder is located.", workspaceId: "The ID of the project where the folder is located." }, DELETE: { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 62dad3cf4..94ba5db55 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -538,8 +538,10 @@ export const registerRoutes = async ( folderDAL, folderVersionDAL, projectEnvDAL, - snapshotService + snapshotService, + projectDAL }); + const integrationAuthService = integrationAuthServiceFactory({ integrationAuthDAL, integrationDAL, diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 1ca97c030..95cd47c7b 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -141,7 +141,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => } ], body: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId), + projectSlug: z.string().trim().describe(FOLDERS.UPDATE.projectSlug), folders: z .object({ id: z.string().describe(FOLDERS.UPDATE.folderId), @@ -160,20 +160,19 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { newFolders, oldFolders } = await server.services.folder.updateManyFolders({ - folders: req.body.folders, + const { newFolders, oldFolders, projectId } = await server.services.folder.updateManyFolders({ + ...req.body, actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.body.workspaceId + actorOrgId: req.permission.orgId }); await Promise.all( req.body.folders.map(async (folder, ind) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId, event: { type: EventType.UPDATE_FOLDER, metadata: { diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 66e96e013..5caeea720 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -8,6 +8,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { BadRequestError } from "@app/lib/errors"; +import { TProjectDALFactory } from "../project/project-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TSecretFolderDALFactory } from "./secret-folder-dal"; import { @@ -25,6 +26,7 @@ type TSecretFolderServiceFactoryDep = { folderDAL: TSecretFolderDALFactory; projectEnvDAL: Pick; folderVersionDAL: TSecretFolderVersionDALFactory; + projectDAL: Pick; }; export type TSecretFolderServiceFactory = ReturnType; @@ -34,7 +36,8 @@ export const secretFolderServiceFactory = ({ snapshotService, permissionService, projectEnvDAL, - folderVersionDAL + folderVersionDAL, + projectDAL }: TSecretFolderServiceFactoryDep) => { const createFolder = async ({ projectId, @@ -125,11 +128,18 @@ export const secretFolderServiceFactory = ({ const updateManyFolders = async ({ actor, actorId, - projectId, + projectSlug, actorAuthMethod, actorOrgId, folders }: TUpdateManyFoldersDTO) => { + const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); + if (!project) { + throw new BadRequestError({ message: "Project not found" }); + } + + const projectId = project.id; + const { permission } = await permissionService.getProjectPermission( actor, actorId, @@ -210,6 +220,7 @@ export const secretFolderServiceFactory = ({ await Promise.all(result.map(async (res) => snapshotService.performSnapshot(res.newFolder.parentId as string))); return { + projectId, newFolders: result.map((res) => res.newFolder), oldFolders: result.map((res) => res.oldFolder) }; diff --git a/backend/src/services/secret-folder/secret-folder-types.ts b/backend/src/services/secret-folder/secret-folder-types.ts index 18c677b49..1405f8bd7 100644 --- a/backend/src/services/secret-folder/secret-folder-types.ts +++ b/backend/src/services/secret-folder/secret-folder-types.ts @@ -14,13 +14,14 @@ export type TUpdateFolderDTO = { } & TProjectPermission; export type TUpdateManyFoldersDTO = { + projectSlug: string; folders: { environment: string; path: string; id: string; name: string; }[]; -} & TProjectPermission; +} & Omit; export type TDeleteFolderDTO = { environment: string; diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index 9bf266ccf..5bd3444d0 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -196,10 +196,10 @@ export const useUpdateFolderBatch = () => { const queryClient = useQueryClient(); return useMutation<{}, {}, TUpdateFolderBatchDTO>({ - mutationFn: async ({ projectId, folders }) => { + mutationFn: async ({ projectSlug, folders }) => { const { data } = await apiRequest.patch("/api/v1/folders/batch", { - folders, - workspaceId: projectId + projectSlug, + folders }); return data; diff --git a/frontend/src/hooks/api/secretFolders/types.ts b/frontend/src/hooks/api/secretFolders/types.ts index 0ae2cfef2..8fde9c63d 100644 --- a/frontend/src/hooks/api/secretFolders/types.ts +++ b/frontend/src/hooks/api/secretFolders/types.ts @@ -39,6 +39,7 @@ export type TDeleteFolderDTO = { export type TUpdateFolderBatchDTO = { projectId: string; + projectSlug: string; folders: { name: string; environment: string; diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index af1ef5d7d..afe806c5e 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -278,8 +278,9 @@ export const SecretOverviewPage = () => { try { await updateFolderBatch({ - projectId: workspaceId, - folders: updatedFolders + projectSlug, + folders: updatedFolders, + projectId: workspaceId }); createNotification({ type: "success", From 9880977098ab003e7e2720064b33f47102a9c954 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 10 May 2024 22:52:08 +0800 Subject: [PATCH 09/10] misc: addressed naming suggestion --- backend/src/server/routes/v1/secret-folder-router.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 95cd47c7b..1a1747f64 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -169,18 +169,18 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }); await Promise.all( - req.body.folders.map(async (folder, ind) => { + req.body.folders.map(async (folder, index) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, projectId, event: { type: EventType.UPDATE_FOLDER, metadata: { - environment: oldFolders[ind].envId, - folderId: oldFolders[ind].id, + environment: oldFolders[index].envId, + folderId: oldFolders[index].id, folderPath: folder.path, - newFolderName: newFolders[ind].name, - oldFolderName: oldFolders[ind].name + newFolderName: newFolders[index].name, + oldFolderName: oldFolders[index].name } } }); From 86aaa486b474ca66aa2ce661c061e86d0202e9ff Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Fri, 10 May 2024 17:00:30 +0200 Subject: [PATCH 10/10] Update secret-folder-service.ts --- .../services/secret-folder/secret-folder-service.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 5caeea720..da429d88a 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -138,12 +138,10 @@ export const secretFolderServiceFactory = ({ throw new BadRequestError({ message: "Project not found" }); } - const projectId = project.id; - const { permission } = await permissionService.getProjectPermission( actor, actorId, - projectId, + project.id, actorAuthMethod, actorOrgId ); @@ -160,12 +158,12 @@ export const secretFolderServiceFactory = ({ folders.map(async (newFolder) => { const { environment, path: secretPath, id, name } = newFolder; - const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath); + const parentFolder = await folderDAL.findBySecretPath(project.id, environment, secretPath); if (!parentFolder) { throw new BadRequestError({ message: "Secret path not found", name: "Batch update folder" }); } - const env = await projectEnvDAL.findOne({ projectId, slug: environment }); + const env = await projectEnvDAL.findOne({ projectId: project.id, slug: environment }); if (!env) { throw new BadRequestError({ message: "Environment not found", name: "Batch update folder" }); } @@ -220,7 +218,7 @@ export const secretFolderServiceFactory = ({ await Promise.all(result.map(async (res) => snapshotService.performSnapshot(res.newFolder.parentId as string))); return { - projectId, + projectId: project.id, newFolders: result.map((res) => res.newFolder), oldFolders: result.map((res) => res.oldFolder) };