From aac3168c802c4e38ac65446cc7a66ff11542a2c9 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Sat, 19 Aug 2023 15:18:48 +0530 Subject: [PATCH] feat(rbac): implemented granular blocking of actions based on permissions on org level ui --- .../controllers/v1/organizationController.ts | 2 +- .../permissions/OrgPermissionCan.tsx | 50 ++ frontend/src/components/permissions/index.tsx | 1 + .../OrgPermissionContext.tsx | 10 +- .../context/OrgPermissionContext/index.tsx | 6 + .../src/context/OrgPermissionContext/types.ts | 22 +- frontend/src/context/index.tsx | 8 +- frontend/src/hoc/index.tsx | 1 + frontend/src/hoc/withPermission/index.tsx | 1 + .../src/hoc/withPermission/withPermission.tsx | 62 ++ frontend/src/pages/org/[id]/billing/index.tsx | 35 +- .../src/pages/org/[id]/overview/index.tsx | 622 +++++++++--------- .../pages/org/[id]/secret-scanning/index.tsx | 176 +++-- .../src/views/Org/MembersPage/MembersPage.tsx | 82 +-- .../OrgMembersTable/OrgMembersTable.tsx | 175 +++-- .../BillingPermission.tsx | 10 +- .../IncidentContactPermission.tsx | 14 +- .../OrgRoleModifySection/MemberPermission.tsx | 17 +- .../OrgRoleModifySection/RolePermission.tsx | 21 +- .../SecretScanningPermission.tsx | 13 +- .../SettingsPermission.tsx | 13 +- .../OrgRoleModifySection/SsoPermission.tsx | 13 +- .../WorkspacePermission.tsx | 10 +- .../components/RiskStatusSelection.tsx | 65 +- .../BillingCloudTab/PreviewSection.tsx | 251 +++---- .../BillingDetailsTab/CompanyNameSection.tsx | 168 +++-- .../BillingDetailsTab/InvoiceEmailSection.tsx | 162 +++-- .../BillingDetailsTab/PmtMethodsSection.tsx | 77 +-- .../BillingDetailsTab/PmtMethodsTable.tsx | 31 +- .../BillingDetailsTab/TaxIDSection.tsx | 58 +- .../BillingDetailsTab/TaxIDTable.tsx | 31 +- .../BillingTabGroup/BillingTabGroup.tsx | 84 +-- .../components/OrgAuthTab/OrgAuthTab.tsx | 16 +- .../components/OrgAuthTab/OrgSSOSection.tsx | 248 +++---- .../OrgGeneralTab/OrgGeneralTab.tsx | 18 +- .../AddOrgIncidentContactModal.tsx | 173 +++-- .../OrgIncidentContactsSection.tsx | 65 +- .../OrgIncidentContactsTable.tsx | 23 +- .../OrgNameChangeSection.tsx | 112 ++-- .../OrgServiceAccountsTable.tsx | 593 +++++++++-------- .../components/OrgTabGroup/OrgTabGroup.tsx | 89 +-- 41 files changed, 1984 insertions(+), 1644 deletions(-) create mode 100644 frontend/src/components/permissions/OrgPermissionCan.tsx create mode 100644 frontend/src/components/permissions/index.tsx create mode 100644 frontend/src/hoc/index.tsx create mode 100644 frontend/src/hoc/withPermission/index.tsx create mode 100644 frontend/src/hoc/withPermission/withPermission.tsx diff --git a/backend/src/controllers/v1/organizationController.ts b/backend/src/controllers/v1/organizationController.ts index f34d6eec..4bad1db2 100644 --- a/backend/src/controllers/v1/organizationController.ts +++ b/backend/src/controllers/v1/organizationController.ts @@ -292,7 +292,7 @@ export const createOrganizationPortalSession = async (req: Request, res: Respons const { permission } = await getUserOrgPermissions(req.user._id, organizationId); ForbiddenError.from(permission).throwUnlessCan( - GeneralPermissionActions.Create, + GeneralPermissionActions.Edit, OrgPermissionSubjects.Billing ); diff --git a/frontend/src/components/permissions/OrgPermissionCan.tsx b/frontend/src/components/permissions/OrgPermissionCan.tsx new file mode 100644 index 00000000..0bfb44f7 --- /dev/null +++ b/frontend/src/components/permissions/OrgPermissionCan.tsx @@ -0,0 +1,50 @@ +import { FunctionComponent, ReactNode } from "react"; +import { BoundCanProps, Can } from "@casl/react"; + +import { + OrgPermissionSubjects, + OrgWorkspacePermissionActions, + TOrgPermission, + useOrgPermission +} from "@app/context/OrgPermissionContext"; + +import { Tooltip } from "../v2"; + +type Props = { + label?: ReactNode; +} & BoundCanProps; + +export const OrgPermissionCan: FunctionComponent = ({ + label = "Permission Denied. Kindly contact your org admin", + children, + passThrough = true, + ...props +}) => { + const permission = useOrgPermission(); + + return ( + + {(isAllowed, ability) => { + // akhilmhdh: This is set as type due to error in casl react type. + const finalChild = + typeof children === "function" + ? children(isAllowed, ability as TOrgPermission) + : children; + + if (!isAllowed && passThrough) { + return {finalChild}; + } + + if (!isAllowed) return null; + + return finalChild; + }} + + ); +}; diff --git a/frontend/src/components/permissions/index.tsx b/frontend/src/components/permissions/index.tsx new file mode 100644 index 00000000..e86fa431 --- /dev/null +++ b/frontend/src/components/permissions/index.tsx @@ -0,0 +1 @@ +export { OrgPermissionCan } from "./OrgPermissionCan"; diff --git a/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx b/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx index 4d968c06..5320ece6 100644 --- a/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx +++ b/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx @@ -3,13 +3,13 @@ import { createContext, ReactNode, useContext } from "react"; import { useGetUserOrgPermissions } from "@app/hooks/api"; import { useOrganization } from "../OrganizationContext"; -import { TPermission } from "./types"; +import { TOrgPermission } from "./types"; type Props = { children: ReactNode; }; -const PermissionContext = createContext(null); +const OrgPermissionContext = createContext(null); export const OrgPermissionProvider = ({ children }: Props): JSX.Element => { const { currentOrg } = useOrganization(); @@ -37,11 +37,13 @@ export const OrgPermissionProvider = ({ children }: Props): JSX.Element => { ); } - return {children}; + return ( + {children} + ); }; export const useOrgPermission = () => { - const ctx = useContext(PermissionContext); + const ctx = useContext(OrgPermissionContext); if (!ctx) { throw new Error("useOrgPermission to be used within "); } diff --git a/frontend/src/context/OrgPermissionContext/index.tsx b/frontend/src/context/OrgPermissionContext/index.tsx index 73cfe0a1..336777d6 100644 --- a/frontend/src/context/OrgPermissionContext/index.tsx +++ b/frontend/src/context/OrgPermissionContext/index.tsx @@ -1 +1,7 @@ export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext"; +export type { TOrgPermission } from "./types"; +export { + OrgGeneralPermissionActions, + OrgPermissionSubjects, + OrgWorkspacePermissionActions +} from "./types"; diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index 29e08383..d385981d 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -1,13 +1,13 @@ import { MongoAbility } from "@casl/ability"; -export enum GeneralPermissionActions { +export enum OrgGeneralPermissionActions { Read = "read", Create = "create", Edit = "edit", Delete = "delete" } -export enum WorkspacePermissionActions { +export enum OrgWorkspacePermissionActions { Read = "read", Create = "create" } @@ -24,13 +24,13 @@ export enum OrgPermissionSubjects { } export type OrgPermissionSet = - | [WorkspacePermissionActions, OrgPermissionSubjects.Workspace] - | [GeneralPermissionActions, OrgPermissionSubjects.Role] - | [GeneralPermissionActions, OrgPermissionSubjects.Member] - | [GeneralPermissionActions, OrgPermissionSubjects.Settings] - | [GeneralPermissionActions, OrgPermissionSubjects.IncidentAccount] - | [GeneralPermissionActions, OrgPermissionSubjects.Sso] - | [GeneralPermissionActions, OrgPermissionSubjects.SecretScanning] - | [GeneralPermissionActions, OrgPermissionSubjects.Billing]; + | [OrgWorkspacePermissionActions, OrgPermissionSubjects.Workspace] + | [OrgGeneralPermissionActions, OrgPermissionSubjects.Role] + | [OrgGeneralPermissionActions, OrgPermissionSubjects.Member] + | [OrgGeneralPermissionActions, OrgPermissionSubjects.Settings] + | [OrgGeneralPermissionActions, OrgPermissionSubjects.IncidentAccount] + | [OrgGeneralPermissionActions, OrgPermissionSubjects.Sso] + | [OrgGeneralPermissionActions, OrgPermissionSubjects.SecretScanning] + | [OrgGeneralPermissionActions, OrgPermissionSubjects.Billing]; -export type TPermission = MongoAbility; +export type TOrgPermission = MongoAbility; diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx index 097703d1..b0279f1f 100644 --- a/frontend/src/context/index.tsx +++ b/frontend/src/context/index.tsx @@ -1,6 +1,12 @@ export { AuthProvider } from "./AuthContext"; export { OrgProvider, useOrganization } from "./OrganizationContext"; -export { OrgPermissionProvider,useOrgPermission } from "./OrgPermissionContext"; +export type { TOrgPermission } from "./OrgPermissionContext"; +export { + OrgGeneralPermissionActions, + OrgPermissionSubjects, + OrgWorkspacePermissionActions +} from "./OrgPermissionContext"; +export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext"; export { SubscriptionProvider, useSubscription } from "./SubscriptionContext"; export { UserProvider, useUser } from "./UserContext"; export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext"; diff --git a/frontend/src/hoc/index.tsx b/frontend/src/hoc/index.tsx new file mode 100644 index 00000000..d5dfb8dc --- /dev/null +++ b/frontend/src/hoc/index.tsx @@ -0,0 +1 @@ +export { withPermission } from "./withPermission"; diff --git a/frontend/src/hoc/withPermission/index.tsx b/frontend/src/hoc/withPermission/index.tsx new file mode 100644 index 00000000..d5dfb8dc --- /dev/null +++ b/frontend/src/hoc/withPermission/index.tsx @@ -0,0 +1 @@ +export { withPermission } from "./withPermission"; diff --git a/frontend/src/hoc/withPermission/withPermission.tsx b/frontend/src/hoc/withPermission/withPermission.tsx new file mode 100644 index 00000000..520ccff2 --- /dev/null +++ b/frontend/src/hoc/withPermission/withPermission.tsx @@ -0,0 +1,62 @@ +import { ComponentType } from "react"; +import { Abilities, AbilityTuple, Generics, SubjectType } from "@casl/ability"; +import { faLock } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; + +import { TOrgPermission, useOrgPermission } from "@app/context"; + +type Props = (T extends AbilityTuple + ? { + action: T[0]; + subject: Extract; + } + : { + action: string; + subject: string; + }) & { className?: string; containerClassName?: string }; + +export const withPermission = ( + Component: ComponentType, + { action, subject, className, containerClassName }: Props["abilities"]> +) => { + const HOC = (hocProps: T) => { + const permission = useOrgPermission(); + + // akhilmhdh: Set as any due to casl/react ts type bug + // REASON: casl due to its type checking can't seem to union even if union intersection is applied + if (permission.cannot(action as any, subject)) { + return ( +
+
+
+ +
+
+
Permission Denied
+
+ You do not have permission to this page.
Kindly contact your organization + administrator +
+
+
+
+ ); + } + + return ; + }; + + HOC.displayName = "WithPermission"; + return HOC; +}; diff --git a/frontend/src/pages/org/[id]/billing/index.tsx b/frontend/src/pages/org/[id]/billing/index.tsx index a752d93f..92585261 100644 --- a/frontend/src/pages/org/[id]/billing/index.tsx +++ b/frontend/src/pages/org/[id]/billing/index.tsx @@ -1,21 +1,28 @@ import { useTranslation } from "react-i18next"; import Head from "next/head"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, TOrgPermission } from "@app/context"; +import { withPermission } from "@app/hoc"; import { BillingSettingsPage } from "@app/views/Settings/BillingSettingsPage"; -export default function SettingsBilling() { - const { t } = useTranslation(); +const SettingsBilling = withPermission<{}, TOrgPermission>( + () => { + const { t } = useTranslation(); - return ( -
- - {t("common.head-title", { title: t("billing.title") })} - - - - -
- ); -} + return ( +
+ + {t("common.head-title", { title: t("billing.title") })} + + + + +
+ ); + }, + { action: OrgGeneralPermissionActions.Delete, subject: OrgPermissionSubjects.Billing } +); -SettingsBilling.requireAuth = true; \ No newline at end of file +Object.assign(SettingsBilling, { requireAuth: true }); + +export default SettingsBilling; diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx index 8a8d832c..e5323557 100644 --- a/frontend/src/pages/org/[id]/overview/index.tsx +++ b/frontend/src/pages/org/[id]/overview/index.tsx @@ -31,6 +31,7 @@ import * as Tabs from "@radix-ui/react-tabs"; import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { OrgPermissionCan } from "@app/components/permissions"; import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck"; import { Button, @@ -42,9 +43,22 @@ import { Skeleton, UpgradePlanModal } from "@app/components/v2"; -import { useSubscription, useUser, useWorkspace } from "@app/context"; -import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useRegisterUserAction,useUploadWsKey } from "@app/hooks/api"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; +import { + OrgPermissionSubjects, + OrgWorkspacePermissionActions, + useSubscription, + useUser, + useWorkspace +} from "@app/context"; +import { withPermission } from "@app/hoc"; +import { + fetchOrgUsers, + useAddUserToWs, + useCreateWorkspace, + useRegisterUserAction, + useUploadWsKey +} from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto"; @@ -301,9 +315,7 @@ const LearningItem = ({ tabIndex={0} onClick={async () => { if (userAction && userAction !== "first_time_secrets_pushed") { - await registerUserAction.mutateAsync( - userAction - ); + await registerUserAction.mutateAsync(userAction); } }} className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${ @@ -446,19 +458,20 @@ type TAddProjectFormData = yup.InferType; // #TODO: Update all the workspaceIds -export default function Organization() { - const { t } = useTranslation(); +const OrganizationPage = withPermission( + () => { + const { t } = useTranslation(); - const router = useRouter(); + const router = useRouter(); - const { workspaces, isLoading: isWorkspaceLoading } = useWorkspace(); - const orgWorkspaces = - workspaces?.filter( - (workspace) => workspace.organization === localStorage.getItem("orgData.id") - ) || []; - const currentOrg = String(router.query.id); - const { createNotification } = useNotificationContext(); - const addWsUser = useAddUserToWs(); + const { workspaces, isLoading: isWorkspaceLoading } = useWorkspace(); + const orgWorkspaces = + workspaces?.filter( + (workspace) => workspace.organization === localStorage.getItem("orgData.id") + ) || []; + const currentOrg = String(router.query.id); + const { createNotification } = useNotificationContext(); + const addWsUser = useAddUserToWs(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "addNewWs", @@ -537,24 +550,24 @@ export default function Organization() { ? subscription.workspacesUsed < subscription.workspaceLimit : true; - useEffect(() => { - onboardingCheck({ - setHasUserClickedIntro, - setHasUserClickedSlack, - setHasUserPushedSecrets, - setUsersInOrg - }); - }, []); + useEffect(() => { + onboardingCheck({ + setHasUserClickedIntro, + setHasUserClickedSlack, + setHasUserPushedSecrets, + setUsersInOrg + }); + }, []); - const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0; + const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0; - return ( -
- - {t("common.head-title", { title: t("settings.members.title") })} - - - {!serverDetails?.redisConfigured &&
+ return ( +
+ + {t("common.head-title", { title: t("settings.members.title") })} + + + {!serverDetails?.redisConfigured &&

Announcements

@@ -566,290 +579,309 @@ export default function Organization() { .
} -
-

Projects

-
- setSearchFilter(e.target.value)} - leftIcon={} - /> - -
-
- {isWorkspaceLoading && - Array.apply(0, Array(3)).map((_x, i) => ( -
-
- -
-
- -
-
- -
-
- ))} - {orgWorkspaces - .filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())) - .map((workspace) => ( -
-
{workspace.name}
-
- {workspace.environments?.length || 0} environments -
- -
- ))} -
- {isWorkspaceEmpty && ( -
- -
- You are not part of any projects in this organization yet. When you are, they will - appear here. -
-
- Create a new project, or ask other organization members to give you necessary - permissions. -
-
- )} -
- {new Date().getTime() - new Date(user?.createdAt).getTime() < 30 * 24 * 60 * 60 * 1000 && ( -
-

Onboarding Guide

-
- - {orgWorkspaces.length !== 0 && ( - <> - - - - )} -
- -
+ Add New Project + + )} +
- {orgWorkspaces.length !== 0 && ( -
-
-
- - {false && ( -
- -
- )} -
-
Inject secrets locally
-
- Replace .env files with a more secure and efficient alternative. -
+
+ {isWorkspaceLoading && + Array.apply(0, Array(3)).map((_x, i) => ( +
+
+ +
+
+ +
+
+
+ ))} + {orgWorkspaces + .filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())) + .map((workspace) => (
- About 2 min +
{workspace.name}
+
+ {workspace.environments?.length || 0} environments +
+
+ ))} +
+ {isWorkspaceEmpty && ( +
+ +
+ You are not part of any projects in this organization yet. When you are, they will + appear here. +
+
+ Create a new project, or ask other organization members to give you necessary + permissions.
- - {false &&
}
)} - {orgWorkspaces.length !== 0 && ( - - )}
- )} -
-

Explore More

-
- {features.map((feature) => ( -
-
{feature.name}
-
- {feature.description} -
-
-
Setup time: 20 min
- - Learn more{" "} - +

Onboarding Guide

+
- ))} + {orgWorkspaces.length !== 0 && ( +
+
+
+ + {false && ( +
+ +
+ )} +
+
Inject secrets locally
+
+ Replace .env files with a more secure and efficient alternative. +
+
+
+
+ About 2 min +
+
+ + {false &&
} +
+ )} + {orgWorkspaces.length !== 0 && ( + + )} +
+ )} +
+

