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) => (
+
+ )}
+
) : (
|