diff --git a/backend/src/ee/routes/v1/group-router.ts b/backend/src/ee/routes/v1/group-router.ts index beb0d8345..d267564f2 100644 --- a/backend/src/ee/routes/v1/group-router.ts +++ b/backend/src/ee/routes/v1/group-router.ts @@ -13,7 +13,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), schema: { body: z.object({ - name: z.string().trim().min(1).describe(GROUPS.CREATE.name), + name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name), slug: z .string() .min(5) diff --git a/backend/src/ee/services/group/group-dal.ts b/backend/src/ee/services/group/group-dal.ts index 240500254..55afd4e10 100644 --- a/backend/src/ee/services/group/group-dal.ts +++ b/backend/src/ee/services/group/group-dal.ts @@ -118,9 +118,8 @@ export const groupDALFactory = (db: TDbClient) => { db.ref("firstName").withSchema(TableName.Users), db.ref("lastName").withSchema(TableName.Users), db.ref("id").withSchema(TableName.Users).as("userId") - // db.raw(`CASE WHEN "${TableName.UserGroupMembership}"."groupId" IS NOT NULL THEN TRUE ELSE FALSE END as isPartOfGroup`) ) - .where({ isGhost: false }) // MAKE SURE USER IS NOT A GHOST USER + .where({ isGhost: false }) .offset(offset); if (limit) { diff --git a/backend/src/ee/services/license/__mocks__/licence-fns.ts b/backend/src/ee/services/license/__mocks__/licence-fns.ts index 9e7732724..b5cbf103e 100644 --- a/backend/src/ee/services/license/__mocks__/licence-fns.ts +++ b/backend/src/ee/services/license/__mocks__/licence-fns.ts @@ -12,15 +12,15 @@ export const getDefaultOnPremFeatures = () => { secretVersioning: true, pitRecovery: false, ipAllowlisting: true, - rbac: true, + rbac: false, customRateLimits: false, customAlerts: false, auditLogs: false, auditLogsRetentionDays: 0, - samlSSO: true, - scim: true, + samlSSO: false, + scim: false, ldap: false, - groups: true, + groups: false, status: null, trial_end: null, has_used_trial: true, diff --git a/backend/src/ee/services/license/licence-fns.ts b/backend/src/ee/services/license/licence-fns.ts index 3e0203644..8a4de57f1 100644 --- a/backend/src/ee/services/license/licence-fns.ts +++ b/backend/src/ee/services/license/licence-fns.ts @@ -19,15 +19,15 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ secretVersioning: true, pitRecovery: false, ipAllowlisting: false, - rbac: true, + rbac: false, customRateLimits: false, customAlerts: false, auditLogs: false, auditLogsRetentionDays: 0, - samlSSO: true, - scim: true, + samlSSO: false, + scim: false, ldap: false, - groups: true, + groups: false, status: null, trial_end: null, has_used_trial: true, diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index ceebfd219..1cea39a83 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -35,15 +35,15 @@ export type TFeatureSet = { secretVersioning: true; pitRecovery: false; ipAllowlisting: false; - rbac: true; + rbac: false; customRateLimits: false; customAlerts: false; auditLogs: false; auditLogsRetentionDays: 0; - samlSSO: true; - scim: true; + samlSSO: false; + scim: false; ldap: false; - groups: true; + groups: false; status: null; trial_end: null; has_used_trial: true; diff --git a/backend/src/services/group-project/group-project-service.ts b/backend/src/services/group-project/group-project-service.ts index 3a59e6615..589d0e474 100644 --- a/backend/src/services/group-project/group-project-service.ts +++ b/backend/src/services/group-project/group-project-service.ts @@ -92,8 +92,8 @@ export const groupProjectServiceFactory = ({ role, project.id ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) + const hasPrivilege = isAtLeastAsPrivileged(permission, rolePermission); + if (!hasPrivilege) throw new ForbiddenRequestError({ message: "Failed to add group to project with more privileged role" }); @@ -226,7 +226,7 @@ export const groupProjectServiceFactory = ({ const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug); - const santiziedProjectMembershipRoles = roles.map((inputRole) => { + const sanitizedProjectMembershipRoles = roles.map((inputRole) => { const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]); if (!inputRole.isTemporary) { return { @@ -252,7 +252,7 @@ export const groupProjectServiceFactory = ({ const updatedRoles = await groupProjectMembershipRoleDAL.transaction(async (tx) => { await groupProjectMembershipRoleDAL.delete({ projectMembershipId: projectGroup.id }, tx); - return groupProjectMembershipRoleDAL.insertMany(santiziedProjectMembershipRoles, tx); + return groupProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx); }); return updatedRoles; @@ -325,8 +325,8 @@ export const groupProjectServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Groups); - const groupMemberhips = await groupProjectDAL.findByProjectId(project.id); - return groupMemberhips; + const groupMemberships = await groupProjectDAL.findByProjectId(project.id); + return groupMemberships; }; return { diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index 6eb0b7a2a..2da2492c0 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -163,7 +163,7 @@ export const identityProjectServiceFactory = ({ const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug); - const santiziedProjectMembershipRoles = roles.map((inputRole) => { + const sanitizedProjectMembershipRoles = roles.map((inputRole) => { const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]); if (!inputRole.isTemporary) { return { @@ -189,7 +189,7 @@ export const identityProjectServiceFactory = ({ const updatedRoles = await identityProjectMembershipRoleDAL.transaction(async (tx) => { await identityProjectMembershipRoleDAL.delete({ projectMembershipId: projectIdentity.id }, tx); - return identityProjectMembershipRoleDAL.insertMany(santiziedProjectMembershipRoles, tx); + return identityProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx); }); return updatedRoles; @@ -226,7 +226,7 @@ export const identityProjectServiceFactory = ({ if (!hasRequiredPriviledges) throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" }); - const [deletedIdentity] = await identityProjectDAL.delete({ identityId }); // TODO: fix + const [deletedIdentity] = await identityProjectDAL.delete({ identityId }); return deletedIdentity; }; @@ -246,8 +246,8 @@ export const identityProjectServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity); - const identityMemberhips = await identityProjectDAL.findByProjectId(projectId); - return identityMemberhips; + const identityMemberships = await identityProjectDAL.findByProjectId(projectId); + return identityMemberships; }; return { diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index cdea45631..2863bf23e 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -157,8 +157,8 @@ export const identityServiceFactory = ({ const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); - const identityMemberhips = await identityOrgMembershipDAL.findByOrgId(orgId); - return identityMemberhips; + const identityMemberships = await identityOrgMembershipDAL.findByOrgId(orgId); + return identityMemberships; }; return { diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index 74584910a..becde2b72 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -362,7 +362,7 @@ export const projectMembershipServiceFactory = ({ if (customRoles.length !== customInputRoles.length) throw new BadRequestError({ message: "Custom role not found" }); const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug); - const santiziedProjectMembershipRoles = roles.map((inputRole) => { + const sanitizedProjectMembershipRoles = roles.map((inputRole) => { const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]); if (!inputRole.isTemporary) { return { @@ -388,7 +388,7 @@ export const projectMembershipServiceFactory = ({ const updatedRoles = await projectMembershipDAL.transaction(async (tx) => { await projectUserMembershipRoleDAL.delete({ projectMembershipId: membershipId }, tx); - return projectUserMembershipRoleDAL.insertMany(santiziedProjectMembershipRoles, tx); + return projectUserMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx); }); return updatedRoles; diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/workspace/index.tsx index 876a2db6c..b0cadac23 100644 --- a/frontend/src/hooks/api/workspace/index.tsx +++ b/frontend/src/hooks/api/workspace/index.tsx @@ -1,9 +1,12 @@ export { useAddGroupToWorkspace, + useDeleteGroupFromWorkspace, + useUpdateGroupWorkspaceRole +} from "./mutations"; +export { useAddIdentityToWorkspace, useCreateWorkspace, useCreateWsEnvironment, - useDeleteGroupFromWorkspace, useDeleteIdentityFromWorkspace, useDeleteUserFromWorkspace, useDeleteWorkspace, @@ -22,8 +25,8 @@ export { useNameWorkspaceSecrets, useRenameWorkspace, useToggleAutoCapitalization, - useUpdateGroupWorkspaceRole, useUpdateIdentityWorkspaceRole, useUpdateUserWorkspaceRole, useUpdateWsEnvironment, - useUpgradeProject} from "./queries"; + useUpgradeProject +} from "./queries"; diff --git a/frontend/src/hooks/api/workspace/mutations.tsx b/frontend/src/hooks/api/workspace/mutations.tsx new file mode 100644 index 000000000..11853157f --- /dev/null +++ b/frontend/src/hooks/api/workspace/mutations.tsx @@ -0,0 +1,64 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { workspaceKeys } from "./queries"; +import { TUpdateWorkspaceGroupRoleDTO } from "./types"; + +export const useAddGroupToWorkspace = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + groupSlug, + projectSlug, + role + }: { + groupSlug: string; + projectSlug: string; + role?: string; + }) => { + const { + data: { groupMembership } + } = await apiRequest.post(`/api/v2/workspace/${projectSlug}/groups/${groupSlug}`, { + role + }); + return groupMembership; + }, + onSuccess: (_, { projectSlug }) => { + queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug)); + } + }); +}; + +export const useUpdateGroupWorkspaceRole = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ groupSlug, projectSlug, roles }: TUpdateWorkspaceGroupRoleDTO) => { + const { + data: { groupMembership } + } = await apiRequest.patch(`/api/v2/workspace/${projectSlug}/groups/${groupSlug}`, { + roles + }); + + return groupMembership; + }, + onSuccess: (_, { projectSlug }) => { + queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug)); + } + }); +}; + +export const useDeleteGroupFromWorkspace = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ groupSlug, projectSlug }: { groupSlug: string; projectSlug: string }) => { + const { + data: { groupMembership } + } = await apiRequest.delete(`/api/v2/workspace/${projectSlug}/groups/${groupSlug}`); + return groupMembership; + }, + onSuccess: (_, { projectSlug }) => { + queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug)); + } + }); +}; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index d8600c4cf..cca544df3 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -17,7 +17,6 @@ import { RenameWorkspaceDTO, TGetUpgradeProjectStatusDTO, ToggleAutoCapitalizationDTO, - TUpdateWorkspaceGroupRoleDTO, TUpdateWorkspaceIdentityRoleDTO, TUpdateWorkspaceUserRoleDTO, UpdateEnvironmentDTO, @@ -455,83 +454,12 @@ export const useGetWorkspaceIdentityMemberships = (workspaceId: string) => { }); }; -export const useAddGroupToWorkspace = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async ({ - groupSlug, - projectSlug, - role - }: { - groupSlug: string; - projectSlug: string; - role?: string; - }) => { - const { - data: { groupMembership } - } = await apiRequest.post( - `/api/v2/workspace/${projectSlug}/groups/${groupSlug}`, - { - role - } - ); - return groupMembership; - }, - onSuccess: (_, { projectSlug }) => { - queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug)); - } - }); -}; - -export const useUpdateGroupWorkspaceRole = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async ({ groupSlug, projectSlug, roles }: TUpdateWorkspaceGroupRoleDTO) => { - const { - data: { groupMembership } - } = await apiRequest.patch( - `/api/v2/workspace/${projectSlug}/groups/${groupSlug}`, { - roles - } - ); - - return groupMembership; - }, - onSuccess: (_, { projectSlug }) => { - queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug)); - } - }); -}; - -export const useDeleteGroupFromWorkspace = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async ({ - groupSlug, - projectSlug - }: { - groupSlug: string; - projectSlug: string; - }) => { - const { - data: { groupMembership } - } = await apiRequest.delete( - `/api/v2/workspace/${projectSlug}/groups/${groupSlug}` - ); - return groupMembership; - }, - onSuccess: (_, { projectSlug }) => { - queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug)); - } - }); -}; - export const useListWorkspaceGroups = (projectSlug: string) => { return useQuery({ queryKey: workspaceKeys.getWorkspaceGroupMemberships(projectSlug), queryFn: async () => { const { - data: { groupMemberships } + data: { groupMemberships } } = await apiRequest.get<{ groupMemberships: TGroupMembership[] }>( `/api/v2/workspace/${projectSlug}/groups` ); diff --git a/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx b/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx index 3e1965f76..7791eaa06 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx @@ -4,19 +4,23 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Input, Modal, ModalContent, Select, SelectItem } from "@app/components/v2"; -import { useOrganization } from "@app/context"; import { - useCreateGroup, - useGetOrgRoles, - useUpdateGroup -} from "@app/hooks/api"; + Button, + FormControl, + Input, + Modal, + ModalContent, + Select, + SelectItem +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { useCreateGroup, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; const GroupFormSchema = z.object({ - name: z.string().min(1, "Name cannot be empty").max(50, "Name must be 50 characters or fewer"), - slug: z.string().min(1, "Slug cannot be empty").max(36, "Slug must be 36 characters or fewer"), - role: z.string() + name: z.string().min(1, "Name cannot be empty").max(50, "Name must be 50 characters or fewer"), + slug: z.string().min(5, "Slug cannot be empty").max(36, "Slug must be 36 characters or fewer"), + role: z.string() }); export type TGroupFormData = z.infer; @@ -27,185 +31,158 @@ type Props = { handlePopUpToggle: (popUpName: keyof UsePopUpState<["group"]>, state?: boolean) => void; }; -export const OrgGroupModal = ({ - popUp, - handlePopUpClose, - handlePopUpToggle -}: Props) => { - const { currentOrg } = useOrganization(); - const { data: roles } = useGetOrgRoles(currentOrg?.id || ""); - const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateGroup(); - const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateGroup(); - - const { - control, - handleSubmit, - reset, - } = useForm({ - resolver: zodResolver(GroupFormSchema) - }); - - useEffect(() => { - const group = popUp?.group?.data as { - groupId: string; - name: string; - slug: string; - role: string; - customRole: { - name: string; - slug: string; - }; - }; +export const OrgGroupModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => { + const { currentOrg } = useOrganization(); + const { data: roles } = useGetOrgRoles(currentOrg?.id || ""); + const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateGroup(); + const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateGroup(); - if (!roles?.length) return; + const { control, handleSubmit, reset } = useForm({ + resolver: zodResolver(GroupFormSchema) + }); - if (group) { - reset({ - name: group.name, - slug: group.slug, - role: group?.customRole?.slug ?? group.role - }); - } else { - reset({ - name: "", - slug: "", - role: roles[0].slug - }); - } - }, [popUp?.group?.data, roles]); - - const onGroupModalSubmit = async ({ - name, - slug, - role - }: TGroupFormData) => { - try { - if (!currentOrg?.id) return; + useEffect(() => { + const group = popUp?.group?.data as { + groupId: string; + name: string; + slug: string; + role: string; + customRole: { + name: string; + slug: string; + }; + }; - const group = popUp?.group?.data as { - groupId: string; - name: string; - slug: string; - }; - - if (group) { - await updateMutateAsync({ - currentSlug: group.slug, - name, - slug, - role: role || undefined - }); - } else { - await createMutateAsync({ - name, - slug, - organizationId: currentOrg.id, - role: role || undefined - }); - } - handlePopUpToggle("group", false); - reset(); - - createNotification({ - text: `Successfully ${popUp?.group?.data ? "updated" : "created"} group`, - type: "success" - }); - } catch (err) { - createNotification({ - text: `Failed to ${popUp?.group?.data ? "updated" : "created"} group`, - type: "error" - }); - } + if (!roles?.length) return; + + if (group) { + reset({ + name: group.name, + slug: group.slug, + role: group?.customRole?.slug ?? group.role + }); + } else { + reset({ + name: "", + slug: "", + role: roles[0].slug + }); } - - return ( - { - handlePopUpToggle("group", isOpen); - reset(); - }} - > - -
- ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> -
- - -
- -
-
- - ); -} \ No newline at end of file + }, [popUp?.group?.data, roles]); + + const onGroupModalSubmit = async ({ name, slug, role }: TGroupFormData) => { + try { + if (!currentOrg?.id) return; + + const group = popUp?.group?.data as { + groupId: string; + name: string; + slug: string; + }; + + if (group) { + await updateMutateAsync({ + currentSlug: group.slug, + name, + slug, + role: role || undefined + }); + } else { + await createMutateAsync({ + name, + slug, + organizationId: currentOrg.id, + role: role || undefined + }); + } + handlePopUpToggle("group", false); + reset(); + + createNotification({ + text: `Successfully ${popUp?.group?.data ? "updated" : "created"} group`, + type: "success" + }); + } catch (err) { + createNotification({ + text: `Failed to ${popUp?.group?.data ? "updated" : "created"} group`, + type: "error" + }); + } + }; + + return ( + { + handlePopUpToggle("group", isOpen); + reset(); + }} + > + +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> +
+ + +
+ +
+
+ ); +}; diff --git a/frontend/src/views/Project/MembersPage/MembersPage.tsx b/frontend/src/views/Project/MembersPage/MembersPage.tsx index af4b46d4b..edd03fade 100644 --- a/frontend/src/views/Project/MembersPage/MembersPage.tsx +++ b/frontend/src/views/Project/MembersPage/MembersPage.tsx @@ -5,7 +5,13 @@ import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { withProjectPermission } from "@app/hoc"; -import { GroupsTab, IdentityTab, MemberListTab, ProjectRoleListTab, ServiceTokenTab } from "./components"; +import { + GroupsTab, + IdentityTab, + MemberListTab, + ProjectRoleListTab, + ServiceTokenTab +} from "./components"; enum TabSections { Member = "members", @@ -18,8 +24,6 @@ enum TabSections { export const MembersPage = withProjectPermission( () => { const { currentWorkspace } = useWorkspace(); - console.log("currentWorkspace: ", currentWorkspace); - return (
@@ -44,7 +48,7 @@ export const MembersPage = withProjectPermission( {currentWorkspace?.version && currentWorkspace.version > 1 && (