From d12c4b7580d4f7832b8be0adf704b129f5e98d08 Mon Sep 17 00:00:00 2001 From: Rhythm Bhiwani Date: Sat, 2 Mar 2024 09:21:46 +0530 Subject: [PATCH 01/24] Stop ability to rename a secret to empty name from frontend --- .../SecretMainPage/components/SecretListView/SecretListView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx index 780e49448..db57cb144 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx @@ -206,7 +206,7 @@ export const SecretListView = ({ reminderRepeatDays, reminderNote } = modSecret; - const hasKeyChanged = oldKey !== key; + const hasKeyChanged = oldKey !== key && key; const tagIds = tags?.map(({ id }) => id); const oldTagIds = (orgSecret?.tags || []).map(({ id }) => id); From 22d5f97793cb0bd319bfbab82b67298e1d7bf6c6 Mon Sep 17 00:00:00 2001 From: Rhythm Bhiwani Date: Sat, 2 Mar 2024 11:14:56 +0530 Subject: [PATCH 02/24] Added Feature to Create Secrets and Folders in all envs from overview page --- .../ActionBar/CreateSecretImportForm.tsx | 2 +- .../SecretOverviewPage/SecretOverviewPage.tsx | 523 +++++++++++------- .../CreateSecretInAllEnvsForm.tsx | 156 ++++++ 3 files changed, 482 insertions(+), 199 deletions(-) create mode 100644 frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx index 8a97a799f..c178e7514 100644 --- a/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx +++ b/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx @@ -81,7 +81,7 @@ export const CreateSecretImportForm = ({ }); } catch (err) { console.error(err); - const axiosError = err as AxiosError + const axiosError = err as AxiosError; if (axiosError?.response?.status === 401) { createNotification({ text: "You do not have access to the selected environment/path", diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index b3e0efb8a..ed4f490f8 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -2,22 +2,31 @@ import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; +import { subject } from "@casl/ability"; import { + faAngleDown, faArrowDown, faArrowUp, faFolderBlank, - faMagnifyingGlass + faFolderPlus, + faMagnifyingGlass, + faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import NavHeader from "@app/components/navigation/NavHeader"; -import { PermissionDeniedBanner } from "@app/components/permissions"; +import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions"; import { Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, EmptyState, IconButton, Input, + Modal, + ModalContent, Table, TableContainer, TableSkeleton, @@ -30,7 +39,13 @@ import { Tr } from "@app/components/v2"; import { UpgradeProjectAlert } from "@app/components/v2/UpgradeProjectAlert"; -import { useOrganization, useWorkspace } from "@app/context"; +import { + ProjectPermissionActions, + ProjectPermissionSub, + useOrganization, + useWorkspace +} from "@app/context"; +import { usePopUp } from "@app/hooks"; import { useCreateFolder, useCreateSecretV3, @@ -42,6 +57,8 @@ import { } from "@app/hooks/api"; import { ProjectVersion } from "@app/hooks/api/workspace/types"; +import { FolderForm } from "../SecretMainPage/components/ActionBar/FolderForm"; +import { CreateSecretInAllEnvsForm } from "./components/CreateSecretsSection/CreateSecretInAllEnvsForm"; import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs"; import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection"; import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow"; @@ -110,6 +127,40 @@ export const SecretOverviewPage = () => { const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3(); const { mutateAsync: createFolder } = useCreateFolder(); + const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ + "addSecretsInAllEnvs", + "addFolder", + "misc" + ] as const); + + const handleFolderCreate = async (folderName: string) => { + const promises = userAvailableEnvs.map((env) => { + const environment = env.slug; + return createFolder({ + name: folderName, + path: secretPath, + environment, + projectId: workspaceId + }); + }); + + const results = await Promise.allSettled(promises); + const isFoldersAdded = results.some((result) => result.status === "fulfilled"); + + if (isFoldersAdded) { + handlePopUpClose("addFolder"); + createNotification({ + type: "success", + text: "Successfully created folder" + }); + } else { + createNotification({ + type: "error", + text: "Failed to create folder" + }); + } + }; + const handleSecretCreate = async (env: string, key: string, value: string) => { try { // create folder if not existing @@ -269,210 +320,286 @@ export const SecretOverviewPage = () => { filteredFolderNames?.length === 0; return ( -
- -
- -
-
-
-

Secrets Overview

-

- Inject your secrets using - - Infisical CLI - - , - - Infisical API - - , - - Infisical SDKs - - , and - - more - - . -

+ <> +
+ +
+
+
+
+

Secrets Overview

+

+ Inject your secrets using + + Infisical CLI + + , + + Infisical API + + , + + Infisical SDKs + + , and + + more + + . +

+
- {currentWorkspace?.version === ProjectVersion.V1 && ( - - )} + {currentWorkspace?.version === ProjectVersion.V1 && ( + + )} -
- -
- setSearchFilter(e.target.value)} - leftIcon={} - /> +
+ +
+
+ setSearchFilter(e.target.value)} + leftIcon={} + /> +
+
+ + {(isAllowed) => ( + + )} + + handlePopUpToggle("misc", isOpen)} + > + + + + + + +
+ + {(isAllowed) => ( + + )} + +
+
+
+
+
-
-
- - - - - - {userAvailableEnvs?.map(({ name, slug }, index) => { - const envSecKeyCount = getEnvSecretKeyCount(slug); - const missingKeyCount = secKeys.length - envSecKeyCount; - return ( -
-
- Name - setSortDir((prev) => (prev === "asc" ? "desc" : "asc"))} - > - - -
-
-
-
+
+
-
+ handlePopUpToggle("addSecretsInAllEnvs", isOpen)} + onClose={() => handlePopUpClose("addSecretsInAllEnvs")} + decryptFileKey={latestFileKey!} + /> + handlePopUpToggle("addFolder", isOpen)} + > + + + + + ); }; diff --git a/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx b/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx new file mode 100644 index 000000000..bc716edff --- /dev/null +++ b/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx @@ -0,0 +1,156 @@ +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { Button, FormControl, Input, Modal, ModalContent, SecretInput } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useCreateSecretV3 } from "@app/hooks/api"; +import { UserWsKeyPair } from "@app/hooks/api/types"; + +const typeSchema = z + .object({ + key: z.string(), + values: z.record(z.string().optional()) + }) + .refine((data) => data.key !== undefined, { + message: "Please enter secret name" + }); + +type TFormSchema = z.infer; + +type Props = { + workspaceId: string; + secretPath?: string; + decryptFileKey: UserWsKeyPair; + + // modal props + isOpen?: boolean; + onClose: () => void; + onTogglePopUp: (isOpen: boolean) => void; +}; + +export const CreateSecretInAllEnvsForm = ({ + workspaceId, + secretPath = "/", + decryptFileKey, + isOpen, + onClose, + onTogglePopUp +}: Props) => { + const { + register, + handleSubmit, + control, + reset, + setError, + formState: { isSubmitting, errors } + } = useForm({ resolver: zodResolver(typeSchema) }); + const { currentWorkspace } = useWorkspace(); + const environments = currentWorkspace?.environments || []; + + const { createNotification } = useNotificationContext(); + + const { mutateAsync: createSecretV3 } = useCreateSecretV3(); + + const handleFormSubmit = async ({ key, values }: TFormSchema) => { + if (!key) { + setError("key", { message: "Please enter secret name" }); + return; + } + + const promises = environments.map((env) => { + const environment = env.slug; + const value = values[environment] || ""; + + return createSecretV3({ + environment, + workspaceId, + secretPath, + secretName: key, + secretValue: value, + secretComment: "", + type: "shared", + latestFileKey: decryptFileKey + }); + }); + + const results = await Promise.allSettled(promises); + const isSecretsAdded = results.some((result) => result.status === "fulfilled"); + + if (isSecretsAdded) { + createNotification({ + type: "success", + text: "Secrets created successfully" + }); + onClose(); + reset(); + } else { + createNotification({ + type: "error", + text: "Failed to create secrets" + }); + } + }; + return ( + + +
+ + + + + {environments.map((env) => { + return ( + ( + + + + )} + /> + ); + })} + +
+ + +
+ +
+
+ ); +}; From 4afb20ad0d896475a591ec76237b18943a78f5c0 Mon Sep 17 00:00:00 2001 From: Rhythm Bhiwani Date: Sat, 2 Mar 2024 19:44:20 +0530 Subject: [PATCH 03/24] Added proper error message when secret name is empty while renaming --- .../components/SecretListView/SecretItem.tsx | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx index a72463c9a..33661dee7 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx @@ -1,24 +1,4 @@ /* eslint-disable simple-import-sort/imports */ -import { memo, useEffect } from "react"; -import { Controller, useFieldArray, useForm } from "react-hook-form"; -import { subject } from "@casl/ability"; -import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; -import { - faCheck, - faClock, - faClose, - faCodeBranch, - faComment, - faCopy, - faEllipsis, - faKey, - faTag, - faTags -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { AnimatePresence, motion } from "framer-motion"; -import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, @@ -48,9 +28,30 @@ import { import { useToggle } from "@app/hooks"; import { DecryptedSecret } from "@app/hooks/api/secrets/types"; import { WsTag } from "@app/hooks/api/types"; +import { subject } from "@casl/ability"; +import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; +import { + faCheck, + faClock, + faClose, + faCodeBranch, + faComment, + faCopy, + faEllipsis, + faKey, + faTag, + faTags +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { AnimatePresence, motion } from "framer-motion"; +import { memo, useEffect } from "react"; +import { Controller, useFieldArray, useForm } from "react-hook-form"; +import { twMerge } from "tailwind-merge"; -import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils"; +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { CreateReminderForm } from "./CreateReminderForm"; +import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils"; type Props = { secret: DecryptedSecret; @@ -86,6 +87,7 @@ export const SecretItem = memo( }: Props) => { const { currentWorkspace } = useWorkspace(); const { permission } = useProjectPermission(); + const { createNotification } = useNotificationContext(); const isReadOnly = permission.can( ProjectPermissionActions.Read, @@ -235,15 +237,25 @@ export const SecretItem = memo( ( + render={({ field, fieldState: { error } }) => ( { + e.preventDefault(); + createNotification({ + text: "Secret name cannot be empty", + type: "error" + }); + }} {...field} - className="w-full px-0 focus:text-bunker-100 focus:ring-transparent" + className="w-full px-0 placeholder:text-red-600 focus:text-bunker-100 focus:ring-transparent" /> )} /> From e3e4a98cd6b97d8fda703ae4c765696131357346 Mon Sep 17 00:00:00 2001 From: Rhythm Bhiwani Date: Sat, 2 Mar 2024 19:46:04 +0530 Subject: [PATCH 04/24] changed share of red in error message --- .../SecretMainPage/components/SecretListView/SecretItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx index 33661dee7..3bc66d0a5 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx @@ -255,7 +255,7 @@ export const SecretItem = memo( }); }} {...field} - className="w-full px-0 placeholder:text-red-600 focus:text-bunker-100 focus:ring-transparent" + className="w-full px-0 placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent" /> )} /> From 68296c1b994ce2b7f6cb9cb47f236f077f7df3c0 Mon Sep 17 00:00:00 2001 From: Rhythm Bhiwani Date: Sun, 3 Mar 2024 01:37:49 +0530 Subject: [PATCH 05/24] Disabled submit button if secret name is empty --- .../components/SecretListView/SecretItem.tsx | 26 ++++++++----------- .../SecretListView/SecretListView.utils.ts | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx index 3bc66d0a5..d165b4bd5 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx @@ -49,7 +49,6 @@ import { memo, useEffect } from "react"; import { Controller, useFieldArray, useForm } from "react-hook-form"; import { twMerge } from "tailwind-merge"; -import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { CreateReminderForm } from "./CreateReminderForm"; import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils"; @@ -87,7 +86,6 @@ export const SecretItem = memo( }: Props) => { const { currentWorkspace } = useWorkspace(); const { permission } = useProjectPermission(); - const { createNotification } = useNotificationContext(); const isReadOnly = permission.can( ProjectPermissionActions.Read, @@ -106,7 +104,8 @@ export const SecretItem = memo( setValue, reset, getValues, - formState: { isDirty, isSubmitting } + trigger, + formState: { isDirty, isSubmitting, errors } } = useForm({ defaultValues: secret, values: secret, @@ -244,16 +243,9 @@ export const SecretItem = memo( autoCapitalization={currentWorkspace?.autoCapitalization} variant="plain" isDisabled={isOverriden} - placeholder="Secret name is required" - isRequired + placeholder={error?.message} isError={Boolean(error)} - onInvalid={(e) => { - e.preventDefault(); - createNotification({ - text: "Secret name cannot be empty", - type: "error" - }); - }} + onKeyUp={() => trigger("key")} {...field} className="w-full px-0 placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent" /> @@ -509,7 +501,7 @@ export const SecretItem = memo( animate={{ x: 0, opacity: 1 }} exit={{ x: -10, opacity: 0 }} > - + {isSubmitting ? ( ) : ( - + )} diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts index fc8a745d5..40e949bc8 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts @@ -8,7 +8,7 @@ export enum SecretActionType { } export const formSchema = z.object({ - key: z.string().trim(), + key: z.string().trim().min(1, { message: "Secret key is required" }), value: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())), idOverride: z.string().trim().optional(), valueOverride: z From fafd963a8a2cbcc585e0861df1c5083927a88545 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Mon, 4 Mar 2024 14:32:53 +0530 Subject: [PATCH 06/24] feat(ui): updated create secret form in overview to better ux --- .../src/components/v2/Checkbox/Checkbox.tsx | 4 +- .../SecretOverviewPage/SecretOverviewPage.tsx | 6 +- .../CreateSecretForm/CreateSecretForm.tsx | 222 ++++++++++++++++++ .../components/CreateSecretForm/index.tsx | 1 + .../CreateSecretInAllEnvsForm.tsx | 156 ------------ 5 files changed, 228 insertions(+), 161 deletions(-) create mode 100644 frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx create mode 100644 frontend/src/views/SecretOverviewPage/components/CreateSecretForm/index.tsx delete mode 100644 frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx diff --git a/frontend/src/components/v2/Checkbox/Checkbox.tsx b/frontend/src/components/v2/Checkbox/Checkbox.tsx index 87e278119..dc0e1a491 100644 --- a/frontend/src/components/v2/Checkbox/Checkbox.tsx +++ b/frontend/src/components/v2/Checkbox/Checkbox.tsx @@ -30,7 +30,7 @@ export const Checkbox = ({
-
- handlePopUpToggle("addSecretsInAllEnvs", isOpen)} onClose={() => handlePopUpClose("addSecretsInAllEnvs")} decryptFileKey={latestFileKey!} diff --git a/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx b/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx new file mode 100644 index 000000000..52eebed9e --- /dev/null +++ b/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx @@ -0,0 +1,222 @@ +import { Controller, useForm } from "react-hook-form"; +import { faWarning } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { + Button, + Checkbox, + FormControl, + FormLabel, + Input, + Modal, + ModalContent, + SecretInput, + Tooltip +} from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useCreateFolder, useCreateSecretV3, useUpdateSecretV3 } from "@app/hooks/api"; +import { DecryptedSecret, UserWsKeyPair } from "@app/hooks/api/types"; + +const typeSchema = z + .object({ + key: z.string().min(1, "Key is required"), + value: z.string().optional(), + environments: z.record(z.boolean().optional()) + }) + .refine((data) => data.key !== undefined, { + message: "Please enter secret name" + }); + +type TFormSchema = z.infer; + +type Props = { + secretPath?: string; + decryptFileKey: UserWsKeyPair; + getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; + // modal props + isOpen?: boolean; + onClose: () => void; + onTogglePopUp: (isOpen: boolean) => void; +}; + +export const CreateSecretForm = ({ + secretPath = "/", + decryptFileKey, + isOpen, + getSecretByKey, + onClose, + onTogglePopUp +}: Props) => { + const { + register, + handleSubmit, + control, + reset, + watch, + formState: { isSubmitting, errors } + } = useForm({ resolver: zodResolver(typeSchema) }); + const newSecretKey = watch("key"); + + const { currentWorkspace } = useWorkspace(); + const workspaceId = currentWorkspace?.id || ""; + const environments = currentWorkspace?.environments || []; + + const { createNotification } = useNotificationContext(); + + const { mutateAsync: createSecretV3 } = useCreateSecretV3(); + const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); + const { mutateAsync: createFolder } = useCreateFolder(); + + const handleFormSubmit = async ({ key, value, environments: selectedEnv }: TFormSchema) => { + const environmentsSelected = environments.filter(({ slug }) => selectedEnv[slug]); + const isEnvironmentsSelected = environmentsSelected.length; + + if (!isEnvironmentsSelected) { + createNotification({ type: "error", text: "Select atleast one environment" }); + return; + } + + const promises = environmentsSelected.map(async (env) => { + const environment = env.slug; + // create folder if not existing + if (secretPath !== "/") { + // /hello/world -> [hello","world"] + const pathSegment = secretPath.split("/").filter(Boolean); + const parentPath = `/${pathSegment.slice(0, -1).join("/")}`; + const folderName = pathSegment.at(-1); + if (folderName && parentPath) { + await createFolder({ + projectId: workspaceId, + path: parentPath, + environment, + name: folderName + }); + } + } + + const isEdit = getSecretByKey(environment, key) !== undefined; + if (isEdit) { + return updateSecretV3({ + environment, + workspaceId, + secretPath, + secretName: key, + secretValue: value || "", + type: "shared", + latestFileKey: decryptFileKey + }); + } + + return createSecretV3({ + environment, + workspaceId, + secretPath, + secretName: key, + secretValue: value || "", + secretComment: "", + type: "shared", + latestFileKey: decryptFileKey + }); + }); + + const results = await Promise.allSettled(promises); + const isSecretsAdded = results.some((result) => result.status === "fulfilled"); + + if (isSecretsAdded) { + createNotification({ + type: "success", + text: "Secrets created successfully" + }); + onClose(); + reset(); + } else { + createNotification({ + type: "error", + text: "Failed to create secrets" + }); + } + }; + return ( + + +
+ + + + ( + + + + )} + /> + +
+ {environments.map((env) => { + return ( + ( + + {env.name} + {getSecretByKey(env.slug, newSecretKey) && ( + + + + )} + + )} + /> + ); + })} +
+
+ + +
+ +
+
+ ); +}; diff --git a/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/index.tsx b/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/index.tsx new file mode 100644 index 000000000..ffbe98f57 --- /dev/null +++ b/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/index.tsx @@ -0,0 +1 @@ +export { CreateSecretForm } from "./CreateSecretForm" diff --git a/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx b/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx deleted file mode 100644 index bc716edff..000000000 --- a/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { Controller, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; - -import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import { Button, FormControl, Input, Modal, ModalContent, SecretInput } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; -import { useCreateSecretV3 } from "@app/hooks/api"; -import { UserWsKeyPair } from "@app/hooks/api/types"; - -const typeSchema = z - .object({ - key: z.string(), - values: z.record(z.string().optional()) - }) - .refine((data) => data.key !== undefined, { - message: "Please enter secret name" - }); - -type TFormSchema = z.infer; - -type Props = { - workspaceId: string; - secretPath?: string; - decryptFileKey: UserWsKeyPair; - - // modal props - isOpen?: boolean; - onClose: () => void; - onTogglePopUp: (isOpen: boolean) => void; -}; - -export const CreateSecretInAllEnvsForm = ({ - workspaceId, - secretPath = "/", - decryptFileKey, - isOpen, - onClose, - onTogglePopUp -}: Props) => { - const { - register, - handleSubmit, - control, - reset, - setError, - formState: { isSubmitting, errors } - } = useForm({ resolver: zodResolver(typeSchema) }); - const { currentWorkspace } = useWorkspace(); - const environments = currentWorkspace?.environments || []; - - const { createNotification } = useNotificationContext(); - - const { mutateAsync: createSecretV3 } = useCreateSecretV3(); - - const handleFormSubmit = async ({ key, values }: TFormSchema) => { - if (!key) { - setError("key", { message: "Please enter secret name" }); - return; - } - - const promises = environments.map((env) => { - const environment = env.slug; - const value = values[environment] || ""; - - return createSecretV3({ - environment, - workspaceId, - secretPath, - secretName: key, - secretValue: value, - secretComment: "", - type: "shared", - latestFileKey: decryptFileKey - }); - }); - - const results = await Promise.allSettled(promises); - const isSecretsAdded = results.some((result) => result.status === "fulfilled"); - - if (isSecretsAdded) { - createNotification({ - type: "success", - text: "Secrets created successfully" - }); - onClose(); - reset(); - } else { - createNotification({ - type: "error", - text: "Failed to create secrets" - }); - } - }; - return ( - - -
- - - - - {environments.map((env) => { - return ( - ( - - - - )} - /> - ); - })} - -
- - -
- -
-
- ); -}; From 564b6b8ef61233968957b68ab7e19119b10181ab Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Mon, 4 Mar 2024 14:50:32 +0530 Subject: [PATCH 07/24] chore: made db connection uri in example to get value from POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 8a714f77d..8463fea92 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 # Required -DB_CONNECTION_URI=postgres://infisical:infisical@db:5432/infisical +DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} # JWT # Required secrets to sign JWT tokens From cd0df2d617c3959ebcf5459ab3a7be3147a8126c Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 5 Mar 2024 14:05:58 -0500 Subject: [PATCH 08/24] update change log script --- .github/resources/changelog-generator.py | 40 +++++++++++++------ .../workflows/generate-release-changelog.yml | 18 ++++----- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/.github/resources/changelog-generator.py b/.github/resources/changelog-generator.py index 1e8c67871..7dd8140ee 100644 --- a/.github/resources/changelog-generator.py +++ b/.github/resources/changelog-generator.py @@ -12,7 +12,7 @@ import uuid REPO_OWNER = "infisical" REPO_NAME = "infisical" TOKEN = os.environ["GITHUB_TOKEN"] -# SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"] +SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"] OPENAI_API_KEY = os.environ["OPENAI_API_KEY"] SLACK_MSG_COLOR = "#36a64f" @@ -30,6 +30,23 @@ def set_multiline_output(name, value): print(value, file=fh) print(delimiter, file=fh) +def post_changelog_to_slack(changelog, tag): + slack_payload = { + "text": "Hey team, it's changelog time! :wave:", + "attachments": [ + { + "color": SLACK_MSG_COLOR, + "title": f"🗓️Infisical Changelog - {tag}", + "text": changelog, + } + ], + } + + response = requests.post(SLACK_WEBHOOK_URL, json=slack_payload) + + if response.status_code != 200: + raise Exception("Failed to post changelog to Slack.") + def find_previous_release_tag(release_tag:str): previous_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0", f"{release_tag}^"]).decode("utf-8").strip() while not(previous_tag.startswith("infisical/")): @@ -123,20 +140,17 @@ The changelog should: 6. Linear Links: note that the Linear link is optional, include it only if provided. 7. Do not wrap your answer in a codeblock. Just output the text, nothing else Here's a good example to follow, please try to match the formatting as closely as possible, only changing the content of the changelog and have some liberty with the introduction. Notice the importance of the formatting of a changelog item: -``` -- : We optimize our ci to strip comments and minify production builds. ()) -``` +- : We optimize our ci to strip comments and minify production builds. ()) And here's an example of the full changelog: -``` + *Features* -• : We optimize our ci to strip comments and minify production builds. () +• : We optimize our ci to strip comments and minify production builds. () *Fixes & Improvements* -• : We optimize our ci to strip comments and minify production builds. () +• : We optimize our ci to strip comments and minify production builds. () *Technical Updates* -• : We optimize our ci to strip comments and minify production builds. () +• : We optimize our ci to strip comments and minify production builds. () Stay tuned for more exciting updates coming soon! -``` And here are the commits: {} """.format( @@ -166,11 +180,11 @@ if __name__ == "__main__": pr_details = extract_commit_details_from_prs(prs) # Generate changelog - changelog = f"## Infisical - {latest_tag}\n\n{generate_changelog_with_openai(pr_details)}" + changelog = generate_changelog_with_openai(pr_details) + post_changelog_to_slack(changelog,latest_tag) # Print or post changelog to Slack - set_multiline_output("changelog", changelog) + # set_multiline_output("changelog", changelog) except Exception as e: - print(str(e)) - + print(str(e)) \ No newline at end of file diff --git a/.github/workflows/generate-release-changelog.yml b/.github/workflows/generate-release-changelog.yml index 48e35497d..de14f288a 100644 --- a/.github/workflows/generate-release-changelog.yml +++ b/.github/workflows/generate-release-changelog.yml @@ -2,7 +2,10 @@ name: Generate Changelog permissions: contents: write -on: [workflow_dispatch] +on: + push: + tags: + - "infisical/v*.*.*-postgres" jobs: generate_changelog: @@ -10,6 +13,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + with: + fetch-tags: true - name: Set up Python uses: actions/setup-python@v5 with: @@ -24,13 +29,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - - name: Set git identity - run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@infisical.noreply.github.com' - - name: Save the changelog to file - run: | - echo "${{ steps.gen-changelog.outputs.changelog }}" >> CHANGELOG.md - git add CHANGELOG.md - git commit -m "chore: changelog update" --no-verify - git push origin main + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} \ No newline at end of file From 03e965ec5a013657cf635729e2b3a16347a14d59 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 5 Mar 2024 14:09:25 -0500 Subject: [PATCH 09/24] add workflow dispatch option --- .github/workflows/generate-release-changelog.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/generate-release-changelog.yml b/.github/workflows/generate-release-changelog.yml index de14f288a..263ed19bc 100644 --- a/.github/workflows/generate-release-changelog.yml +++ b/.github/workflows/generate-release-changelog.yml @@ -3,6 +3,7 @@ permissions: contents: write on: + workflow_dispatch: push: tags: - "infisical/v*.*.*-postgres" From d6d780a7b4d18cddbd8f71efc47e23c984259b90 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 5 Mar 2024 14:13:11 -0500 Subject: [PATCH 10/24] Update generate-release-changelog.yml --- .github/workflows/generate-release-changelog.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate-release-changelog.yml b/.github/workflows/generate-release-changelog.yml index 263ed19bc..37dd2253a 100644 --- a/.github/workflows/generate-release-changelog.yml +++ b/.github/workflows/generate-release-changelog.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-tags: true - name: Set up Python @@ -30,4 +30,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} \ No newline at end of file + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From c7d08745fcf08e81ae23c52b5b263d3469c8c311 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 5 Mar 2024 14:15:32 -0500 Subject: [PATCH 11/24] fetch tags manaully --- .github/workflows/generate-release-changelog.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/generate-release-changelog.yml b/.github/workflows/generate-release-changelog.yml index 37dd2253a..45186e874 100644 --- a/.github/workflows/generate-release-changelog.yml +++ b/.github/workflows/generate-release-changelog.yml @@ -16,6 +16,8 @@ jobs: uses: actions/checkout@v4 with: fetch-tags: true + - name: Get tags + run: git fetch --tags origin - name: Set up Python uses: actions/setup-python@v5 with: From 919e1843052d1565be3ec35d625aa72c8efa14f4 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 5 Mar 2024 14:18:41 -0500 Subject: [PATCH 12/24] update change log --- .github/workflows/generate-release-changelog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate-release-changelog.yml b/.github/workflows/generate-release-changelog.yml index 45186e874..c3cafe0a3 100644 --- a/.github/workflows/generate-release-changelog.yml +++ b/.github/workflows/generate-release-changelog.yml @@ -17,7 +17,7 @@ jobs: with: fetch-tags: true - name: Get tags - run: git fetch --tags origin + run: git fetch origin - name: Set up Python uses: actions/setup-python@v5 with: From 3174896d3728c08b2392a70970c4011d685fdcc6 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 5 Mar 2024 14:22:53 -0500 Subject: [PATCH 13/24] update change log script --- .github/workflows/generate-release-changelog.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/generate-release-changelog.yml b/.github/workflows/generate-release-changelog.yml index c3cafe0a3..e26a304cf 100644 --- a/.github/workflows/generate-release-changelog.yml +++ b/.github/workflows/generate-release-changelog.yml @@ -16,8 +16,7 @@ jobs: uses: actions/checkout@v4 with: fetch-tags: true - - name: Get tags - run: git fetch origin + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: From 5f5f46eddf263262cab7f696dacab476b45cdffc Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 05:05:55 +0100 Subject: [PATCH 14/24] Update secret-service.ts --- backend/src/services/secret/secret-service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 4c14edc0c..ecd13dae1 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -375,6 +375,10 @@ export const secretServiceFactory = ({ await projectDAL.checkProjectUpgradeStatus(projectId); + if (inputSecret.newSecretName === "") { + throw new BadRequestError({ message: "New secret name cannot be empty" }); + } + const folder = await folderDAL.findBySecretPath(projectId, environment, path); if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); const folderId = folder.id; From ed6c6e8d1e097fb0b5935a2b4c77cdec8dc6a77f Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Wed, 6 Mar 2024 23:21:06 +0530 Subject: [PATCH 15/24] fix(server): removed global aws cred config in secret integration --- .../integration-sync-secret.ts | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index abb6a7b1e..62a4dcfa7 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -441,16 +441,19 @@ const syncSecretsAWSParameterStore = async ({ }) => { if (!accessId) return; - AWS.config.update({ + const config = new AWS.Config({ region: integration.region as string, - accessKeyId: accessId, - secretAccessKey: accessToken + credentials: { + accessKeyId: accessId, + secretAccessKey: accessToken + } }); const ssm = new AWS.SSM({ apiVersion: "2014-11-06", region: integration.region as string }); + ssm.config.update(config); const params = { Path: integration.path as string, @@ -514,12 +517,6 @@ const syncSecretsAWSParameterStore = async ({ } }) ); - - AWS.config.update({ - region: undefined, - accessKeyId: undefined, - secretAccessKey: undefined - }); }; /** @@ -541,12 +538,6 @@ const syncSecretsAWSSecretManager = async ({ try { if (!accessId) return; - AWS.config.update({ - region: integration.region as string, - accessKeyId: accessId, - secretAccessKey: accessToken - }); - secretsManager = new SecretsManagerClient({ region: integration.region as string, credentials: { @@ -575,12 +566,6 @@ const syncSecretsAWSSecretManager = async ({ }) ); } - - AWS.config.update({ - region: undefined, - accessKeyId: undefined, - secretAccessKey: undefined - }); } catch (err) { if (err instanceof ResourceNotFoundException && secretsManager) { await secretsManager.send( @@ -590,11 +575,6 @@ const syncSecretsAWSSecretManager = async ({ }) ); } - AWS.config.update({ - region: undefined, - accessKeyId: undefined, - secretAccessKey: undefined - }); } }; From d140e4f3c944fb054ec57e1c62de59d6ef641e7a Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Wed, 6 Mar 2024 14:12:05 -0500 Subject: [PATCH 16/24] update bulk add message --- .../components/CreateSecretForm/CreateSecretForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx b/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx index 52eebed9e..9b112dd4b 100644 --- a/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx +++ b/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx @@ -143,8 +143,8 @@ export const CreateSecretForm = ({
From 3375d3ff850fd725e43de79d46417a0dfa510ba3 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:09:25 +0100 Subject: [PATCH 17/24] Update project-queue.ts --- backend/src/services/project/project-queue.ts | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index efc795cae..9cdd228a0 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -102,8 +102,11 @@ export const projectQueueFactory = ({ const oldProjectKey = await projectKeyDAL.findLatestProjectKey(data.startedByUserId, data.projectId); - if (!project || !oldProjectKey) { - throw new Error("Project or project key not found"); + if (!project) { + throw new Error("Project not found"); + } + if (!oldProjectKey) { + throw new Error("Old project key not found"); } if (project.upgradeStatus !== ProjectUpgradeStatus.Failed && project.upgradeStatus !== null) { @@ -267,8 +270,19 @@ export const projectQueueFactory = ({ const user = await userDAL.findUserEncKeyByUserId(key.receiverId); const [orgMembership] = await orgDAL.findMembership({ userId: key.receiverId, orgId: project.orgId }); - if (!user || !orgMembership) { - throw new Error(`User with ID ${key.receiverId} was not found during upgrade, or user is not in org.`); + if (!user) { + throw new Error(`User with ID ${key.receiverId} was not found during upgrade.`); + } + + if (!orgMembership) { + // This can happen. Since we don't remove project memberships and project keys when a user is removed from an org, this is a valid case. + logger.info("User is not in organization", { + userId: key.receiverId, + orgId: project.orgId, + projectId: project.id + }); + // eslint-disable-next-line no-continue + continue; } const [newMember] = assignWorkspaceKeysToMembers({ @@ -515,6 +529,8 @@ export const projectQueueFactory = ({ throw new Error("Parts of the upgrade failed. Some secrets were not updated"); } + throw new Error("It worked! (This is a test error message)"); + await projectDAL.setProjectUpgradeStatus(data.projectId, null, tx); // await new Promise((resolve) => setTimeout(resolve, 15_000)); @@ -532,7 +548,12 @@ export const projectQueueFactory = ({ logger.error("Failed to upgrade project, because no project was found", data); } else { await projectDAL.setProjectUpgradeStatus(data.projectId, ProjectUpgradeStatus.Failed); - logger.error(err, "Failed to upgrade project"); + logger.error("Failed to upgrade project", err, { + extra: { + project, + jobData: data + } + }); } throw err; From da5eca3e68225c07d0a008c9f5198ae20f44c639 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:13:22 +0100 Subject: [PATCH 18/24] Fix: Seeding not working --- backend/src/db/seeds/1-user.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/db/seeds/1-user.ts b/backend/src/db/seeds/1-user.ts index 8e2b00b90..5a5ff5404 100644 --- a/backend/src/db/seeds/1-user.ts +++ b/backend/src/db/seeds/1-user.ts @@ -9,7 +9,12 @@ export async function seed(knex: Knex): Promise { await knex(TableName.Users).del(); await knex(TableName.UserEncryptionKey).del(); await knex(TableName.SuperAdmin).del(); - await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true }]); + + await knex(TableName.SuperAdmin).insert([ + // eslint-disable-next-line + // @ts-ignore + { id: "00000000-0000-0000-0000-000000000000", initialized: true, allowSignUp: true } + ]); // Inserts seed entries const [user] = await knex(TableName.Users) .insert([ From 0b2bc1d345f4e5af2c219917d36eae3dbec3ea05 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:14:52 +0100 Subject: [PATCH 19/24] Fix: Remove project memberships & project keys when org membership is deleted --- backend/src/server/routes/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index d3d01ccb5..7880f79e3 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -263,6 +263,8 @@ export const registerRoutes = async ( incidentContactDAL, tokenService, projectDAL, + projectMembershipDAL, + projectKeyDAL, smtpService, userDAL, orgBotDAL From 7ae024724db4c165fae94fdc4e554ef241c78e6d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:15:02 +0100 Subject: [PATCH 20/24] Fix: Remove project memberships & project keys when org membership is deleted (Service) --- backend/src/services/org/org-service.ts | 52 +++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index f39ac5366..28cf4b382 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -22,6 +22,8 @@ import { ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type"; import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; import { TProjectDALFactory } from "../project/project-dal"; +import { TProjectKeyDALFactory } from "../project-key/project-key-dal"; +import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { TUserDALFactory } from "../user/user-dal"; import { TIncidentContactsDALFactory } from "./incident-contacts-dal"; @@ -44,6 +46,8 @@ type TOrgServiceFactoryDep = { orgRoleDAL: TOrgRoleDALFactory; userDAL: TUserDALFactory; projectDAL: TProjectDALFactory; + projectMembershipDAL: Pick; + projectKeyDAL: Pick; incidentContactDAL: TIncidentContactsDALFactory; samlConfigDAL: Pick; smtpService: TSmtpService; @@ -65,6 +69,8 @@ export const orgServiceFactory = ({ permissionService, smtpService, projectDAL, + projectMembershipDAL, + projectKeyDAL, tokenService, orgBotDAL, licenseService, @@ -503,10 +509,50 @@ export const orgServiceFactory = ({ const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member); - const membership = await orgDAL.deleteMembershipById(membershipId, orgId); + const deletedMembership = await orgDAL.transaction(async (tx) => { + const orgMembership = await orgDAL.deleteMembershipById(membershipId, orgId, tx); - await licenseService.updateSubscriptionOrgMemberCount(orgId); - return membership; + if (!orgMembership.userId) { + await licenseService.updateSubscriptionOrgMemberCount(orgId); + return orgMembership; + } + + // Get all the project memberships of the user in the organization + const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, orgMembership.userId); + + // Delete all the project memberships of the user in the organization + await projectMembershipDAL.delete( + { + $in: { + id: projectMemberships.map((membership) => membership.id) + } + }, + tx + ); + + // Get all the project keys of the user in the organization + const projectKeys = await projectKeyDAL.find({ + $in: { + projectId: projectMemberships.map((membership) => membership.projectId) + }, + receiverId: orgMembership.userId + }); + + // Delete all the project keys of the user in the organization + await projectKeyDAL.delete( + { + $in: { + id: projectKeys.map((key) => key.id) + } + }, + tx + ); + + await licenseService.updateSubscriptionOrgMemberCount(orgId); + return orgMembership; + }); + + return deletedMembership; }; /* From ae53f03f7161bb3f3578379235be2ec4f358ebb5 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:15:13 +0100 Subject: [PATCH 21/24] Fix: Remove project memberships & project keys when org membership is deleted (DAL) --- .../project-membership-dal.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/src/services/project-membership/project-membership-dal.ts b/backend/src/services/project-membership/project-membership-dal.ts index e0002f911..77ed867c4 100644 --- a/backend/src/services/project-membership/project-membership-dal.ts +++ b/backend/src/services/project-membership/project-membership-dal.ts @@ -82,5 +82,25 @@ export const projectMembershipDALFactory = (db: TDbClient) => { } }; - return { ...projectMemberOrm, findAllProjectMembers, findProjectGhostUser, findMembershipsByEmail }; + const findProjectMembershipsByUserId = async (orgId: string, userId: string) => { + try { + const memberships = await db(TableName.ProjectMembership) + .where({ userId }) + .join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`) + .where({ [`${TableName.Project}.orgId` as "orgId"]: orgId }) + .select(selectAllTableCols(TableName.ProjectMembership)); + + return memberships; + } catch (error) { + throw new DatabaseError({ error, name: "Find project memberships by user id" }); + } + }; + + return { + ...projectMemberOrm, + findAllProjectMembers, + findProjectGhostUser, + findMembershipsByEmail, + findProjectMembershipsByUserId + }; }; From 889df3dcb1b45b3579e0327f47f005e548ee9019 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:20:23 +0100 Subject: [PATCH 22/24] Update project-queue.ts --- backend/src/services/project/project-queue.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index 9cdd228a0..e1d45e96d 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -529,8 +529,6 @@ export const projectQueueFactory = ({ throw new Error("Parts of the upgrade failed. Some secrets were not updated"); } - throw new Error("It worked! (This is a test error message)"); - await projectDAL.setProjectUpgradeStatus(data.projectId, null, tx); // await new Promise((resolve) => setTimeout(resolve, 15_000)); From b910ceacfccc742efad3b944c4c57856f27b33c7 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Wed, 6 Mar 2024 17:50:19 -0500 Subject: [PATCH 23/24] create secret on overview typo --- .../components/CreateSecretForm/CreateSecretForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx b/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx index 9b112dd4b..403564274 100644 --- a/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx +++ b/frontend/src/views/SecretOverviewPage/components/CreateSecretForm/CreateSecretForm.tsx @@ -75,7 +75,7 @@ export const CreateSecretForm = ({ const isEnvironmentsSelected = environmentsSelected.length; if (!isEnvironmentsSelected) { - createNotification({ type: "error", text: "Select atleast one environment" }); + createNotification({ type: "error", text: "Select at least one environment" }); return; } From 6d0bea6d5f4423f665fb29515f022d3319ef6917 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Wed, 6 Mar 2024 18:42:27 -0500 Subject: [PATCH 24/24] Update .goreleaser.yaml to support arm --- .goreleaser.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 1c5db8f00..054359527 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -197,3 +197,14 @@ dockers: - "infisical/cli:{{ .Major }}.{{ .Minor }}" - "infisical/cli:{{ .Major }}" - "infisical/cli:latest" + + - dockerfile: docker/alpine + goos: linux + goarch: arm64 + ids: + - all-other-builds + image_templates: + - "infisical/cli:{{ .Version }}" + - "infisical/cli:{{ .Major }}.{{ .Minor }}" + - "infisical/cli:{{ .Major }}" + - "infisical/cli:latest"