feat(rbac): implemented project based permission loading and role management

This commit is contained in:
Akhil Mohan
2023-09-08 21:22:36 +05:30
parent aac3168c80
commit 520a553ea1
63 changed files with 2237 additions and 488 deletions
+9 -6
View File
@@ -19,6 +19,7 @@ import {
AuthProvider,
OrgPermissionProvider,
OrgProvider,
ProjectPermissionProvider,
SubscriptionProvider,
UserProvider,
WorkspaceProvider
@@ -98,15 +99,17 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element =>
<OrgProvider>
<OrgPermissionProvider>
<WorkspaceProvider>
<SubscriptionProvider>
<UserProvider>
<NotificationProvider>
<ProjectPermissionProvider>
<SubscriptionProvider>
<UserProvider>
<NotificationProvider>
<AppLayout>
<Component {...pageProps} />
</AppLayout>
</NotificationProvider>
</UserProvider>
</SubscriptionProvider>
</NotificationProvider>
</UserProvider>
</SubscriptionProvider>
</ProjectPermissionProvider>
</WorkspaceProvider>
</OrgPermissionProvider>
</OrgProvider>
@@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import Head from "next/head";
import { OrgGeneralPermissionActions, OrgPermissionSubjects, TOrgPermission } from "@app/context";
import { GeneralPermissionActions, OrgPermissionSubjects, TOrgPermission } from "@app/context";
import { withPermission } from "@app/hoc";
import { BillingSettingsPage } from "@app/views/Settings/BillingSettingsPage";
@@ -20,7 +20,7 @@ const SettingsBilling = withPermission<{}, TOrgPermission>(
</div>
);
},
{ action: OrgGeneralPermissionActions.Delete, subject: OrgPermissionSubjects.Billing }
{ action: GeneralPermissionActions.Delete, subject: OrgPermissionSubjects.Billing }
);
Object.assign(SettingsBilling, { requireAuth: true });
@@ -45,8 +45,8 @@ import {
} from "@app/components/v2";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import {
GeneralPermissionActions,
OrgPermissionSubjects,
OrgWorkspacePermissionActions,
useSubscription,
useUser,
useWorkspace
@@ -590,7 +590,7 @@ const OrganizationPage = withPermission(
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
/>
<OrgPermissionCan
I={OrgWorkspacePermissionActions.Create}
I={GeneralPermissionActions.Create}
an={OrgPermissionSubjects.Workspace}
>
{(isAllowed) => (
@@ -877,7 +877,7 @@ const OrganizationPage = withPermission(
);
},
{
action: OrgWorkspacePermissionActions.Read,
action: GeneralPermissionActions.Read,
subject: OrgPermissionSubjects.Workspace
}
);
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
import { GeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc";
import { SecretScanningLogsTable } from "@app/views/SecretScanning/components";
@@ -101,7 +101,7 @@ const SecretScanning = withPermission(
) : (
<div className="flex items-center h-[3.25rem]">
<OrgPermissionCan
I={OrgGeneralPermissionActions.Create}
I={GeneralPermissionActions.Create}
a={OrgPermissionSubjects.SecretScanning}
>
{(isAllowed) => (
@@ -125,7 +125,7 @@ const SecretScanning = withPermission(
</div>
);
},
{ action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.SecretScanning }
{ action: GeneralPermissionActions.Read, subject: OrgPermissionSubjects.SecretScanning }
);
Object.assign(SecretScanning, { requireAuth: true });
@@ -1,225 +1,21 @@
import { useEffect, useState } from "react";
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useTranslation } from "react-i18next";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "@app/components/basic/buttons/Button";
import AddProjectMemberDialog from "@app/components/basic/dialog/AddProjectMemberDialog";
import ProjectUsersTable from "@app/components/basic/table/ProjectUsersTable";
import guidGenerator from "@app/components/utilities/randomId";
import { Input } from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useAddUserToWorkspace,
useGetOrgUsers,
useGetUser,
useGetWorkspaceUsers} from "@app/hooks/api";
import { uploadWsKey } from "@app/hooks/api/keys/queries";
import {
decryptAssymmetric,
encryptAssymmetric
} from "../../../../components/utilities/cryptography/crypto";
interface UserProps {
firstName: string;
lastName: string;
email: string;
_id: string;
publicKey: string;
}
interface MembershipProps {
deniedPermissions: any[];
user: UserProps;
inviteEmail: string;
role: string;
status: string;
_id: string;
}
// #TODO: Update all the workspaceIds
export default function Users() {
const router = useRouter();
const workspaceId = router.query.id as string;
const { data: user } = useGetUser();
const { currentOrg } = useOrganization();
const { data: orgUsers } = useGetOrgUsers(currentOrg?._id ?? "");
const { data: workspaceUsers } = useGetWorkspaceUsers(workspaceId);
const { mutateAsync: addUserToWorkspaceMutateAsync } = useAddUserToWorkspace();
const [isAddOpen, setIsAddOpen] = useState(false);
// let [isDeleteOpen, setIsDeleteOpen] = useState(false);
// let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false);
const [email, setEmail] = useState("");
const [personalEmail, setPersonalEmail] = useState("");
const [searchUsers, setSearchUsers] = useState("");
import { MembersPage } from "@app/views/Project/MembersPage";
export default function WorkspaceMemberSettings() {
const { t } = useTranslation();
const [userList, setUserList] = useState<any[]>([]);
const [isUserListLoading, setIsUserListLoading] = useState(true);
const [orgUserList, setOrgUserList] = useState<any[]>([]);
useEffect(() => {
if (user && workspaceUsers && orgUsers) {
(async () => {
setPersonalEmail(user.email);
const tempUserList = workspaceUsers.map((membership: MembershipProps) => ({
key: guidGenerator(),
firstName: membership.user?.firstName,
lastName: membership.user?.lastName,
email: membership.user?.email === null ? membership.inviteEmail : membership.user?.email,
role: membership?.role,
status: membership?.status,
userId: membership.user?._id,
membershipId: membership._id,
deniedPermissions: membership.deniedPermissions,
publicKey: membership.user?.publicKey
}));
setUserList(tempUserList);
setIsUserListLoading(false);
setOrgUserList(orgUsers);
setEmail(
orgUsers
?.filter((membership: MembershipProps) => membership.status === "accepted")
.map((membership: MembershipProps) => membership.user.email)
.filter(
(usEmail: string) =>
!tempUserList?.map((user1: UserProps) => user1.email).includes(usEmail)
)[0]
);
})();
}
}, [user, workspaceUsers, orgUsers]);
const closeAddModal = () => {
setIsAddOpen(false);
};
const openAddModal = () => {
setIsAddOpen(true);
};
// function closeDeleteModal() {
// setIsDeleteOpen(false);
// }
// function deleteMembership(userId) {
// deleteUserFromWorkspace(userId, router.query.id)
// }
// function openDeleteModal() {
// setIsDeleteOpen(true);
// }
const submitAddModal = async () => {
const result = await addUserToWorkspaceMutateAsync({
email,
workspaceId
});
if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY
});
await uploadWsKey({
workspaceId,
userId: result.invitee._id,
encryptedKey: ciphertext,
nonce
});
}
setEmail("");
setIsAddOpen(false);
};
return userList ? (
<div className="flex max-w-7xl mx-auto flex-col justify-start bg-bunker-800 md:h-screen">
return (
<>
<Head>
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4">
<p className="mr-4 font-semibold text-white">{t("settings.members.title")}</p>
</div>
<AddProjectMemberDialog
isOpen={isAddOpen}
closeModal={closeAddModal}
submitModal={submitAddModal}
email={email}
data={orgUserList
?.filter((membership: MembershipProps) => membership.status === "accepted")
.map((membership: MembershipProps) => membership.user.email)
.filter(
(orgEmail) => !userList?.map((user1: UserProps) => user1.email).includes(orgEmail)
)}
setEmail={setEmail}
/>
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="flex w-full flex-row items-start px-6 pb-1">
<div className="flex w-full max-w-sm flex flex-row ml-auto">
<Input
className="h-[2.3rem] bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
placeholder="Search by users..."
value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
/>
</div>
<div className="ml-2 flex min-w-max flex-row items-start justify-start">
<Button
text={String(t("section.members.add-member"))}
onButtonPressed={() => {
openAddModal();
}}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
<div className="block overflow-x-auto px-6 pb-6">
<ProjectUsersTable
userData={userList}
changeData={setUserList}
myUser={personalEmail}
filter={searchUsers}
isUserListLoading={isUserListLoading}
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}
/>
</div>
</div>
) : (
<div className="relative z-10 mr-auto ml-2 flex h-full w-10/12 flex-col items-center justify-center bg-bunker-800">
<Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
</div>
<MembersPage />
</>
);
}
Users.requireAuth = true;
WorkspaceMemberSettings.requireAuth = true;