diff --git a/backend/src/controllers/v1/roleController.ts b/backend/src/controllers/v1/roleController.ts index f5d50d22f..c1fe7d66f 100644 --- a/backend/src/controllers/v1/roleController.ts +++ b/backend/src/controllers/v1/roleController.ts @@ -138,6 +138,17 @@ export const getRoles = async (req: Request, res: Response) => { const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId }); const roles = [ + ...(isOrgRole + ? [ + { + _id: "owner", + name: "Owner", + slug: "owner", + description: "Complete administration access over the organization.", + permissions: adminPermissions.rules + } + ] + : []), { _id: "admin", name: "Admin", @@ -152,24 +163,19 @@ export const getRoles = async (req: Request, res: Response) => { description: "Non-administrative role in an organization", permissions: isOrgRole ? memberPermissions.rules : adminProjectPermissions.rules }, - { - _id: "viewer", - name: "Viewer", - slug: "viewer", - description: "Non-administrative role in an organization", - permissions: isOrgRole ? viewerProjectPermission.rules : viewerProjectPermission.rules - }, + ...(isOrgRole + ? [] + : [ + { + _id: "viewer", + name: "Viewer", + slug: "viewer", + description: "Non-administrative role in an organization", + permissions: isOrgRole ? viewerProjectPermission.rules : viewerProjectPermission.rules + } + ]), ...customRoles ]; - if (isOrgRole) { - roles.unshift({ - _id: "owner", - name: "Owner", - slug: "owner", - description: "Complete administration access over the organization.", - permissions: adminPermissions.rules - }); - } res.status(200).json({ message: "Successfully fetched role list", diff --git a/frontend/src/components/permissions/ProjectPermissionCan.tsx b/frontend/src/components/permissions/ProjectPermissionCan.tsx index 9f6cd39cc..2371450fb 100644 --- a/frontend/src/components/permissions/ProjectPermissionCan.tsx +++ b/frontend/src/components/permissions/ProjectPermissionCan.tsx @@ -10,7 +10,7 @@ type Props = { } & BoundCanProps; export const ProjectPermissionCan: FunctionComponent = ({ - label = "Permission Denied. Kindly contact your org admin", + label = "Permission Denied. Kindly contact your project admin", children, passThrough = true, ...props diff --git a/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx b/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx index 054ce34a1..e8e4e5b2c 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx @@ -4,7 +4,8 @@ import { useRouter } from "next/router"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { Button, Modal, ModalContent } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { withProjectPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { useDeleteIntegration, @@ -31,191 +32,194 @@ type Props = { frameworkIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>; }; -export const IntegrationsPage = ({ frameworkIntegrations }: Props) => { - const { t } = useTranslation(); - const { createNotification } = useNotificationContext(); - const router = useRouter(); +export const IntegrationsPage = withProjectPermission( + ({ frameworkIntegrations }: Props) => { + const { t } = useTranslation(); + const { createNotification } = useNotificationContext(); + const router = useRouter(); - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?._id || ""; - const environments = currentWorkspace?.environments || []; + const { currentWorkspace } = useWorkspace(); + const workspaceId = currentWorkspace?._id || ""; + const environments = currentWorkspace?.environments || []; - const { data: latestWsKey } = useGetUserWsKey(workspaceId); + const { data: latestWsKey } = useGetUserWsKey(workspaceId); - const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ - "activeBot" - ] as const); + const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ + "activeBot" + ] as const); - const { data: cloudIntegrations, isLoading: isCloudIntegrationsLoading } = - useGetCloudIntegrations(); + const { data: cloudIntegrations, isLoading: isCloudIntegrationsLoading } = + useGetCloudIntegrations(); - const { data: integrationAuths, isLoading: isIntegrationAuthLoading } = - useGetWorkspaceAuthorizations( - workspaceId, - useCallback((data: IntegrationAuth[]) => { - const groupBy: Record = {}; - data.forEach((el) => { - groupBy[el.integration] = el; - }); - return groupBy; - }, []) - ); - // mutation - const { - data: integrations, - isLoading: isIntegrationLoading, - isFetching: isIntegrationFetching - } = useGetWorkspaceIntegrations(workspaceId); + const { data: integrationAuths, isLoading: isIntegrationAuthLoading } = + useGetWorkspaceAuthorizations( + workspaceId, + useCallback((data: IntegrationAuth[]) => { + const groupBy: Record = {}; + data.forEach((el) => { + groupBy[el.integration] = el; + }); + return groupBy; + }, []) + ); + // mutation + const { + data: integrations, + isLoading: isIntegrationLoading, + isFetching: isIntegrationFetching + } = useGetWorkspaceIntegrations(workspaceId); - const { data: bot } = useGetWorkspaceBot(workspaceId); + const { data: bot } = useGetWorkspaceBot(workspaceId); - // mutation - const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } = - useUpdateBotActiveStatus(); - const { mutateAsync: deleteIntegration } = useDeleteIntegration(); - const { - mutateAsync: deleteIntegrationAuth, - isLoading: isDeleteIntegrationAuthSuccess, - reset: resetDeleteIntegrationAuth - } = useDeleteIntegrationAuth(); + // mutation + const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } = + useUpdateBotActiveStatus(); + const { mutateAsync: deleteIntegration } = useDeleteIntegration(); + const { + mutateAsync: deleteIntegrationAuth, + isLoading: isDeleteIntegrationAuthSuccess, + reset: resetDeleteIntegrationAuth + } = useDeleteIntegrationAuth(); - // summary: this use effect is trigger when all integration auths are removed thus deactivate bot - // details: so onsuccessfully deleting an integration auth, immediately integration list is refeteched - // After the refetch is completed check if its empty. Then set bot active and reset the submit hook - useEffect(() => { - if (isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !integrations?.length) { - if (bot?._id) - updateBotActiveStatusSync({ - isActive: false, - botId: bot._id, - workspaceId - }); - resetDeleteIntegrationAuth(); - } - }, [isIntegrationFetching, isDeleteIntegrationAuthSuccess, integrations?.length]); - - const handleProviderIntegration = async (provider: string) => { - const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug); - if (!selectedCloudIntegration) return; - - try { - if (bot && !bot.isActive) { - const botKey = generateBotKey(bot.publicKey, latestWsKey!); - await updateBotActiveStatus({ - workspaceId, - botKey, - isActive: true, - botId: bot._id - }); + // summary: this use effect is trigger when all integration auths are removed thus deactivate bot + // details: so onsuccessfully deleting an integration auth, immediately integration list is refeteched + // After the refetch is completed check if its empty. Then set bot active and reset the submit hook + useEffect(() => { + if (isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !integrations?.length) { + if (bot?._id) + updateBotActiveStatusSync({ + isActive: false, + botId: bot._id, + workspaceId + }); + resetDeleteIntegrationAuth(); } - const integrationAuthForProvider = integrationAuths?.[provider]; - if (!integrationAuthForProvider) { - redirectForProviderAuth(selectedCloudIntegration); + }, [isIntegrationFetching, isDeleteIntegrationAuthSuccess, integrations?.length]); + + const handleProviderIntegration = async (provider: string) => { + const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug); + if (!selectedCloudIntegration) return; + + try { + if (bot && !bot.isActive) { + const botKey = generateBotKey(bot.publicKey, latestWsKey!); + await updateBotActiveStatus({ + workspaceId, + botKey, + isActive: true, + botId: bot._id + }); + } + const integrationAuthForProvider = integrationAuths?.[provider]; + if (!integrationAuthForProvider) { + redirectForProviderAuth(selectedCloudIntegration); + return; + } + + const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id); + router.push(url); + } catch (error) { + console.error(error); + } + }; + + // function to strat integration for a provider + // confirmation to user passing the bot key for provider to get secret access + const handleProviderIntegrationStart = (provider: string) => { + if (!bot?.isActive) { + handlePopUpOpen("activeBot", { provider }); return; } + handleProviderIntegration(provider); + }; - const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id); - router.push(url); - } catch (error) { - console.error(error); - } - }; + const handleUserAcceptBotCondition = () => { + const { provider } = popUp.activeBot?.data as { provider: string }; + handleProviderIntegration(provider); + handlePopUpClose("activeBot"); + }; - // function to strat integration for a provider - // confirmation to user passing the bot key for provider to get secret access - const handleProviderIntegrationStart = (provider: string) => { - if (!bot?.isActive) { - handlePopUpOpen("activeBot", { provider }); - return; - } - handleProviderIntegration(provider); - }; + const handleIntegrationDelete = async (integrationId: string, cb: () => void) => { + try { + await deleteIntegration({ id: integrationId, workspaceId }); + if (cb) cb(); + createNotification({ + type: "success", + text: "Deleted integration" + }); + } catch (err) { + console.log(err); + createNotification({ + type: "error", + text: "Failed to delete integration" + }); + } + }; - const handleUserAcceptBotCondition = () => { - const { provider } = popUp.activeBot?.data as { provider: string }; - handleProviderIntegration(provider); - handlePopUpClose("activeBot"); - }; + const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => { + const integrationAuthForProvider = integrationAuths?.[provider]; + if (!integrationAuthForProvider) return; + try { + await deleteIntegrationAuth({ + id: integrationAuthForProvider._id, + workspaceId + }); + if (cb) cb(); + createNotification({ + type: "success", + text: "Revoked provider authentication" + }); + } catch (err) { + console.error(err); + createNotification({ + type: "error", + text: "Failed to revoke provider authentication" + }); + } + }; - const handleIntegrationDelete = async (integrationId: string, cb: () => void) => { - try { - await deleteIntegration({ id: integrationId, workspaceId }); - if (cb) cb(); - createNotification({ - type: "success", - text: "Deleted integration" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to delete integration" - }); - } - }; - - const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => { - const integrationAuthForProvider = integrationAuths?.[provider]; - if (!integrationAuthForProvider) return; - try { - await deleteIntegrationAuth({ - id: integrationAuthForProvider._id, - workspaceId - }); - if (cb) cb(); - createNotification({ - type: "success", - text: "Revoked provider authentication" - }); - } catch (err) { - console.error(err); - createNotification({ - type: "error", - text: "Failed to revoke provider authentication" - }); - } - }; - - return ( -
- handleIntegrationDelete(id, cb)} - /> - - handlePopUpToggle("activeBot", isOpen)} - > - - - -
- } + return ( +
+ handleIntegrationDelete(id, cb)} + /> + + handlePopUpToggle("activeBot", isOpen)} > - {t("integrations.why-infisical-needs-access")} - - - -
- ); -}; + + + + + } + > + {t("integrations.why-infisical-needs-access")} + + + + + ); + }, + { action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Integrations } +); diff --git a/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx b/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx index 7f458d5e5..9f00bfebe 100644 --- a/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx @@ -2,7 +2,9 @@ import { useTranslation } from "react-i18next"; import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { DeleteActionModal,Skeleton, Tooltip } from "@app/components/v2"; +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { DeleteActionModal, Skeleton, Tooltip } from "@app/components/v2"; +import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context"; import { usePopUp } from "@app/hooks"; import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types"; @@ -28,11 +30,13 @@ export const CloudIntegrationSection = ({ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "deleteConfirmation" ] as const); + const permission = useProjectPermission(); + const { createNotification } = useNotificationContext(); const isEmpty = !isLoading && !cloudIntegrations?.length; const sortedCloudIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name)); - + return (
@@ -57,6 +61,18 @@ export const CloudIntegrationSection = ({ } flex h-32 flex-row items-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4`} onClick={() => { if (!cloudIntegration.isAvailable) return; + if ( + permission.cannot( + ProjectPermissionActions.Create, + ProjectPermissionSub.Integrations + ) + ) { + createNotification({ + type: "error", + text: "Permission Denied. Kindly contact your project admin" + }); + return; + } onIntegrationStart(cloudIntegration.slug); }} key={cloudIntegration.slug} diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx index 72533796d..701766a9e 100644 --- a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx @@ -2,6 +2,7 @@ import { faArrowRight, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { integrationSlugNameMapping } from "public/data/frequentConstants"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { DeleteActionModal, EmptyState, @@ -13,6 +14,7 @@ import { Skeleton, Tooltip } from "@app/components/v2"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { usePopUp } from "@app/hooks"; import { TIntegration } from "@app/hooks/api/types"; @@ -125,18 +127,26 @@ export const IntegrationsSection = ({ )}
-
- - handlePopUpOpen("deleteConfirmation", integration)} - ariaLabel="delete" - colorSchema="danger" - variant="star" - > - - - -
+ + {(isAllowed) => ( +
+ + handlePopUpOpen("deleteConfirmation", integration)} + ariaLabel="delete" + isDisabled={!isAllowed} + colorSchema="danger" + variant="star" + > + + + +
+ )} +
))} diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx index c23770227..c0cb0e474 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx @@ -42,17 +42,19 @@ export const AutoCapitalizationSection = withProjectPermission(

{t("settings.project.auto-capitalization")}

{(isAllowed) => ( - { - handleToggleCapitalizationToggle(state as boolean); - }} - > - {t("settings.project.auto-capitalization-description")} - +
+ { + handleToggleCapitalizationToggle(state as boolean); + }} + > + {t("settings.project.auto-capitalization-description")} + +
)}
diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/E2EESection/E2EESection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/E2EESection/E2EESection.tsx index 815b50f24..24091b445 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/E2EESection/E2EESection.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/E2EESection/E2EESection.tsx @@ -90,17 +90,19 @@ export const E2EESection = withProjectPermission(

{(isAllowed) => ( - { - await toggleBotActivate(); - }} - > - End-to-end encryption enabled - +
+ { + await toggleBotActivate(); + }} + > + End-to-end encryption enabled + +
)}
diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx index 6b2f26881..fb892bb7e 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx @@ -1,4 +1,4 @@ -import { faArrowDown,faArrowUp, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { faArrowDown, faArrowUp, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; @@ -16,9 +16,7 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { - useReorderWsEnvironment -} from "@app/hooks/api"; +import { useReorderWsEnvironment } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { @@ -39,18 +37,23 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { const { createNotification } = useNotificationContext(); const reorderWsEnvironment = useReorderWsEnvironment(); - const handleReorderEnv= async (shouldMoveUp: boolean, name: string, slug: string) => { + const handleReorderEnv = async (shouldMoveUp: boolean, name: string, slug: string) => { try { if (!currentWorkspace?._id) return; - const indexOfEnv = currentWorkspace.environments.findIndex((env) => env.name === name && env.slug === slug); + const indexOfEnv = currentWorkspace.environments.findIndex( + (env) => env.name === name && env.slug === slug + ); // check that this reordering is possible - if (indexOfEnv === 0 && shouldMoveUp || indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp) { - return + if ( + (indexOfEnv === 0 && shouldMoveUp) || + (indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp) + ) { + return; } - const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1 + const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1; await reorderWsEnvironment.mutateAsync({ workspaceID: currentWorkspace._id, @@ -92,31 +95,48 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { {name} {slug} - { - handleReorderEnv(false, name, slug) - }} - colorSchema="primary" - variant="plain" - ariaLabel="update" - isDisabled={pos === currentWorkspace.environments.length - 1} + - - - { - handleReorderEnv(true, name, slug) - }} - colorSchema="primary" - variant="plain" - ariaLabel="update" - isDisabled={pos === 0} + {(isAllowed) => ( + { + handleReorderEnv(false, name, slug); + }} + colorSchema="primary" + variant="plain" + ariaLabel="update" + isDisabled={pos === currentWorkspace.environments.length - 1 || !isAllowed} + > + + + )} + + - - - ( + { + handleReorderEnv(true, name, slug); + }} + colorSchema="primary" + variant="plain" + ariaLabel="update" + isDisabled={pos === 0 || !isAllowed} + > + + + )} + + + {(isAllowed) => ( { if (!currentWorkspace?._id) return; if (!encryptedSecrets) return; @@ -65,9 +66,19 @@ export const ProjectIndexSecretsSection = withProjectPermission( secrets. To access individual secrets by name through the SDK and public API, please enable blind indexing.

- + + {(isAllowed) => ( + + )} + ) : (