From 34fb7be1c4737fea874d7bfdb144ff23260445e1 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Wed, 16 Aug 2023 20:53:24 +0530 Subject: [PATCH] feat(rbac): added org permission context for pulling permission from api --- backend/src/controllers/v1/roleController.ts | 15 ++++++ backend/src/routes/v1/role.ts | 7 +++ backend/src/validation/role.ts | 12 +++-- .../OrgPermissionContext.tsx | 50 +++++++++++++++++++ .../context/OrgPermissionContext/index.tsx | 1 + .../src/context/OrgPermissionContext/types.ts | 36 +++++++++++++ frontend/src/context/index.tsx | 1 + frontend/src/hooks/api/roles/index.tsx | 4 +- frontend/src/hooks/api/roles/queries.tsx | 29 ++++++++++- frontend/src/hooks/api/roles/types.ts | 4 ++ frontend/src/pages/_app.tsx | 25 ++++++---- 11 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx create mode 100644 frontend/src/context/OrgPermissionContext/index.tsx create mode 100644 frontend/src/context/OrgPermissionContext/types.ts diff --git a/backend/src/controllers/v1/roleController.ts b/backend/src/controllers/v1/roleController.ts index 844caa5fc..5a7fe7c8f 100644 --- a/backend/src/controllers/v1/roleController.ts +++ b/backend/src/controllers/v1/roleController.ts @@ -13,8 +13,10 @@ import { CreateRoleSchema, DeleteRoleSchema, GetRoleSchema, + GetUserPermission, UpdateRoleSchema } from "../../validation"; +import { packRules } from "@casl/ability/extra"; export const createRole = async (req: Request, res: Response) => { const { @@ -160,3 +162,16 @@ export const getRoles = async (req: Request, res: Response) => { } }); }; + +export const getUserPermissions = async (req: Request, res: Response) => { + const { + params: { orgId } + } = await validateRequest(GetUserPermission, req); + const { permission } = await getUserOrgPermissions(req.user.id, orgId); + + res.status(200).json({ + data: { + permissions: packRules(permission.rules) + } + }); +}; diff --git a/backend/src/routes/v1/role.ts b/backend/src/routes/v1/role.ts index a590091b8..e2a64eebe 100644 --- a/backend/src/routes/v1/role.ts +++ b/backend/src/routes/v1/role.ts @@ -17,4 +17,11 @@ router.delete( router.get("/", requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), roleController.getRoles); +// get a user permissions in an org +router.get( + "/:orgId/permissions", + requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), + roleController.getUserPermissions +); + export default router; diff --git a/backend/src/validation/role.ts b/backend/src/validation/role.ts index 2e6623d57..c431efd7c 100644 --- a/backend/src/validation/role.ts +++ b/backend/src/validation/role.ts @@ -40,13 +40,19 @@ export const UpdateRoleSchema = z.object({ export const DeleteRoleSchema = z.object({ params: z.object({ - id: z.string() + id: z.string().trim() }) }); export const GetRoleSchema = z.object({ query: z.object({ - workspaceId: z.string().optional(), - orgId: z.string() + workspaceId: z.string().trim().optional(), + orgId: z.string().trim() + }) +}); + +export const GetUserPermission = z.object({ + params: z.object({ + orgId: z.string().trim() }) }); diff --git a/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx b/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx new file mode 100644 index 000000000..4d968c06b --- /dev/null +++ b/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx @@ -0,0 +1,50 @@ +import { createContext, ReactNode, useContext } from "react"; + +import { useGetUserOrgPermissions } from "@app/hooks/api"; + +import { useOrganization } from "../OrganizationContext"; +import { TPermission } from "./types"; + +type Props = { + children: ReactNode; +}; + +const PermissionContext = createContext(null); + +export const OrgPermissionProvider = ({ children }: Props): JSX.Element => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?._id || ""; + const { data: permission, isLoading } = useGetUserOrgPermissions({ orgId }); + + if (isLoading) { + return ( +
+ infisical loading indicator +
+ ); + } + + if (!permission) { + return ( +
+ Failed to load user permissions +
+ ); + } + + return {children}; +}; + +export const useOrgPermission = () => { + const ctx = useContext(PermissionContext); + if (!ctx) { + throw new Error("useOrgPermission to be used within "); + } + + return ctx; +}; diff --git a/frontend/src/context/OrgPermissionContext/index.tsx b/frontend/src/context/OrgPermissionContext/index.tsx new file mode 100644 index 000000000..73cfe0a19 --- /dev/null +++ b/frontend/src/context/OrgPermissionContext/index.tsx @@ -0,0 +1 @@ +export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext"; diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts new file mode 100644 index 000000000..29e08383a --- /dev/null +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -0,0 +1,36 @@ +import { MongoAbility } from "@casl/ability"; + +export enum GeneralPermissionActions { + Read = "read", + Create = "create", + Edit = "edit", + Delete = "delete" +} + +export enum WorkspacePermissionActions { + Read = "read", + Create = "create" +} + +export enum OrgPermissionSubjects { + Workspace = "workspace", + Role = "role", + Member = "member", + Settings = "settings", + IncidentAccount = "incident-contact", + Sso = "sso", + Billing = "billing", + SecretScanning = "secret-scanning" +} + +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]; + +export type TPermission = MongoAbility; diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx index 7eb61268a..097703d1e 100644 --- a/frontend/src/context/index.tsx +++ b/frontend/src/context/index.tsx @@ -1,5 +1,6 @@ export { AuthProvider } from "./AuthContext"; export { OrgProvider, useOrganization } from "./OrganizationContext"; +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/hooks/api/roles/index.tsx b/frontend/src/hooks/api/roles/index.tsx index e675be785..5fd5cd3ac 100644 --- a/frontend/src/hooks/api/roles/index.tsx +++ b/frontend/src/hooks/api/roles/index.tsx @@ -1,2 +1,2 @@ -export { useCreateRole, useDeleteRole,useUpdateRole } from "./mutation"; -export { useGetRoles } from "./queries"; +export { useCreateRole, useDeleteRole, useUpdateRole } from "./mutation"; +export { useGetRoles, useGetUserOrgPermissions } from "./queries"; diff --git a/frontend/src/hooks/api/roles/queries.tsx b/frontend/src/hooks/api/roles/queries.tsx index e0f3246f8..8fda90a85 100644 --- a/frontend/src/hooks/api/roles/queries.tsx +++ b/frontend/src/hooks/api/roles/queries.tsx @@ -1,11 +1,16 @@ +import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability"; +import { PackRule, unpackRules } from "@casl/ability/extra"; import { useQuery } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; +import { OrgPermissionSet } from "@app/context/OrgPermissionContext/types"; -import { TGetRolesDTO, TRole } from "./types"; +import { TGetRolesDTO, TGetUserOrgPermissionsDTO, TRole } from "./types"; export const roleQueryKeys = { - getRoles: ({ orgId, workspaceId }: TGetRolesDTO) => ["roles", { orgId, workspaceId }] as const + getRoles: ({ orgId, workspaceId }: TGetRolesDTO) => ["roles", { orgId, workspaceId }] as const, + getUserOrgPermissions: ({ orgId }: TGetUserOrgPermissionsDTO) => + ["user-permissions", { orgId }] as const }; const getRoles = async ({ orgId, workspaceId }: TGetRolesDTO) => { @@ -25,3 +30,23 @@ export const useGetRoles = ({ orgId, workspaceId }: TGetRolesDTO) => queryFn: () => getRoles({ orgId, workspaceId }), enabled: Boolean(orgId) }); + +const getUserOrgPermissions = async ({ orgId }: TGetUserOrgPermissionsDTO) => { + const { data } = await apiRequest.get<{ + data: { permissions: PackRule>>[] }; + }>(`/api/v1/roles/${orgId}/permissions`, {}); + + return data.data.permissions; +}; + +export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) => + useQuery({ + queryKey: roleQueryKeys.getUserOrgPermissions({ orgId }), + queryFn: () => getUserOrgPermissions({ orgId }), + enabled: Boolean(orgId), + select: (data) => { + const rule = unpackRules>>(data); + const ability = createMongoAbility(rule); + return ability; + } + }); diff --git a/frontend/src/hooks/api/roles/types.ts b/frontend/src/hooks/api/roles/types.ts index 4096225ff..07b73fae6 100644 --- a/frontend/src/hooks/api/roles/types.ts +++ b/frontend/src/hooks/api/roles/types.ts @@ -49,3 +49,7 @@ export type TDeleteRoleDTO = { id: string; workspaceId?: string; }; + +export type TGetUserOrgPermissionsDTO = { + orgId: string; +}; diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index 56fb8c9f7..c540798eb 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -17,6 +17,7 @@ import { TooltipProvider } from "@app/components/v2"; import { publicPaths } from "@app/const"; import { AuthProvider, + OrgPermissionProvider, OrgProvider, SubscriptionProvider, UserProvider, @@ -95,17 +96,19 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element => - - - - - - - - - - - + + + + + + + + + + + + +