From 3888a7d7e88aae74d122a088acac3277286d8ca4 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 23 Jun 2023 14:22:16 +0800 Subject: [PATCH 1/5] Fix permissions hook in org settings page --- .../interfaces/Auth/Users/AddUserDropdown.tsx | 6 ++-- .../TeamSettings/TeamSettings.tsx | 7 +++- .../TeamSettings/TeamSettings.utils.ts | 32 +++++++++++++------ studio/data/permissions/permissions-query.ts | 4 +-- studio/hooks/misc/useCheckPermissions.ts | 19 ++++++----- studio/hooks/misc/withAuth.tsx | 1 - 6 files changed, 45 insertions(+), 24 deletions(-) diff --git a/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx b/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx index 3cacf12808d..6cbdd654104 100644 --- a/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx +++ b/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx @@ -1,6 +1,6 @@ import * as Tooltip from '@radix-ui/react-tooltip' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions } from 'hooks' +import { useCheckPermissions } from 'hooks' import { IS_PLATFORM } from 'lib/constants' import { useState } from 'react' import semver from 'semver' @@ -21,8 +21,8 @@ const AddUserDropdown = ({ projectKpsVersion }: AddUserDropdownProps) => { ) : true - const canInviteUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user') - const canCreateUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'create_user') + const canInviteUsers = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user') + const canCreateUsers = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'create_user') const [inviteVisible, setInviteVisible] = useState(false) const [createVisible, setCreateVisible] = useState(false) diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx index fcb76dfc531..6ef55384e79 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx @@ -6,6 +6,7 @@ import { useParams } from 'common/hooks' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query' import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' +import { usePermissionsQuery } from 'data/permissions/permissions-query' import { useProfileQuery } from 'data/profile/profile-query' import { useSelectedOrganization, useStore } from 'hooks' import { delete_ } from 'lib/common/fetch' @@ -22,13 +23,17 @@ const TeamSettings = () => { const selectedOrganization = useSelectedOrganization() const isOwner = selectedOrganization?.is_owner + const { data: permissions } = usePermissionsQuery() const { data: detailData } = useOrganizationDetailQuery({ slug }) const { data: rolesData } = useOrganizationRolesQuery({ slug }) const members = detailData?.members ?? [] const roles = rolesData?.roles ?? [] - const { rolesAddable } = getRolesManagementPermissions(roles) + const { rolesAddable } = + selectedOrganization !== undefined + ? getRolesManagementPermissions(selectedOrganization?.id, roles, permissions ?? []) + : { rolesAddable: [] } const [isLeaving, setIsLeaving] = useState(false) const [searchString, setSearchString] = useState('') diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts index 9651dacc86c..c25f2235246 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts @@ -1,23 +1,37 @@ -import { Member, Role } from 'types' -import { checkPermissions } from 'hooks' +import { Member, Permission, Role } from 'types' +import { useCheckPermissions } from 'hooks' import { PermissionAction } from '@supabase/shared-types/out/constants' export const getRolesManagementPermissions = ( - roles: Role[] + orgId: number, + roles: Role[], + permissions: Permission[] ): { rolesAddable: Number[]; rolesRemovable: Number[] } => { const rolesAddable: Number[] = [] const rolesRemovable: Number[] = [] if (!roles) return { rolesAddable, rolesRemovable } roles.forEach((role: Role) => { - const canAdd = checkPermissions(PermissionAction.CREATE, 'auth.subject_roles', { - resource: { role_id: role.id }, - }) + const canAdd = useCheckPermissions( + PermissionAction.CREATE, + 'auth.subject_roles', + { + resource: { role_id: role.id }, + }, + orgId, + permissions + ) if (canAdd) rolesAddable.push(role.id) - const canRemove = checkPermissions(PermissionAction.DELETE, 'auth.subject_roles', { - resource: { role_id: role.id }, - }) + const canRemove = useCheckPermissions( + PermissionAction.DELETE, + 'auth.subject_roles', + { + resource: { role_id: role.id }, + }, + orgId, + permissions + ) if (canRemove) rolesRemovable.push(role.id) }) diff --git a/studio/data/permissions/permissions-query.ts b/studio/data/permissions/permissions-query.ts index 0dff6750374..8b714c7b4f1 100644 --- a/studio/data/permissions/permissions-query.ts +++ b/studio/data/permissions/permissions-query.ts @@ -1,6 +1,6 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' import { get } from 'lib/common/fetch' -import { API_URL } from 'lib/constants' +import { API_URL, IS_PLATFORM } from 'lib/constants' import { useCallback } from 'react' import { Permission } from 'types' import { permissionKeys } from './keys' @@ -27,7 +27,7 @@ export const usePermissionsQuery = ( useQuery( permissionKeys.list(), ({ signal }) => getPermissions(signal), - options + { enabled: IS_PLATFORM && options.enabled, ...options } ) export const usePermissionsPrefetch = () => { diff --git a/studio/hooks/misc/useCheckPermissions.ts b/studio/hooks/misc/useCheckPermissions.ts index 300285def31..4ee6264d889 100644 --- a/studio/hooks/misc/useCheckPermissions.ts +++ b/studio/hooks/misc/useCheckPermissions.ts @@ -3,6 +3,7 @@ import jsonLogic from 'json-logic-js' import { usePermissionsQuery } from 'data/permissions/permissions-query' import { IS_PLATFORM } from 'lib/constants' import { useSelectedOrganization } from './useSelectedOrganization' +import { Permission } from 'types' const toRegexpString = (actionOrResource: string) => `^${actionOrResource.replace('.', '\\.').replace('%', '.*')}$` @@ -11,18 +12,20 @@ export function useCheckPermissions( action: string, resource: string, data?: object, - organizationId?: number + // [Joshen] Pass the variables if you want to avoid hooks in this + // e.g If you want to use useCheckPermissions in a loop like organization settings + organizationId?: number, + permissions?: Permission[] ) { - const { data: permissions } = usePermissionsQuery({ - enabled: IS_PLATFORM, - }) - const organization = useSelectedOrganization() - if (!IS_PLATFORM) return true - const orgId = organizationId ?? organization?.id + const { data: allPermissions } = + permissions === undefined ? usePermissionsQuery() : { data: permissions } + const organization = + organizationId === undefined ? useSelectedOrganization() : { id: organizationId } + const orgId = organization?.id - return (permissions ?? []) + return (allPermissions ?? []) .filter( (permission) => permission.organization_id === orgId && diff --git a/studio/hooks/misc/withAuth.tsx b/studio/hooks/misc/withAuth.tsx index 3c0216cc2b8..7cf5706b7ff 100644 --- a/studio/hooks/misc/withAuth.tsx +++ b/studio/hooks/misc/withAuth.tsx @@ -41,7 +41,6 @@ export function withAuth( const redirectIfFound = options?.redirectIfFound usePermissionsQuery({ - enabled: IS_PLATFORM, onSuccess(permissions) { ui.setPermissions(permissions) }, From 797625a745731be41955ae2daed95f0f9e820e34 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 23 Jun 2023 14:27:48 +0800 Subject: [PATCH 2/5] Apply fix to other areas --- .../Organization/TeamSettings/MemberActions.tsx | 10 ++++++++-- .../Organization/TeamSettings/MembersView.tsx | 10 ++++++++-- .../Organization/TeamSettings/TeamSettings.tsx | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx index 099999fe0a6..0defdff3844 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx @@ -5,7 +5,7 @@ import { FC } from 'react' import { Button, Dropdown, IconMoreHorizontal, IconTrash } from 'ui' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' -import { checkPermissions, useStore } from 'hooks' +import { checkPermissions, useSelectedOrganization, useStore } from 'hooks' import { useParams } from 'common/hooks' import { Member, Role } from 'types' @@ -14,6 +14,7 @@ import { useOrganizationMemberInviteCreateMutation } from 'data/organizations/or import { useOrganizationMemberInviteDeleteMutation } from 'data/organizations/organization-member-invite-delete-mutation' import { isInviteExpired } from '../Organization.utils' import { getRolesManagementPermissions } from './TeamSettings.utils' +import { usePermissionsQuery } from 'data/permissions/permissions-query' interface Props { member: Member @@ -23,7 +24,12 @@ interface Props { const MemberActions: FC = ({ member, roles }) => { const { ui } = useStore() const { slug } = useParams() - const { rolesRemovable } = getRolesManagementPermissions(roles) + const selectedOrganization = useSelectedOrganization() + const { data: permissions } = usePermissionsQuery() + const { rolesRemovable } = + selectedOrganization !== undefined + ? getRolesManagementPermissions(selectedOrganization.id, roles, permissions ?? []) + : { rolesRemovable: [] as number[] } const isExpired = isInviteExpired(member?.invited_at ?? '') const isPendingInviteAcceptance = member.invited_id diff --git a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx index 1727cb59eaa..feba9bd2b2f 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite' import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle, IconLoader } from 'ui' import { Member } from 'types' -import { useStore } from 'hooks' +import { useSelectedOrganization, useStore } from 'hooks' import { useParams } from 'common/hooks' import ShimmeringLoader from 'components/ui/ShimmeringLoader' @@ -18,6 +18,7 @@ import { useOrganizationRolesQuery } from 'data/organizations/organization-roles import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query' import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation' import { useProfileQuery } from 'data/profile/profile-query' +import { usePermissionsQuery } from 'data/permissions/permissions-query' interface SelectedMember extends Member { oldRoleId: number @@ -31,8 +32,10 @@ export interface MembersViewProps { const MembersView = ({ searchString }: MembersViewProps) => { const { ui } = useStore() const { slug } = useParams() + const selectedOrganization = useSelectedOrganization() const { data: profile } = useProfileQuery() + const { data: permissions } = usePermissionsQuery() const { data: detailData, isLoading: isLoadingOrgDetails } = useOrganizationDetailQuery({ slug }) const { data: rolesData, isLoading: isLoadingRoles } = useOrganizationRolesQuery({ slug }) const { mutate: updateOrganizationMember, isLoading } = useOrganizationMemberUpdateMutation({ @@ -52,7 +55,10 @@ const MembersView = ({ searchString }: MembersViewProps) => { const roles = rolesData?.roles ?? [] const members = detailData?.members ?? [] - const { rolesAddable, rolesRemovable } = getRolesManagementPermissions(roles) + const { rolesAddable, rolesRemovable } = + selectedOrganization !== undefined + ? getRolesManagementPermissions(selectedOrganization.id, roles, permissions ?? []) + : { rolesAddable: [] as number[], rolesRemovable: [] as number[] } const [selectedMember, setSelectedMember] = useState() const [userRoleChangeModalVisible, setUserRoleChangeModalVisible] = useState(false) diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx index 6ef55384e79..1c2c4704995 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx @@ -33,7 +33,7 @@ const TeamSettings = () => { const { rolesAddable } = selectedOrganization !== undefined ? getRolesManagementPermissions(selectedOrganization?.id, roles, permissions ?? []) - : { rolesAddable: [] } + : { rolesAddable: [] as number[] } const [isLeaving, setIsLeaving] = useState(false) const [searchString, setSearchString] = useState('') From 5d4325b63202c63d6dc20522a41bd164a0181e3d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 23 Jun 2023 17:23:21 +0800 Subject: [PATCH 3/5] Feedback --- studio/data/permissions/permissions-query.ts | 2 +- studio/hooks/misc/useCheckPermissions.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/studio/data/permissions/permissions-query.ts b/studio/data/permissions/permissions-query.ts index 8b714c7b4f1..fa7d16d5492 100644 --- a/studio/data/permissions/permissions-query.ts +++ b/studio/data/permissions/permissions-query.ts @@ -27,7 +27,7 @@ export const usePermissionsQuery = ( useQuery( permissionKeys.list(), ({ signal }) => getPermissions(signal), - { enabled: IS_PLATFORM && options.enabled, ...options } + { ...options, enabled: IS_PLATFORM && options.enabled } ) export const usePermissionsPrefetch = () => { diff --git a/studio/hooks/misc/useCheckPermissions.ts b/studio/hooks/misc/useCheckPermissions.ts index 4ee6264d889..771915f223c 100644 --- a/studio/hooks/misc/useCheckPermissions.ts +++ b/studio/hooks/misc/useCheckPermissions.ts @@ -17,14 +17,16 @@ export function useCheckPermissions( organizationId?: number, permissions?: Permission[] ) { - if (!IS_PLATFORM) return true - const { data: allPermissions } = + // eslint-disable-next-line react-hooks/rules-of-hooks permissions === undefined ? usePermissionsQuery() : { data: permissions } const organization = + // eslint-disable-next-line react-hooks/rules-of-hooks organizationId === undefined ? useSelectedOrganization() : { id: organizationId } const orgId = organization?.id + if (!IS_PLATFORM) return true + return (allPermissions ?? []) .filter( (permission) => From 8836a1b023784665d18e73ea9cf08a80b1cbc9af Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Fri, 23 Jun 2023 19:34:42 +1000 Subject: [PATCH 4/5] refactor useCheckPermissions hook to obey the rules of hooks --- studio/hooks/misc/useCheckPermissions.ts | 17 +++++++++++------ studio/hooks/misc/useSelectedOrganization.ts | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/studio/hooks/misc/useCheckPermissions.ts b/studio/hooks/misc/useCheckPermissions.ts index 771915f223c..d46f4a30b30 100644 --- a/studio/hooks/misc/useCheckPermissions.ts +++ b/studio/hooks/misc/useCheckPermissions.ts @@ -17,12 +17,17 @@ export function useCheckPermissions( organizationId?: number, permissions?: Permission[] ) { - const { data: allPermissions } = - // eslint-disable-next-line react-hooks/rules-of-hooks - permissions === undefined ? usePermissionsQuery() : { data: permissions } - const organization = - // eslint-disable-next-line react-hooks/rules-of-hooks - organizationId === undefined ? useSelectedOrganization() : { id: organizationId } + const permissionsResult = usePermissionsQuery({ + enabled: permissions === undefined, + }) + + const allPermissions = permissions === undefined ? permissionsResult.data : permissions + + const organizationResult = useSelectedOrganization({ + enabled: organizationId === undefined, + }) + + const organization = organizationId === undefined ? organizationResult : { id: organizationId } const orgId = organization?.id if (!IS_PLATFORM) return true diff --git a/studio/hooks/misc/useSelectedOrganization.ts b/studio/hooks/misc/useSelectedOrganization.ts index b63927fd14b..c2446380fc5 100644 --- a/studio/hooks/misc/useSelectedOrganization.ts +++ b/studio/hooks/misc/useSelectedOrganization.ts @@ -4,9 +4,9 @@ import { useMemo } from 'react' import { useSelectedProject } from './useSelectedProject' -export function useSelectedOrganization() { +export function useSelectedOrganization({ enabled = true } = {}) { const { slug } = useParams() - const { data } = useOrganizationsQuery() + const { data } = useOrganizationsQuery({ enabled }) const selectedProject = useSelectedProject() return useMemo(() => { From a59e45191d6654316c558dc070ec3a5da4dafd64 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Fri, 23 Jun 2023 20:07:29 +1000 Subject: [PATCH 5/5] change getRolesManagementPermissions to hook --- .../TeamSettings/MemberActions.tsx | 22 +++--- .../Organization/TeamSettings/MembersView.tsx | 34 ++++----- .../TeamSettings/TeamSettings.tsx | 11 +-- .../TeamSettings/TeamSettings.utils.ts | 32 ++++---- studio/hooks/misc/useCheckPermissions.ts | 76 +++++++++++++------ 5 files changed, 105 insertions(+), 70 deletions(-) diff --git a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx index 0defdff3844..0038fdf50ea 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx @@ -2,19 +2,18 @@ import * as Tooltip from '@radix-ui/react-tooltip' import { PermissionAction } from '@supabase/shared-types/out/constants' import { observer } from 'mobx-react-lite' import { FC } from 'react' -import { Button, Dropdown, IconMoreHorizontal, IconTrash } from 'ui' -import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' -import { checkPermissions, useSelectedOrganization, useStore } from 'hooks' import { useParams } from 'common/hooks' -import { Member, Role } from 'types' - +import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' import { useOrganizationMemberDeleteMutation } from 'data/organizations/organization-member-delete-mutation' import { useOrganizationMemberInviteCreateMutation } from 'data/organizations/organization-member-invite-create-mutation' import { useOrganizationMemberInviteDeleteMutation } from 'data/organizations/organization-member-invite-delete-mutation' -import { isInviteExpired } from '../Organization.utils' -import { getRolesManagementPermissions } from './TeamSettings.utils' import { usePermissionsQuery } from 'data/permissions/permissions-query' +import { checkPermissions, useSelectedOrganization, useStore } from 'hooks' +import { Member, Role } from 'types' +import { Button, Dropdown, IconMoreHorizontal, IconTrash } from 'ui' +import { isInviteExpired } from '../Organization.utils' +import { useGetRolesManagementPermissions } from './TeamSettings.utils' interface Props { member: Member @@ -26,10 +25,11 @@ const MemberActions: FC = ({ member, roles }) => { const { slug } = useParams() const selectedOrganization = useSelectedOrganization() const { data: permissions } = usePermissionsQuery() - const { rolesRemovable } = - selectedOrganization !== undefined - ? getRolesManagementPermissions(selectedOrganization.id, roles, permissions ?? []) - : { rolesRemovable: [] as number[] } + const { rolesRemovable } = useGetRolesManagementPermissions( + selectedOrganization?.id, + roles, + permissions ?? [] + ) const isExpired = isInviteExpired(member?.invited_at ?? '') const isPendingInviteAcceptance = member.invited_id diff --git a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx index feba9bd2b2f..89549bcc9c6 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx @@ -1,24 +1,23 @@ -import Image from 'next/image' import * as Tooltip from '@radix-ui/react-tooltip' -import { useState, Fragment } from 'react' import { observer } from 'mobx-react-lite' -import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle, IconLoader } from 'ui' +import Image from 'next/image' +import { Fragment, useState } from 'react' -import { Member } from 'types' -import { useSelectedOrganization, useStore } from 'hooks' import { useParams } from 'common/hooks' - -import ShimmeringLoader from 'components/ui/ShimmeringLoader' import Table from 'components/to-be-cleaned/Table' -import MemberActions from './MemberActions' -import RolesHelperModal from './RolesHelperModal/RolesHelperModal' -import { getRolesManagementPermissions } from './TeamSettings.utils' -import { isInviteExpired, getUserDisplayName } from '../Organization.utils' -import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query' import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation' -import { useProfileQuery } from 'data/profile/profile-query' +import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' import { usePermissionsQuery } from 'data/permissions/permissions-query' +import { useProfileQuery } from 'data/profile/profile-query' +import { useSelectedOrganization, useStore } from 'hooks' +import { Member } from 'types' +import { Badge, Button, IconAlertCircle, IconLoader, IconUser, Listbox, Loading, Modal } from 'ui' +import { getUserDisplayName, isInviteExpired } from '../Organization.utils' +import MemberActions from './MemberActions' +import RolesHelperModal from './RolesHelperModal/RolesHelperModal' +import { useGetRolesManagementPermissions } from './TeamSettings.utils' interface SelectedMember extends Member { oldRoleId: number @@ -55,10 +54,11 @@ const MembersView = ({ searchString }: MembersViewProps) => { const roles = rolesData?.roles ?? [] const members = detailData?.members ?? [] - const { rolesAddable, rolesRemovable } = - selectedOrganization !== undefined - ? getRolesManagementPermissions(selectedOrganization.id, roles, permissions ?? []) - : { rolesAddable: [] as number[], rolesRemovable: [] as number[] } + const { rolesAddable, rolesRemovable } = useGetRolesManagementPermissions( + selectedOrganization?.id, + roles, + permissions ?? [] + ) const [selectedMember, setSelectedMember] = useState() const [userRoleChangeModalVisible, setUserRoleChangeModalVisible] = useState(false) diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx index 1c2c4704995..600ce662774 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx @@ -13,7 +13,7 @@ import { delete_ } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import InviteMemberButton from './InviteMemberButton' import MembersView from './MembersView' -import { getRolesManagementPermissions, hasMultipleOwners } from './TeamSettings.utils' +import { hasMultipleOwners, useGetRolesManagementPermissions } from './TeamSettings.utils' const TeamSettings = () => { const { ui } = useStore() @@ -30,10 +30,11 @@ const TeamSettings = () => { const members = detailData?.members ?? [] const roles = rolesData?.roles ?? [] - const { rolesAddable } = - selectedOrganization !== undefined - ? getRolesManagementPermissions(selectedOrganization?.id, roles, permissions ?? []) - : { rolesAddable: [] as number[] } + const { rolesAddable } = useGetRolesManagementPermissions( + selectedOrganization?.id, + roles, + permissions ?? [] + ) const [isLeaving, setIsLeaving] = useState(false) const [searchString, setSearchString] = useState('') diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts index c25f2235246..a245ffacce2 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts @@ -1,36 +1,42 @@ -import { Member, Permission, Role } from 'types' -import { useCheckPermissions } from 'hooks' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { doPermissionsCheck, useGetPermissions } from 'hooks' +import { Member, Permission, Role } from 'types' -export const getRolesManagementPermissions = ( - orgId: number, - roles: Role[], - permissions: Permission[] +export const useGetRolesManagementPermissions = ( + orgId?: number, + roles?: Role[], + permissions?: Permission[] ): { rolesAddable: Number[]; rolesRemovable: Number[] } => { + const { permissions: allPermissions, organizationId } = useGetPermissions( + permissions, + orgId, + permissions !== undefined && orgId !== undefined + ) + const rolesAddable: Number[] = [] const rolesRemovable: Number[] = [] - if (!roles) return { rolesAddable, rolesRemovable } + if (!roles || !orgId) return { rolesAddable, rolesRemovable } roles.forEach((role: Role) => { - const canAdd = useCheckPermissions( + const canAdd = doPermissionsCheck( + allPermissions, PermissionAction.CREATE, 'auth.subject_roles', { resource: { role_id: role.id }, }, - orgId, - permissions + organizationId ) if (canAdd) rolesAddable.push(role.id) - const canRemove = useCheckPermissions( + const canRemove = doPermissionsCheck( + allPermissions, PermissionAction.DELETE, 'auth.subject_roles', { resource: { role_id: role.id }, }, - orgId, - permissions + organizationId ) if (canRemove) rolesRemovable.push(role.id) }) diff --git a/studio/hooks/misc/useCheckPermissions.ts b/studio/hooks/misc/useCheckPermissions.ts index d46f4a30b30..c54c0a1717f 100644 --- a/studio/hooks/misc/useCheckPermissions.ts +++ b/studio/hooks/misc/useCheckPermissions.ts @@ -2,12 +2,58 @@ import jsonLogic from 'json-logic-js' import { usePermissionsQuery } from 'data/permissions/permissions-query' import { IS_PLATFORM } from 'lib/constants' -import { useSelectedOrganization } from './useSelectedOrganization' import { Permission } from 'types' +import { useSelectedOrganization } from './useSelectedOrganization' const toRegexpString = (actionOrResource: string) => `^${actionOrResource.replace('.', '\\.').replace('%', '.*')}$` +export function doPermissionsCheck( + permissions: Permission[] | undefined, + action: string, + resource: string, + data?: object, + organizationId?: number +) { + return (permissions ?? []) + .filter( + (permission) => + permission.organization_id === organizationId && + permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) && + permission.resources.some((res) => resource.match(toRegexpString(res))) + ) + .some( + ({ condition }: { condition: jsonLogic.RulesLogic }) => + condition === null || jsonLogic.apply(condition, { resource_name: resource, ...data }) + ) +} + +export function useGetPermissions( + permissionsOverride?: Permission[], + organizationIdOverride?: number, + enabled = true +) { + const permissionsResult = usePermissionsQuery({ + enabled: permissionsOverride === undefined && enabled, + }) + + const permissions = + permissionsOverride === undefined ? permissionsResult.data : permissionsOverride + + const organizationResult = useSelectedOrganization({ + enabled: organizationIdOverride === undefined && enabled, + }) + + const organization = + organizationIdOverride === undefined ? organizationResult : { id: organizationIdOverride } + const organizationId = organization?.id + + return { + permissions, + organizationId, + } +} + export function useCheckPermissions( action: string, resource: string, @@ -17,32 +63,14 @@ export function useCheckPermissions( organizationId?: number, permissions?: Permission[] ) { - const permissionsResult = usePermissionsQuery({ - enabled: permissions === undefined, - }) - - const allPermissions = permissions === undefined ? permissionsResult.data : permissions - - const organizationResult = useSelectedOrganization({ - enabled: organizationId === undefined, - }) - - const organization = organizationId === undefined ? organizationResult : { id: organizationId } - const orgId = organization?.id + const { permissions: allPermissions, organizationId: orgId } = useGetPermissions( + permissions, + organizationId + ) if (!IS_PLATFORM) return true - return (allPermissions ?? []) - .filter( - (permission) => - permission.organization_id === orgId && - permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) && - permission.resources.some((res) => resource.match(toRegexpString(res))) - ) - .some( - ({ condition }: { condition: jsonLogic.RulesLogic }) => - condition === null || jsonLogic.apply(condition, { resource_name: resource, ...data }) - ) + return doPermissionsCheck(allPermissions, action, resource, data, orgId) } /**