Explore More

+
+ {features.map((feature) => ( +
+
{feature.name}
+
+ {feature.description} +
+
+
+ Setup time: 20 min +
+ + Learn more{" "} + + +
+
+ ))} +
-
- { - handlePopUpToggle("addNewWs", isModalOpen); - reset(); - }} - > - { + handlePopUpToggle("addNewWs", isModalOpen); + reset(); + }} > -
- ( - - - - )} - /> -
+ + ( - ( + - Add all members of my organization to this project - + + )} /> -
-
- - -
- -
-
- handlePopUpToggle("upgradePlan", isOpen)} - text="You have exceeded the number of projects allowed on the free plan." - /> - {/* */} -
- ); -} +
+ ( + + Add all members of my organization to this project + + )} + /> +
+
+ + +
+ + + + handlePopUpToggle("upgradePlan", isOpen)} + text="You have exceeded the number of projects allowed on the free plan." + /> + {/* */} +
+ ); + }, + { + action: OrgWorkspacePermissionActions.Read, + subject: OrgPermissionSubjects.Workspace + } +); + +Object.assign(OrganizationPage, { requireAuth: true }); -Organization.requireAuth = true; +export default OrganizationPage; diff --git a/frontend/src/pages/org/[id]/secret-scanning/index.tsx b/frontend/src/pages/org/[id]/secret-scanning/index.tsx index e0f98243..626333e4 100644 --- a/frontend/src/pages/org/[id]/secret-scanning/index.tsx +++ b/frontend/src/pages/org/[id]/secret-scanning/index.tsx @@ -1,89 +1,133 @@ import { useEffect, useState } from "react"; import Head from "next/head"; -import { useRouter } from "next/router" +import { useRouter } from "next/router"; +import { OrgPermissionCan } from "@app/components/permissions"; import { Button } from "@app/components/v2"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { withPermission } from "@app/hoc"; import { SecretScanningLogsTable } from "@app/views/SecretScanning/components"; import createNewIntegrationSession from "../../../api/secret-scanning/createSecretScanningSession"; import getInstallationStatus from "../../../api/secret-scanning/getInstallationStatus"; import linkGitAppInstallationWithOrganization from "../../../api/secret-scanning/linkGitAppInstallationWithOrganization"; -export default function SecretScanning() { - const router = useRouter() - const queryParams = router.query - const [integrationEnabled, setIntegrationStatus] = useState(false) +const SecretScanning = withPermission( + () => { + const router = useRouter(); + const queryParams = router.query; + const [integrationEnabled, setIntegrationStatus] = useState(false); - useEffect(()=>{ - const linkInstallation = async () => { - if (typeof queryParams.state === "string" && typeof queryParams.installation_id === "string"){ - try { - const isLinked = await linkGitAppInstallationWithOrganization(queryParams.installation_id as string, queryParams.state as string) - if (isLinked){ - router.reload() + useEffect(() => { + const linkInstallation = async () => { + if ( + typeof queryParams.state === "string" && + typeof queryParams.installation_id === "string" + ) { + try { + const isLinked = await linkGitAppInstallationWithOrganization( + queryParams.installation_id as string, + queryParams.state as string + ); + if (isLinked) { + router.reload(); + } + + console.log("installation verification complete"); + } catch (e) { + console.log("app installation is stale, start new session", e); } - - console.log("installation verification complete") - }catch (e){ - console.log("app installation is stale, start new session", e) } - } - } + }; - const fetchInstallationStatus = async () => { - const status = await getInstallationStatus(String(localStorage.getItem("orgData.id"))) - setIntegrationStatus(status) - } + const fetchInstallationStatus = async () => { + const status = await getInstallationStatus(String(localStorage.getItem("orgData.id"))); + setIntegrationStatus(status); + }; - fetchInstallationStatus() - linkInstallation() - },[queryParams.state, queryParams.installation_id]) + fetchInstallationStatus(); + linkInstallation(); + }, [queryParams.state, queryParams.installation_id]); - const generateNewIntegrationSession = async () => { - const session = await createNewIntegrationSession(String(localStorage.getItem("orgData.id"))) - router.push(`https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`) - } + const generateNewIntegrationSession = async () => { + const session = await createNewIntegrationSession(String(localStorage.getItem("orgData.id"))); + router.push( + `https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}` + ); + }; - return ( -
- - Secret scanning - - - -
-
-
Secret Scanning
-
Automatically monitor your GitHub activity and prevent secret leaks
-
-
-
Secret Scanning Status: {integrationEnabled ?

Enabled

:

Not enabled

}
-
{integrationEnabled ?

Your GitHub organization is connected to Infisical, and is being continuously monitored for secret leaks.

:

Connect your GitHub organization to Infisical.

}
+ return ( +
+ + Secret scanning + + + +
+
+
Secret Scanning
+
+ Automatically monitor your GitHub activity and prevent secret leaks
- {integrationEnabled ? ( -
-
-
-
-
- ) : ( -
- +
+
+
+ Secret Scanning Status:{" "} + {integrationEnabled ? ( +

Enabled

+ ) : ( +

Not enabled

+ )} +
+
+ {integrationEnabled ? ( +

+ Your GitHub organization is connected to Infisical, and is being continuously + monitored for secret leaks. +

+ ) : ( +

+ Connect your GitHub organization to Infisical. +

+ )} +
- )} + {integrationEnabled ? ( +
+
+
+
+
+ ) : ( +
+ + {(isAllowed) => ( + + )} + +
+ )} +
+
-
-
- ); -} + ); + }, + { action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.SecretScanning } +); + +Object.assign(SecretScanning, { requireAuth: true }); -SecretScanning.requireAuth = true; +export default SecretScanning; diff --git a/frontend/src/views/Org/MembersPage/MembersPage.tsx b/frontend/src/views/Org/MembersPage/MembersPage.tsx index 065825b4..d2c213dc 100644 --- a/frontend/src/views/Org/MembersPage/MembersPage.tsx +++ b/frontend/src/views/Org/MembersPage/MembersPage.tsx @@ -3,7 +3,8 @@ import { useTranslation } from "react-i18next"; import { motion } from "framer-motion"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; -import { useOrganization } from "@app/context"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { withPermission } from "@app/hoc"; import { useGetRoles } from "@app/hooks/api"; import { OrgMembersTable } from "./components/OrgMembersTable"; @@ -14,45 +15,48 @@ enum TabSections { Roles = "roles" } -export const MembersPage = () => { - const { t } = useTranslation(); - const { currentOrg } = useOrganization(); +export const MembersPage = withPermission( + () => { + const { t } = useTranslation(); + const { currentOrg } = useOrganization(); - const orgId = currentOrg?._id || ""; + const orgId = currentOrg?._id || ""; - const { data: roles } = useGetRoles({ - orgId - }); + const { data: roles } = useGetRoles({ + orgId + }); - return ( -
-
-

- {t("section.members.org-members")} -

- - - Members - {process.env.NEXT_PUBLIC_NEW_PERMISSION_FLAG === "true" && ( - Roles - )} - - - - - - - - - - + return ( +
+
+

+ {t("section.members.org-members")} +

+ + + Members + {process.env.NEXT_PUBLIC_NEW_PERMISSION_FLAG === "true" && ( + Roles + )} + + + + + + + + + + +
-
- ); -}; + ); + }, + { action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.Member } +); diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx index e13a2ca0..2cdb0036 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx @@ -14,6 +14,7 @@ import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { OrgPermissionCan } from "@app/components/permissions"; import { decryptAssymmetric, encryptAssymmetric @@ -41,7 +42,14 @@ import { Tr, UpgradePlanModal } from "@app/components/v2"; -import { useOrganization, useSubscription, useUser, useWorkspace } from "@app/context"; +import { + OrgGeneralPermissionActions, + OrgPermissionSubjects, + useOrganization, + useSubscription, + useUser, + useWorkspace +} from "@app/context"; import { usePopUp, useToggle } from "@app/hooks"; import { useAddUserToOrg, @@ -297,27 +305,32 @@ export const OrgMembersTable = ({ roles = [] }: Props) => { placeholder="Search members..." />
- + + {(isAllowed) => ( + + )} +
@@ -345,48 +358,59 @@ export const OrgMembersTable = ({ roles = [] }: Props) => { {name} {email} - {status === "accepted" && ( - - )} - {(status === "invited" || status === "verified") && - serverDetails?.emailConfigured && ( - + + {(isAllowed) => ( + <> + {status === "accepted" && ( + + )} + {(status === "invited" || status === "verified") && + serverDetails?.emailConfigured && ( + + )} + {status === "completed" && ( + + )} + )} - {status === "completed" && ( - - )} + {userWs ? ( @@ -428,16 +452,23 @@ export const OrgMembersTable = ({ roles = [] }: Props) => { {userId !== u?._id && ( - - handlePopUpOpen("removeMember", { id: orgMembershipId }) - } + - - + {(isAllowed) => ( + + handlePopUpOpen("removeMember", { id: orgMembershipId }) + } + > + + + )} + )} diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/BillingPermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/BillingPermission.tsx index 6fbeac9f..5d77d63e 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/BillingPermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/BillingPermission.tsx @@ -38,10 +38,9 @@ export const BillingPermission = ({ isNonEditable, setValue, control }: Props) = const [isCustom, setIsCustom] = useToggle(); const selectedPermissionCategory = useMemo(() => { - let score = 0; const actions = Object.keys(rule || {}) as Array; const totalActions = PERMISSIONS.length; - actions.forEach((key) => (score += rule[key] ? 1 : 0)); + const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); if (isCustom) return Permission.Custom; if (score === 0) return Permission.NoAccess; @@ -52,11 +51,14 @@ export const BillingPermission = ({ isNonEditable, setValue, control }: Props) = }, [rule, isCustom]); useEffect(() => { - selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); }, [selectedPermissionCategory]); const handlePermissionChange = (val: Permission) => { - val === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (val === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + switch (val) { case Permission.NoAccess: setValue( diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/IncidentContactPermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/IncidentContactPermission.tsx index 2d92f510..14fffb76 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/IncidentContactPermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/IncidentContactPermission.tsx @@ -38,10 +38,9 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }: const [isCustom, setIsCustom] = useToggle(); const selectedPermissionCategory = useMemo(() => { - let score = 0; const actions = Object.keys(rule || {}) as Array; const totalActions = PERMISSIONS.length; - actions.forEach((key) => (score += rule[key] ? 1 : 0)); + const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); if (isCustom) return Permission.Custom; if (score === 0) return Permission.NoAccess; @@ -52,14 +51,16 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }: }, [rule, isCustom]); useEffect(() => { - selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); }, [selectedPermissionCategory]); const handlePermissionChange = (val: Permission) => { - val === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (val === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + switch (val) { case Permission.NoAccess: - setIsCustom.off(); setValue( "permissions.incident-contact", { read: false, edit: false, create: false, delete: false }, @@ -67,7 +68,6 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }: ); break; case Permission.FullAccess: - setIsCustom.off(); setValue( "permissions.incident-contact", { read: true, edit: true, create: true, delete: true }, @@ -75,7 +75,6 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }: ); break; case Permission.ReadOnly: - setIsCustom.off(); setValue( "permissions.incident-contact", { read: true, edit: false, create: false, delete: false }, @@ -83,7 +82,6 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }: ); break; default: - setIsCustom.on(); setValue( "permissions.incident-contact", { read: false, edit: false, create: false, delete: false }, diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/MemberPermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/MemberPermission.tsx index f924e52d..de758b48 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/MemberPermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/MemberPermission.tsx @@ -31,31 +31,34 @@ const PERMISSIONS = [ ] as const; export const MemberPermission = ({ isNonEditable, setValue, control }: Props) => { - const memberRule = useWatch({ + const rule = useWatch({ control, name: "permissions.member" }); const [isCustom, setIsCustom] = useToggle(); const selectedPermissionCategory = useMemo(() => { - let score = 0; - const actions = Object.keys(memberRule || {}) as Array; + const actions = Object.keys(rule || {}) as Array; const totalActions = PERMISSIONS.length; - actions.forEach((key) => (score += memberRule[key] ? 1 : 0)); + const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); if (isCustom) return Permission.Custom; if (score === 0) return Permission.NoAccess; if (score === totalActions) return Permission.FullAccess; - if (score === 1 && memberRule.read) return Permission.ReadOnly; + if (score === 1 && rule.read) return Permission.ReadOnly; return Permission.Custom; - }, [memberRule, isCustom]); + }, [rule, isCustom]); useEffect(() => { - selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); }, [selectedPermissionCategory]); const handlePermissionChange = (val: Permission) => { + if (val === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + switch (val) { case Permission.NoAccess: setValue( diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/RolePermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/RolePermission.tsx index 342d8773..c7700b20 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/RolePermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/RolePermission.tsx @@ -31,34 +31,36 @@ const PERMISSIONS = [ ] as const; export const RolePermission = ({ isNonEditable, setValue, control }: Props) => { - const roleRule = useWatch({ + const rule = useWatch({ control, name: "permissions.role" }); const [isCustom, setIsCustom] = useToggle(); const selectedPermissionCategory = useMemo(() => { - let score = 0; - const actions = Object.keys(roleRule || {}) as Array; + const actions = Object.keys(rule || {}) as Array; const totalActions = PERMISSIONS.length; - actions.forEach((key) => (score += roleRule[key] ? 1 : 0)); + const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); if (isCustom) return Permission.Custom; if (score === 0) return Permission.NoAccess; if (score === totalActions) return Permission.FullAccess; - if (score === 1 && roleRule.read) return Permission.ReadOnly; + if (score === 1 && rule.read) return Permission.ReadOnly; return Permission.Custom; - }, [roleRule, isCustom]); + }, [rule, isCustom]); useEffect(() => { - selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); }, [selectedPermissionCategory]); const handlePermissionChange = (val: Permission) => { + if (val === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + switch (val) { case Permission.NoAccess: - setIsCustom.off(); setValue( "permissions.role", { read: false, edit: false, create: false, delete: false }, @@ -66,7 +68,6 @@ export const RolePermission = ({ isNonEditable, setValue, control }: Props) => { ); break; case Permission.FullAccess: - setIsCustom.off(); setValue( "permissions.role", { read: true, edit: true, create: true, delete: true }, @@ -74,7 +75,6 @@ export const RolePermission = ({ isNonEditable, setValue, control }: Props) => { ); break; case Permission.ReadOnly: - setIsCustom.off(); setValue( "permissions.role", { read: true, edit: false, create: false, delete: false }, @@ -82,7 +82,6 @@ export const RolePermission = ({ isNonEditable, setValue, control }: Props) => { ); break; default: - setIsCustom.on(); setValue( "permissions.role", { read: false, edit: false, create: false, delete: false }, diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SecretScanningPermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SecretScanningPermission.tsx index 779ea51b..37b59b6b 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SecretScanningPermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SecretScanningPermission.tsx @@ -38,10 +38,9 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr const [isCustom, setIsCustom] = useToggle(); const selectedPermissionCategory = useMemo(() => { - let score = 0; const actions = Object.keys(rule || {}) as Array; const totalActions = PERMISSIONS.length; - actions.forEach((key) => (score += rule[key] ? 1 : 0)); + const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); if (isCustom) return Permission.Custom; if (score === 0) return Permission.NoAccess; @@ -52,13 +51,16 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr }, [rule, isCustom]); useEffect(() => { - selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); }, [selectedPermissionCategory]); const handlePermissionChange = (val: Permission) => { + if (val === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + switch (val) { case Permission.NoAccess: - setIsCustom.off(); setValue( "permissions.secret-scanning", { read: false, edit: false, create: false, delete: false }, @@ -66,7 +68,6 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr ); break; case Permission.FullAccess: - setIsCustom.off(); setValue( "permissions.secret-scanning", { read: true, edit: true, create: true, delete: true }, @@ -74,7 +75,6 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr ); break; case Permission.ReadOnly: - setIsCustom.off(); setValue( "permissions.secret-scanning", { read: true, edit: false, create: false, delete: false }, @@ -82,7 +82,6 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr ); break; default: - setIsCustom.on(); setValue( "permissions.secret-scanning", { read: false, edit: false, create: false, delete: false }, diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SettingsPermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SettingsPermission.tsx index 0bfc821e..c574901f 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SettingsPermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SettingsPermission.tsx @@ -38,10 +38,9 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props) const [isCustom, setIsCustom] = useToggle(); const selectedPermissionCategory = useMemo(() => { - let score = 0; const actions = Object.keys(rule || {}) as Array; const totalActions = PERMISSIONS.length; - actions.forEach((key) => (score += rule[key] ? 1 : 0)); + const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); if (isCustom) return Permission.Custom; if (score === 0) return Permission.NoAccess; @@ -52,13 +51,16 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props) }, [rule, isCustom]); useEffect(() => { - selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); }, [selectedPermissionCategory]); const handlePermissionChange = (val: Permission) => { + if (val === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + switch (val) { case Permission.NoAccess: - setIsCustom.off(); setValue( "permissions.settings", { read: false, edit: false, create: false, delete: false }, @@ -66,7 +68,6 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props) ); break; case Permission.FullAccess: - setIsCustom.off(); setValue( "permissions.settings", { read: true, edit: true, create: true, delete: true }, @@ -74,7 +75,6 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props) ); break; case Permission.ReadOnly: - setIsCustom.off(); setValue( "permissions.settings", { read: true, edit: false, create: false, delete: false }, @@ -82,7 +82,6 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props) ); break; default: - setIsCustom.on(); setValue( "permissions.settings", { read: false, edit: false, create: false, delete: false }, diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SsoPermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SsoPermission.tsx index 25ae2438..3d6493fe 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SsoPermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/SsoPermission.tsx @@ -38,10 +38,9 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => { const [isCustom, setIsCustom] = useToggle(); const selectedPermissionCategory = useMemo(() => { - let score = 0; const actions = Object.keys(rule || {}) as Array; const totalActions = PERMISSIONS.length; - actions.forEach((key) => (score += rule[key] ? 1 : 0)); + const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); if (isCustom) return Permission.Custom; if (score === 0) return Permission.NoAccess; @@ -52,13 +51,16 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => { }, [rule, isCustom]); useEffect(() => { - selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); }, [selectedPermissionCategory]); const handlePermissionChange = (val: Permission) => { + if (val === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + switch (val) { case Permission.NoAccess: - setIsCustom.off(); setValue( "permissions.sso", { read: false, edit: false, create: false, delete: false }, @@ -66,7 +68,6 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => { ); break; case Permission.FullAccess: - setIsCustom.off(); setValue( "permissions.sso", { read: true, edit: true, create: true, delete: true }, @@ -74,7 +75,6 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => { ); break; case Permission.ReadOnly: - setIsCustom.off(); setValue( "permissions.sso", { read: true, edit: false, create: false, delete: false }, @@ -82,7 +82,6 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => { ); break; default: - setIsCustom.on(); setValue( "permissions.sso", { read: false, edit: false, create: false, delete: false }, diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/WorkspacePermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/WorkspacePermission.tsx index b8fcf4b6..8e1abc87 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/WorkspacePermission.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/WorkspacePermission.tsx @@ -36,10 +36,9 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props) const [isCustom, setIsCustom] = useToggle(); const selectedPermissionCategory = useMemo(() => { - let score = 0; const actions = Object.keys(rule || {}) as Array; const totalActions = PERMISSIONS.length; - actions.forEach((key) => (score += rule[key] ? 1 : 0)); + const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); if (isCustom) return Permission.Custom; if (score === 0) return Permission.NoAccess; @@ -50,11 +49,14 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props) }, [rule, isCustom]); useEffect(() => { - selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); }, [selectedPermissionCategory]); const handlePermissionChange = (val: Permission) => { - val === Permission.Custom ? setIsCustom.on() : setIsCustom.off(); + if (val === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + switch (val) { case Permission.NoAccess: setValue("permissions.workspace", { read: false, create: false }, { shouldDirty: true }); diff --git a/frontend/src/views/SecretScanning/components/RiskStatusSelection.tsx b/frontend/src/views/SecretScanning/components/RiskStatusSelection.tsx index 54b66725..e434f653 100644 --- a/frontend/src/views/SecretScanning/components/RiskStatusSelection.tsx +++ b/frontend/src/views/SecretScanning/components/RiskStatusSelection.tsx @@ -1,28 +1,47 @@ import { useEffect, useState } from "react"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context"; import updateRiskStatus, { RiskStatus } from "@app/pages/api/secret-scanning/updateRiskStatus"; -export const RiskStatusSelection = ({riskId, currentSelection}: {riskId: any, currentSelection: any }) => { - const [selectedRiskStatus, setSelectedRiskStatus] = useState(currentSelection); - useEffect(()=>{ - if (currentSelection !== selectedRiskStatus){ - const updateSelection = async () =>{ - await updateRiskStatus(String(localStorage.getItem("orgData.id")), riskId, selectedRiskStatus) - } - updateSelection() - } - },[selectedRiskStatus]) +export const RiskStatusSelection = ({ + riskId, + currentSelection +}: { + riskId: any; + currentSelection: any; +}) => { + const [selectedRiskStatus, setSelectedRiskStatus] = useState(currentSelection); + useEffect(() => { + if (currentSelection !== selectedRiskStatus) { + const updateSelection = async () => { + await updateRiskStatus( + String(localStorage.getItem("orgData.id")), + riskId, + selectedRiskStatus + ); + }; + updateSelection(); + } + }, [selectedRiskStatus]); - return ( - - ); -} \ No newline at end of file + return ( + + {(isAllowed) => ( + + )} + + ); +}; diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx index 9dd75136..fe7b03ac 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx @@ -1,125 +1,148 @@ +import { OrgPermissionCan } from "@app/components/permissions"; import { Button } from "@app/components/v2"; -import { useOrganization,useSubscription } from "@app/context"; -import { - useCreateCustomerPortalSession, - useGetOrgPlanBillingInfo, - useGetOrgTrialUrl +import { + OrgGeneralPermissionActions, + OrgPermissionSubjects, + useOrganization, + useSubscription +} from "@app/context"; +import { + useCreateCustomerPortalSession, + useGetOrgPlanBillingInfo, + useGetOrgTrialUrl } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { ManagePlansModal } from "./ManagePlansModal"; export const PreviewSection = () => { - const { currentOrg } = useOrganization(); - const { subscription } = useSubscription(); - const { data, isLoading } = useGetOrgPlanBillingInfo(currentOrg?._id ?? ""); - const getOrgTrialUrl = useGetOrgTrialUrl(); - const createCustomerPortalSession = useCreateCustomerPortalSession(); - - const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ - "managePlan" - ] as const); - - const formatAmount = (amount: number) => { - const formattedTotal = (Math.floor(amount) / 100).toLocaleString("en-US", { - style: "currency", - currency: "USD", + const { currentOrg } = useOrganization(); + const { subscription } = useSubscription(); + const { data, isLoading } = useGetOrgPlanBillingInfo(currentOrg?._id ?? ""); + const getOrgTrialUrl = useGetOrgTrialUrl(); + const createCustomerPortalSession = useCreateCustomerPortalSession(); + + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["managePlan"] as const); + + const formatAmount = (amount: number) => { + const formattedTotal = (Math.floor(amount) / 100).toLocaleString("en-US", { + style: "currency", + currency: "USD" + }); + + return formattedTotal; + }; + + const formatDate = (date: number) => { + const createdDate = new Date(date * 1000); + const day: number = createdDate.getDate(); + const month: number = createdDate.getMonth() + 1; + const year: number = createdDate.getFullYear(); + const formattedDate: string = `${day}/${month}/${year}`; + + return formattedDate; + }; + + function formatPlanSlug(slug: string) { + return slug.replace(/(\b[a-z])/g, (match) => match.toUpperCase()).replace(/-/g, " "); + } + + const handleUpgradeBtnClick = async () => { + try { + if (!subscription || !currentOrg) return; + + if (!subscription.has_used_trial) { + // direct user to start pro trial + const url = await getOrgTrialUrl.mutateAsync({ + orgId: currentOrg._id, + success_url: window.location.href }); - - return formattedTotal; - } - - const formatDate = (date: number) => { - const createdDate = new Date(date * 1000); - const day: number = createdDate.getDate(); - const month: number = createdDate.getMonth() + 1; - const year: number = createdDate.getFullYear(); - const formattedDate: string = `${day}/${month}/${year}`; - - return formattedDate; - } - function formatPlanSlug(slug: string) { - return slug - .replace(/(\b[a-z])/g, match => match.toUpperCase()) - .replace(/-/g, " "); - } - - const handleUpgradeBtnClick = async () => { - try { - if (!subscription || !currentOrg) return; - - if (!subscription.has_used_trial) { - // direct user to start pro trial - const url = await getOrgTrialUrl.mutateAsync({ - orgId: currentOrg._id, - success_url: window.location.href - }); - - window.location.href = url; - } else { - // open compare plans modal - handlePopUpOpen("managePlan"); - } - } catch (err) { - console.error(err); - } + window.location.href = url; + } else { + // open compare plans modal + handlePopUpOpen("managePlan"); + } + } catch (err) { + console.error(err); } - - return ( -
- {subscription && subscription?.slug !== "enterprise" && subscription?.slug !== "pro" && subscription?.slug !== "pro-annual" && ( -
-
-

Become Infisical

-

Unlimited members, projects, RBAC, smart alerts, and so much more

-
- -
- )} - {!isLoading && subscription && data && ( -
-
-

Current plan

-

- {`${formatPlanSlug(subscription.slug)} ${subscription.status === "trialing" ? "(Trial)" : ""}`} -

- -
-
-

Price

-

- {subscription.status === "trialing" ? "$0.00 / month" : `${formatAmount(data.amount)} / ${data.interval}`} -

-
-
-

Subscription renews on

-

- {formatDate(data.currentPeriodEnd)} -

-
-
- )} - + }; + + return ( +
+ {subscription && + subscription?.slug !== "enterprise" && + subscription?.slug !== "pro" && + subscription?.slug !== "pro-annual" && ( +
+
+

Become Infisical

+

+ Unlimited members, projects, RBAC, smart alerts, and so much more +

+
+ + {(isAllowed) => ( + + )} + +
+ )} + {!isLoading && subscription && data && ( +
+
+

Current plan

+

+ {`${formatPlanSlug(subscription.slug)} ${ + subscription.status === "trialing" ? "(Trial)" : "" + }`} +

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

Price

+

+ {subscription.status === "trialing" + ? "$0.00 / month" + : `${formatAmount(data.amount)} / ${data.interval}`} +

+
+
+

Subscription renews on

+

+ {formatDate(data.currentPeriodEnd)} +

+
- ); -} \ No newline at end of file + )} + +
+ ); +}; diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/CompanyNameSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/CompanyNameSection.tsx index 0f27757f..a11d053c 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/CompanyNameSection.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/CompanyNameSection.tsx @@ -1,98 +1,92 @@ import { useEffect } from "react"; -import { Controller, useForm } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import { - Button, - FormControl, - Input -} from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { - useGetOrgBillingDetails, - useUpdateOrgBillingDetails -} from "@app/hooks/api"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { Button, FormControl, Input } from "@app/components/v2"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { useGetOrgBillingDetails, useUpdateOrgBillingDetails } from "@app/hooks/api"; -const schema = yup.object({ +const schema = yup + .object({ name: yup.string().required("Company name is required") -}).required(); + }) + .required(); export const CompanyNameSection = () => { - const { createNotification } = useNotificationContext(); - const { currentOrg } = useOrganization(); - const { reset, control, handleSubmit } = useForm({ - defaultValues: { - name: "" - }, - resolver: yupResolver(schema) - }); - const { data } = useGetOrgBillingDetails(currentOrg?._id ?? ""); - const { mutateAsync, isLoading } = useUpdateOrgBillingDetails(); - - useEffect(() => { - if (data) { - reset({ - name: data?.name ?? "" - }); - } - }, [data]); - - const onFormSubmit = async ({ name }: { name: string }) => { - try { - if (!currentOrg?._id) return; - if (name === "") return; - await mutateAsync({ - name, - organizationId: currentOrg._id - }); - - createNotification({ - text: "Successfully updated business name", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update business name", - type: "error" - }); - } + const { createNotification } = useNotificationContext(); + const { currentOrg } = useOrganization(); + const { reset, control, handleSubmit } = useForm({ + defaultValues: { + name: "" + }, + resolver: yupResolver(schema) + }); + const { data } = useGetOrgBillingDetails(currentOrg?._id ?? ""); + const { mutateAsync, isLoading } = useUpdateOrgBillingDetails(); + + useEffect(() => { + if (data) { + reset({ + name: data?.name ?? "" + }); + } + }, [data]); + + const onFormSubmit = async ({ name }: { name: string }) => { + try { + if (!currentOrg?._id) return; + if (name === "") return; + await mutateAsync({ + name, + organizationId: currentOrg._id + }); + + createNotification({ + text: "Successfully updated business name", + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to update business name", + type: "error" + }); } + }; - return ( -
-

- Business name -

-
- ( - - - - )} - control={control} - name="name" - /> -
- -
- ); -} \ No newline at end of file + return ( +
+

Business name

+
+ ( + + + + )} + control={control} + name="name" + /> +
+ + {(isAllowed) => ( + + )} + +
+ ); +}; diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/InvoiceEmailSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/InvoiceEmailSection.tsx index 441cb9cb..10bc836f 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/InvoiceEmailSection.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/InvoiceEmailSection.tsx @@ -1,97 +1,93 @@ import { useEffect } from "react"; -import { Controller, useForm } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import { - Button, - FormControl, - Input} from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { - useGetOrgBillingDetails, - useUpdateOrgBillingDetails -} from "@app/hooks/api"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { Button, FormControl, Input } from "@app/components/v2"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { useGetOrgBillingDetails, useUpdateOrgBillingDetails } from "@app/hooks/api"; -const schema = yup.object({ +const schema = yup + .object({ email: yup.string().required("Email is required") -}).required(); + }) + .required(); export const InvoiceEmailSection = () => { - const { createNotification } = useNotificationContext(); - const { currentOrg } = useOrganization(); - const { reset, control, handleSubmit } = useForm({ - defaultValues: { - email: "" - }, - resolver: yupResolver(schema) - }); - const { data } = useGetOrgBillingDetails(currentOrg?._id ?? ""); - const { mutateAsync, isLoading } = useUpdateOrgBillingDetails(); + const { createNotification } = useNotificationContext(); + const { currentOrg } = useOrganization(); + const { reset, control, handleSubmit } = useForm({ + defaultValues: { + email: "" + }, + resolver: yupResolver(schema) + }); + const { data } = useGetOrgBillingDetails(currentOrg?._id ?? ""); + const { mutateAsync, isLoading } = useUpdateOrgBillingDetails(); - useEffect(() => { - if (data) { - reset({ - email: data?.email ?? "" - }); - } - }, [data]); + useEffect(() => { + if (data) { + reset({ + email: data?.email ?? "" + }); + } + }, [data]); + + const onFormSubmit = async ({ email }: { email: string }) => { + try { + if (!currentOrg?._id) return; + if (email === "") return; - const onFormSubmit = async ({ email }: { email: string }) => { - try { - if (!currentOrg?._id) return; - if (email === "") return; + await mutateAsync({ + email, + organizationId: currentOrg._id + }); - await mutateAsync({ - email, - organizationId: currentOrg._id - }); - - createNotification({ - text: "Successfully updated invoice email recipient", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update invoice email recipient", - type: "error" - }); - } + createNotification({ + text: "Successfully updated invoice email recipient", + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to update invoice email recipient", + type: "error" + }); } + }; - return ( -
-

- Invoice email recipient -

-
- ( - - - - )} - control={control} - name="email" - /> -
- -
- ); -} \ No newline at end of file + return ( +
+

Invoice email recipient

+
+ ( + + + + )} + control={control} + name="email" + /> +
+ + {(isAllowed) => ( + + )} + +
+ ); +}; diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/PmtMethodsSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/PmtMethodsSection.tsx index a124813e..011006db 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/PmtMethodsSection.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/PmtMethodsSection.tsx @@ -1,46 +1,47 @@ - import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - Button -} from "@app/components/v2"; -import { useOrganization } from "@app/context"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { Button } from "@app/components/v2"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { useAddOrgPmtMethod } from "@app/hooks/api"; import { PmtMethodsTable } from "./PmtMethodsTable"; export const PmtMethodsSection = () => { - const { currentOrg } = useOrganization(); - const { mutateAsync, isLoading } = useAddOrgPmtMethod(); - - const handleAddPmtMethodBtnClick = async () => { - if (!currentOrg?._id) return; - const url = await mutateAsync({ - organizationId: currentOrg._id, - success_url: window.location.href, - cancel_url: window.location.href - }); - - window.location.href = url; - } - - return ( -
-
-

- Payment methods -

- -
- -
- ); -} \ No newline at end of file + const { currentOrg } = useOrganization(); + const { mutateAsync, isLoading } = useAddOrgPmtMethod(); + + const handleAddPmtMethodBtnClick = async () => { + if (!currentOrg?._id) return; + const url = await mutateAsync({ + organizationId: currentOrg._id, + success_url: window.location.href, + cancel_url: window.location.href + }); + + window.location.href = url; + }; + + return ( +
+
+

Payment methods

+ + {(isAllowed) => ( + + )} + +
+ +
+ ); +}; diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/PmtMethodsTable.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/PmtMethodsTable.tsx index 17f4fd21..6818dbb6 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/PmtMethodsTable.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/PmtMethodsTable.tsx @@ -1,6 +1,7 @@ import { faCreditCard, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { OrgPermissionCan } from "@app/components/permissions"; import { EmptyState, IconButton, @@ -13,7 +14,7 @@ import { THead, Tr } from "@app/components/v2"; -import { useOrganization } from "@app/context"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { useDeleteOrgPmtMethod, useGetOrgPmtMethods } from "@app/hooks/api"; export const PmtMethodsTable = () => { @@ -52,17 +53,25 @@ export const PmtMethodsTable = () => { {last4} {`${exp_month}/${exp_year}`} - { - await handleDeletePmtMethodBtnClick(_id); - }} - size="lg" - colorSchema="danger" - variant="plain" - ariaLabel="update" + - - + {(isAllowed) => ( + { + await handleDeletePmtMethodBtnClick(_id); + }} + size="lg" + isDisabled={!isAllowed} + colorSchema="danger" + variant="plain" + ariaLabel="update" + > + + + )} + ))} diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/TaxIDSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/TaxIDSection.tsx index 198e02f6..5e388461 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/TaxIDSection.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/TaxIDSection.tsx @@ -1,38 +1,42 @@ - import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { OrgPermissionCan } from "@app/components/permissions"; import { Button } from "@app/components/v2"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context"; import { usePopUp } from "@app/hooks/usePopUp"; import { TaxIDModal } from "./TaxIDModal"; import { TaxIDTable } from "./TaxIDTable"; export const TaxIDSection = () => { - const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ - "addTaxID" - ] as const); + const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ + "addTaxID" + ] as const); - return ( -
-
-

- Tax ID -

- -
- - -
- ); -} \ No newline at end of file + return ( +
+
+

Tax ID

+ + {(isAllowed) => ( + + )} + +
+ + +
+ ); +}; diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/TaxIDTable.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/TaxIDTable.tsx index 2779bb19..9980e468 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/TaxIDTable.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingDetailsTab/TaxIDTable.tsx @@ -1,6 +1,7 @@ import { faFileInvoice, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { OrgPermissionCan } from "@app/components/permissions"; import { EmptyState, IconButton, @@ -13,7 +14,7 @@ import { THead, Tr } from "@app/components/v2"; -import { useOrganization } from "@app/context"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { useDeleteOrgTaxId, useGetOrgTaxIds } from "@app/hooks/api"; const taxIDTypeLabelMap: { [key: string]: string } = { @@ -101,17 +102,25 @@ export const TaxIDTable = () => { {taxIDTypeLabelMap[type]} {value} - { - await handleDeleteTaxIdBtnClick(_id); - }} - size="lg" - colorSchema="danger" - variant="plain" - ariaLabel="update" + - - + {(isAllowed) => ( + { + await handleDeleteTaxIdBtnClick(_id); + }} + size="lg" + colorSchema="danger" + variant="plain" + ariaLabel="update" + isDisabled={!isAllowed} + > + + + )} + ))} diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingTabGroup/BillingTabGroup.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingTabGroup/BillingTabGroup.tsx index d30fa6ff..bd37adf5 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingTabGroup/BillingTabGroup.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingTabGroup/BillingTabGroup.tsx @@ -1,5 +1,8 @@ -import { Fragment } from "react" -import { Tab } from "@headlessui/react" +import { Fragment } from "react"; +import { Tab } from "@headlessui/react"; + +import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { withPermission } from "@app/hoc"; import { BillingCloudTab } from "../BillingCloudTab"; import { BillingDetailsTab } from "../BillingDetailsTab"; @@ -7,43 +10,48 @@ import { BillingReceiptsTab } from "../BillingReceiptsTab"; import { BillingSelfHostedTab } from "../BillingSelfHostedTab"; const tabs = [ - { name: "Infisical Cloud", key: "tab-infisical-cloud" }, - { name: "Infisical Self-Hosted", key: "tab-infisical-self-hosted" }, - { name: "Receipts", key: "tab-receipts" }, - { name: "Billing details", key: "tab-billing-details" } + { name: "Infisical Cloud", key: "tab-infisical-cloud" }, + { name: "Infisical Self-Hosted", key: "tab-infisical-self-hosted" }, + { name: "Receipts", key: "tab-receipts" }, + { name: "Billing details", key: "tab-billing-details" } ]; -export const BillingTabGroup = () => { +export const BillingTabGroup = withPermission( + () => { return ( - - - {tabs.map((tab) => ( - - {({ selected }) => ( - - )} - - ))} - - - - - - - - - - - - - - - - + + + {tabs.map((tab) => ( + + {({ selected }) => ( + + )} + + ))} + + + + + + + + + + + + + + + + ); -} \ No newline at end of file + }, + { action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.Billing } +); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx index 9a367223..b7cf27c0 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx @@ -1,9 +1,15 @@ +import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { withPermission } from "@app/hoc"; + import { OrgSSOSection } from "./OrgSSOSection"; -export const OrgAuthTab = () => { +export const OrgAuthTab = withPermission( + () => { return ( -
- -
+
+ +
); -} \ No newline at end of file + }, + { action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.Sso } +); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx index 7b4e65cb..274d0e51 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx @@ -2,136 +2,150 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { OrgPermissionCan } from "@app/components/permissions"; import { Button, Switch, UpgradePlanModal } from "@app/components/v2"; -import { useOrganization, useSubscription } from "@app/context"; -import { - useCreateSSOConfig, - useGetSSOConfig, - useUpdateSSOConfig -} from "@app/hooks/api"; +import { + OrgGeneralPermissionActions, + OrgPermissionSubjects, + useOrganization, + useSubscription +} from "@app/context"; +import { useCreateSSOConfig, useGetSSOConfig, useUpdateSSOConfig } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { SSOModal } from "./SSOModal"; const ssoAuthProviderMap: { [key: string]: string } = { - "okta-saml": "Okta SAML", - "azure-saml": "Azure SAML", - "jumpcloud-saml": "JumpCloud SAML" -} + "okta-saml": "Okta SAML", + "azure-saml": "Azure SAML", + "jumpcloud-saml": "JumpCloud SAML" +}; export const OrgSSOSection = (): JSX.Element => { - const { currentOrg } = useOrganization(); - const { subscription } = useSubscription(); - const { createNotification } = useNotificationContext(); - const { data, isLoading } = useGetSSOConfig(currentOrg?._id ?? ""); - const { mutateAsync } = useUpdateSSOConfig(); - const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ - "upgradePlan", - "addSSO" - ] as const); - - const { mutateAsync: createMutateAsync } = useCreateSSOConfig(); - - const handleSamlSSOToggle = async (value: boolean) => { - try { - if (!currentOrg?._id) return; + const { currentOrg } = useOrganization(); + const { subscription } = useSubscription(); + const { createNotification } = useNotificationContext(); + const { data, isLoading } = useGetSSOConfig(currentOrg?._id ?? ""); + const { mutateAsync } = useUpdateSSOConfig(); + const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ + "upgradePlan", + "addSSO" + ] as const); - await mutateAsync({ - organizationId: currentOrg?._id, - isActive: value - }); + const { mutateAsync: createMutateAsync } = useCreateSSOConfig(); - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} SAML SSO`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${value ? "enable" : "disable"} SAML SSO`, - type: "error" - }); - } + const handleSamlSSOToggle = async (value: boolean) => { + try { + if (!currentOrg?._id) return; + + await mutateAsync({ + organizationId: currentOrg?._id, + isActive: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} SAML SSO`, + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: `Failed to ${value ? "enable" : "disable"} SAML SSO`, + type: "error" + }); } - - const addSSOBtnClick = async () => { - try { - if (subscription?.samlSSO && currentOrg) { - if (!data) { - // case: SAML SSO is not configured - // -> initialize empty SAML SSO configuration - await createMutateAsync({ - organizationId: currentOrg._id, - authProvider: "okta-saml", - isActive: false, - entryPoint: "", - issuer: "", - cert: "" - }); - } + }; - handlePopUpOpen("addSSO"); - } else { - handlePopUpOpen("upgradePlan"); - } - } catch (err) { - console.error(err); + const addSSOBtnClick = async () => { + try { + if (subscription?.samlSSO && currentOrg) { + if (!data) { + // case: SAML SSO is not configured + // -> initialize empty SAML SSO configuration + await createMutateAsync({ + organizationId: currentOrg._id, + authProvider: "okta-saml", + isActive: false, + entryPoint: "", + issuer: "", + cert: "" + }); } + + handlePopUpOpen("addSSO"); + } else { + handlePopUpOpen("upgradePlan"); + } + } catch (err) { + console.error(err); } - - return ( -
-
-

- SAML SSO Configuration -

- {!isLoading && ( - - )} -
- {data && ( -
- handleSamlSSOToggle(value)} - isChecked={data ? data.isActive : false} - > - Enable SAML SSO - -
+ }; + + return ( +
+
+

SAML SSO Configuration

+ {!isLoading && ( + + {(isAllowed) => ( + + )} + + )} +
+ {data && ( +
+ + {(isAllowed) => ( + handleSamlSSOToggle(value)} + isChecked={data ? data.isActive : false} + isDisabled={!isAllowed} + > + Enable SAML SSO + )} -
-

SSO identifier

-

{(data && data._id !== "") ? data._id : "-"}

-
-
-

Type

-

{(data && data.authProvider !== "") ? ssoAuthProviderMap[data.authProvider] : "-"}

-
-
-

Entrypoint

-

{(data && data.entryPoint !== "") ? data.entryPoint : "-"}

-
-
-

Issuer

-

{(data && data.issuer !== "") ? data.issuer : "-"}

-
- - handlePopUpToggle("upgradePlan", isOpen)} - text="You can use SAML SSO if you switch to Infisical's Pro plan." - /> +
- ); -}; \ No newline at end of file + )} +
+

SSO identifier

+

{data && data._id !== "" ? data._id : "-"}

+
+
+

Type

+

+ {data && data.authProvider !== "" ? ssoAuthProviderMap[data.authProvider] : "-"} +

+
+
+

Entrypoint

+

+ {data && data.entryPoint !== "" ? data.entryPoint : "-"} +

+
+
+

Issuer

+

{data && data.issuer !== "" ? data.issuer : "-"}

+
+ + handlePopUpToggle("upgradePlan", isOpen)} + text="You can use SAML SSO if you switch to Infisical's Pro plan." + /> +
+ ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx index 5c1fb273..3c9c016a 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx @@ -3,13 +3,11 @@ import { OrgNameChangeSection } from "../OrgNameChangeSection"; import { OrgServiceAccountsTable } from "../OrgServiceAccountsTable"; export const OrgGeneralTab = () => { - return ( -
- -
- -
- -
- ); -} \ No newline at end of file + return ( +
+ + + +
+ ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx index 238c5d19..e28bbc38 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx @@ -3,17 +3,9 @@ import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import { - Button, - FormControl, - Input, - Modal, - ModalContent -} from "@app/components/v2"; +import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; import { useOrganization } from "@app/context"; -import { - useAddIncidentContact -} from "@app/hooks/api"; +import { useAddIncidentContact } from "@app/hooks/api"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -24,97 +16,90 @@ const addContactFormSchema = yup.object({ type TAddContactForm = yup.InferType; type Props = { - popUp: UsePopUpState<["addContact"]>; - handlePopUpClose: (popUpName: keyof UsePopUpState<["addContact"]>) => void; - handlePopUpToggle: (popUpName: keyof UsePopUpState<["addContact"]>, state?: boolean) => void; + popUp: UsePopUpState<["addContact"]>; + handlePopUpClose: (popUpName: keyof UsePopUpState<["addContact"]>) => void; + handlePopUpToggle: (popUpName: keyof UsePopUpState<["addContact"]>, state?: boolean) => void; }; export const AddOrgIncidentContactModal = ({ - popUp, - handlePopUpClose, - handlePopUpToggle + popUp, + handlePopUpClose, + handlePopUpToggle }: Props) => { - const { createNotification } = useNotificationContext(); - const { currentOrg } = useOrganization(); - const { data: serverDetails } = useFetchServerStatus() - const { - control, - handleSubmit, - reset - } = useForm({ resolver: yupResolver(addContactFormSchema) }); + const { createNotification } = useNotificationContext(); + const { currentOrg } = useOrganization(); + const { data: serverDetails } = useFetchServerStatus(); + const { control, handleSubmit, reset } = useForm({ + resolver: yupResolver(addContactFormSchema) + }); - const { mutateAsync, isLoading } = useAddIncidentContact(); - - const onFormSubmit = async ({ email }: TAddContactForm) => { - try { - if (!currentOrg?._id) return; - - await mutateAsync({ - orgId: currentOrg._id, - email - }); + const { mutateAsync, isLoading } = useAddIncidentContact(); - createNotification({ - text: "Successfully added incident contact", - type: "success" - }); - - if (serverDetails?.emailConfigured){ - handlePopUpClose("addContact"); - } + const onFormSubmit = async ({ email }: TAddContactForm) => { + try { + if (!currentOrg?._id) return; - reset(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to add incident contact", - type: "error" - }); - } + await mutateAsync({ + orgId: currentOrg._id, + email + }); + + createNotification({ + text: "Successfully added incident contact", + type: "success" + }); + + if (serverDetails?.emailConfigured) { + handlePopUpClose("addContact"); + } + + reset(); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to add incident contact", + type: "error" + }); } + }; - return ( - { - handlePopUpToggle("addContact", isOpen); - reset(); - }} - > - { + handlePopUpToggle("addContact", isOpen); + reset(); + }} + > + +
+ ( + + + + )} + /> +
+ + - -
- -
-
- ); -} \ No newline at end of file + Cancel + +
+ + + + ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsSection.tsx index 29d1064a..a4b0ab01 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsSection.tsx @@ -2,42 +2,53 @@ import { useTranslation } from "react-i18next"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - Button -} from "@app/components/v2"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { Button } from "@app/components/v2"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { AddOrgIncidentContactModal } from "./AddOrgIncidentContactModal"; import { OrgIncidentContactsTable } from "./OrgIncidentContactsTable"; -export const OrgIncidentContactsSection = () => { +export const OrgIncidentContactsSection = withPermission( + () => { const { t } = useTranslation(); const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ - "addContact" + "addContact" ] as const); return ( -
-
-

- {t("section.incident.incident-contacts")} -

- -
- - +
+
+

+ {t("section.incident.incident-contacts")} +

+ + {(isAllowed) => ( + + )} +
+ + +
); -} - + }, + { action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.IncidentAccount } +); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx index 0d301157..4976d11f 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx @@ -3,6 +3,7 @@ import { faContactBook, faMagnifyingGlass, faTrash } from "@fortawesome/free-sol import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { OrgPermissionCan } from "@app/components/permissions"; import { DeleteActionModal, EmptyState, @@ -17,7 +18,7 @@ import { THead, Tr } from "@app/components/v2"; -import { useOrganization } from "@app/context"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useDeleteIncidentContact, useGetOrgIncidentContact } from "@app/hooks/api"; @@ -83,13 +84,21 @@ export const OrgIncidentContactsTable = () => { {email} - handlePopUpOpen("removeContact", { email })} + - - + {(isAllowed) => ( + handlePopUpOpen("removeContact", { email })} + isDisabled={!isAllowed} + > + + + )} + ))} diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx index 3c22f01f..c1f1d449 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx @@ -4,8 +4,10 @@ import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { OrgPermissionCan } from "@app/components/permissions"; import { Button, FormControl, Input } from "@app/components/v2"; -import { useOrganization } from "@app/context"; +import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { withPermission } from "@app/hoc"; import { useRenameOrg } from "@app/hooks/api"; const formSchema = yup.object({ @@ -14,49 +16,46 @@ const formSchema = yup.object({ type FormData = yup.InferType; -export const OrgNameChangeSection = (): JSX.Element => { - const { currentOrg } = useOrganization(); - const { createNotification } = useNotificationContext(); - const { - handleSubmit, - control, - reset - } = useForm({ resolver: yupResolver(formSchema) }); - const { mutateAsync, isLoading } = useRenameOrg(); +export const OrgNameChangeSection = withPermission( + (): JSX.Element => { + const { currentOrg } = useOrganization(); + const { createNotification } = useNotificationContext(); + const { handleSubmit, control, reset } = useForm({ + resolver: yupResolver(formSchema) + }); + const { mutateAsync, isLoading } = useRenameOrg(); - useEffect(() => { - if (currentOrg) { - reset({ name: currentOrg.name }); - } - }, [currentOrg]); + useEffect(() => { + if (currentOrg) { + reset({ name: currentOrg.name }); + } + }, [currentOrg]); - const onFormSubmit = async ({ name }: FormData) => { - try { - if (!currentOrg?._id) return; - if (name === "") return; + const onFormSubmit = async ({ name }: FormData) => { + try { + if (!currentOrg?._id) return; + if (name === "") return; - await mutateAsync({ orgId: currentOrg?._id, newOrgName: name }); - createNotification({ - text: "Successfully renamed organization", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to rename organization", - type: "error" - }); - } - }; + await mutateAsync({ orgId: currentOrg?._id, newOrgName: name }); + createNotification({ + text: "Successfully renamed organization", + type: "success" + }); + } catch (error) { + console.error(error); + createNotification({ + text: "Failed to rename organization", + type: "error" + }); + } + }; - return ( -
-

- Organization name -

+ return ( + +

Organization name

{ name="name" />
- -
- ); -}; + + {(isAllowed) => ( + + )} + + + ); + }, + { + action: OrgGeneralPermissionActions.Read, + subject: OrgPermissionSubjects.Settings, + containerClassName: "mb-4" + } +); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgServiceAccountsTable/OrgServiceAccountsTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgServiceAccountsTable/OrgServiceAccountsTable.tsx index 6ead6cf8..eaac5d7c 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgServiceAccountsTable/OrgServiceAccountsTable.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgServiceAccountsTable/OrgServiceAccountsTable.tsx @@ -34,7 +34,13 @@ import { THead, Tr } from "@app/components/v2"; -import { useOrganization, useWorkspace } from "@app/context"; +import { + OrgGeneralPermissionActions, + OrgPermissionSubjects, + useOrganization, + useWorkspace +} from "@app/context"; +import { withPermission } from "@app/hoc"; import { usePopUp, useToggle } from "@app/hooks"; import { // useCreateServiceAccount, @@ -62,313 +68,322 @@ import // Controller, // type TAddServiceAccountForm = yup.InferType; -export const OrgServiceAccountsTable = () => { - const router = useRouter(); - const { currentOrg } = useOrganization(); - const { currentWorkspace } = useWorkspace(); +export const OrgServiceAccountsTable = withPermission( + () => { + const router = useRouter(); + const { currentOrg } = useOrganization(); + const { currentWorkspace } = useWorkspace(); - const orgId = currentOrg?._id || ""; - const [step, setStep] = useState(0); - const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false); - const [isPublicKeyCopied, setIsPublicKeyCopied] = useToggle(false); - const [isPrivateKeyCopied, setIsPrivateKeyCopied] = useToggle(false); - const [accessKey] = useState(""); - const [publicKey] = useState(""); - const [privateKey] = useState(""); - const [searchServiceAccountFilter, setSearchServiceAccountFilter] = useState(""); - const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ - "addServiceAccount", - "removeServiceAccount" - ] as const); + const orgId = currentOrg?._id || ""; + const [step, setStep] = useState(0); + const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false); + const [isPublicKeyCopied, setIsPublicKeyCopied] = useToggle(false); + const [isPrivateKeyCopied, setIsPrivateKeyCopied] = useToggle(false); + const [accessKey] = useState(""); + const [publicKey] = useState(""); + const [privateKey] = useState(""); + const [searchServiceAccountFilter, setSearchServiceAccountFilter] = useState(""); + const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + "addServiceAccount", + "removeServiceAccount" + ] as const); - const { data: serviceAccounts = [], isLoading: isServiceAccountsLoading } = - useGetServiceAccounts(orgId); + const { data: serviceAccounts = [], isLoading: isServiceAccountsLoading } = + useGetServiceAccounts(orgId); - // const createServiceAccount = useCreateServiceAccount(); - const removeServiceAccount = useDeleteServiceAccount(); + // const createServiceAccount = useCreateServiceAccount(); + const removeServiceAccount = useDeleteServiceAccount(); - useEffect(() => { - let timer: NodeJS.Timeout; - if (isAccessKeyCopied) { - timer = setTimeout(() => setIsAccessKeyCopied.off(), 2000); - } + useEffect(() => { + let timer: NodeJS.Timeout; + if (isAccessKeyCopied) { + timer = setTimeout(() => setIsAccessKeyCopied.off(), 2000); + } - if (isPublicKeyCopied) { - timer = setTimeout(() => setIsPublicKeyCopied.off(), 2000); - } + if (isPublicKeyCopied) { + timer = setTimeout(() => setIsPublicKeyCopied.off(), 2000); + } - if (isPrivateKeyCopied) { - timer = setTimeout(() => setIsPrivateKeyCopied.off(), 2000); - } + if (isPrivateKeyCopied) { + timer = setTimeout(() => setIsPrivateKeyCopied.off(), 2000); + } - return () => clearTimeout(timer); - }, [isAccessKeyCopied, isPublicKeyCopied, isPrivateKeyCopied]); + return () => clearTimeout(timer); + }, [isAccessKeyCopied, isPublicKeyCopied, isPrivateKeyCopied]); - // const { - // control, - // handleSubmit, - // reset, - // formState: { isSubmitting } - // } = useForm({ resolver: yupResolver(addServiceAccountFormSchema) }); + // const { + // control, + // handleSubmit, + // reset, + // formState: { isSubmitting } + // } = useForm({ resolver: yupResolver(addServiceAccountFormSchema) }); - // const onAddServiceAccount = async ({ name, expiresIn }: TAddServiceAccountForm) => { - // if (!currentOrg?._id) return; + // const onAddServiceAccount = async ({ name, expiresIn }: TAddServiceAccountForm) => { + // if (!currentOrg?._id) return; - // const keyPair = generateKeyPair(); - // setPublicKey(keyPair.publicKey); - // setPrivateKey(keyPair.privateKey); + // const keyPair = generateKeyPair(); + // setPublicKey(keyPair.publicKey); + // setPrivateKey(keyPair.privateKey); - // const serviceAccountDetails = await createServiceAccount.mutateAsync({ - // name, - // organizationId: currentOrg?._id, - // publicKey: keyPair.publicKey, - // expiresIn: Number(expiresIn) - // }); + // const serviceAccountDetails = await createServiceAccount.mutateAsync({ + // name, + // organizationId: currentOrg?._id, + // publicKey: keyPair.publicKey, + // expiresIn: Number(expiresIn) + // }); - // setAccessKey(serviceAccountDetails.serviceAccountAccessKey); + // setAccessKey(serviceAccountDetails.serviceAccountAccessKey); - // setStep(1); - // reset(); - // } + // setStep(1); + // reset(); + // } - const onRemoveServiceAccount = async () => { - const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id; - await removeServiceAccount.mutateAsync(serviceAccountId); - handlePopUpClose("removeServiceAccount"); - }; + const onRemoveServiceAccount = async () => { + const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id; + await removeServiceAccount.mutateAsync(serviceAccountId); + handlePopUpClose("removeServiceAccount"); + }; - const filteredServiceAccounts = useMemo( - () => - serviceAccounts.filter(({ name }) => name.toLowerCase().includes(searchServiceAccountFilter)), - [serviceAccounts, searchServiceAccountFilter] - ); + const filteredServiceAccounts = useMemo( + () => + serviceAccounts.filter(({ name }) => + name.toLowerCase().includes(searchServiceAccountFilter) + ), + [serviceAccounts, searchServiceAccountFilter] + ); - const renderStep = (stepToRender: number) => { - switch (stepToRender) { - case 0: - return ( -
- We are currently revising the service account mechanism. In the meantime, please use - service tokens or API key to fetch secrets via API request. -
- //
- // ( - // - // - // - // )} - // /> - // { - // return ( - // - // - // - // ); - // }} - // /> - //
- // - // - //
- // - ); - case 1: - return ( - <> -

Access Key

-
-

{accessKey}

- { - navigator.clipboard.writeText(accessKey); - setIsAccessKeyCopied.on(); - }} - > - - - Copy - - -
-

Public Key

-
-

{publicKey}

- { - navigator.clipboard.writeText(publicKey); - setIsPublicKeyCopied.on(); - }} - > - - - Copy - - -
-

Private Key

-
-

{privateKey}

- { - navigator.clipboard.writeText(privateKey); - setIsPrivateKeyCopied.on(); - }} - > - - - Copy - - + const renderStep = (stepToRender: number) => { + switch (stepToRender) { + case 0: + return ( +
+ We are currently revising the service account mechanism. In the meantime, please use + service tokens or API key to fetch secrets via API request.
- - ); - default: - return
; - } - }; + //
+ // ( + // + // + // + // )} + // /> + // { + // return ( + // + // + // + // ); + // }} + // /> + //
+ // + // + //
+ // + ); + case 1: + return ( + <> +

Access Key

+
+

{accessKey}

+ { + navigator.clipboard.writeText(accessKey); + setIsAccessKeyCopied.on(); + }} + > + + + Copy + + +
+

Public Key

+
+

{publicKey}

+ { + navigator.clipboard.writeText(publicKey); + setIsPublicKeyCopied.on(); + }} + > + + + Copy + + +
+

Private Key

+
+

{privateKey}

+ { + navigator.clipboard.writeText(privateKey); + setIsPrivateKeyCopied.on(); + }} + > + + + Copy + + +
+ + ); + default: + return
; + } + }; - return ( -
-
-

Service Accounts

- +
+ setSearchServiceAccountFilter(e.target.value)} + leftIcon={} + placeholder="Search service accounts..." + /> + + + + + + + + {isServiceAccountsLoading && ( + + )} + {!isServiceAccountsLoading && + filteredServiceAccounts.map(({ name, expiresAt, _id: serviceAccountId }) => { + return ( + + + + + + ); + })} + +
NameValid Until +
{name}{new Date(expiresAt).toUTCString()} +
+ { + if (currentWorkspace?._id) { + router.push( + `/settings/org/${currentWorkspace._id}/service-accounts/${serviceAccountId}` + ); + } + }} + className="mr-2" + > + + + + handlePopUpOpen("removeServiceAccount", { _id: serviceAccountId }) + } + > + + +
+
+ {!isServiceAccountsLoading && filteredServiceAccounts?.length === 0 && ( + + )} +
+ { + handlePopUpToggle("addServiceAccount", isOpen); // reset(); - handlePopUpOpen("addServiceAccount"); }} > - Add Service Account - + + {renderStep(step)} + + + handlePopUpToggle("removeServiceAccount", isOpen)} + onDeleteApproved={onRemoveServiceAccount} + />
- setSearchServiceAccountFilter(e.target.value)} - leftIcon={} - placeholder="Search service accounts..." - /> - - - - - - - - {isServiceAccountsLoading && ( - - )} - {!isServiceAccountsLoading && - filteredServiceAccounts.map(({ name, expiresAt, _id: serviceAccountId }) => { - return ( - - - - - - ); - })} - -
NameValid Until -
{name}{new Date(expiresAt).toUTCString()} -
- { - if (currentWorkspace?._id) { - router.push( - `/settings/org/${currentWorkspace._id}/service-accounts/${serviceAccountId}` - ); - } - }} - className="mr-2" - > - - - - handlePopUpOpen("removeServiceAccount", { _id: serviceAccountId }) - } - > - - -
-
- {!isServiceAccountsLoading && filteredServiceAccounts?.length === 0 && ( - - )} -
- { - handlePopUpToggle("addServiceAccount", isOpen); - // reset(); - }} - > - - {renderStep(step)} - - - handlePopUpToggle("removeServiceAccount", isOpen)} - onDeleteApproved={onRemoveServiceAccount} - /> -
- ); -}; + ); + }, + { + action: OrgGeneralPermissionActions.Read, + subject: OrgPermissionSubjects.Settings, + containerClassName: "mb-4" + } +); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx index 1d0ebeb6..cacbf1b5 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx @@ -1,59 +1,40 @@ -import { Fragment } from "react" -import { Tab } from "@headlessui/react" - -import { useOrganization,useUser } from "@app/context"; -import { - useGetOrgUsers -} from "@app/hooks/api"; +import { Fragment } from "react"; +import { Tab } from "@headlessui/react"; import { OrgAuthTab } from "../OrgAuthTab"; import { OrgGeneralTab } from "../OrgGeneralTab"; +const tabs = [ + { name: "General", key: "tab-org-general" }, + { name: "Authentication", key: "tab-org-auth" } +]; export const OrgTabGroup = () => { - const { currentOrg } = useOrganization(); - const { user } = useUser(); - const { data } = useGetOrgUsers(currentOrg?._id ?? ""); - - const isRoleSufficient = data?.some((orgUser) => { - return orgUser.role !== "member" && orgUser.user._id === user._id; - }); - - const tabs = [ - { name: "General", key: "tab-org-general" }, - ]; - - if (isRoleSufficient) { - tabs.push( - { name: "Authentication", key: "tab-org-auth" } - ); - } - - return ( - - - {tabs.map((tab) => ( - - {({ selected }) => ( - - )} - - ))} - - - - - - {isRoleSufficient && ( - - - - )} - - - ); -} \ No newline at end of file + return ( + + + {tabs.map((tab) => ( + + {({ selected }) => ( + + )} + + ))} + + + + + + + + + + + ); +};