Merge pull request #15299 from supabase/chore/fix-permissions-hook-in-org-settings-page

Fix permissions hook in org settings page
This commit is contained in:
Joshen Lim authored and GitHub committed 2023-06-26 15:54:35 +08:00
commit 81766cccaf
9 files changed
+127 -52

No files matched your search

@@ -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)
@@ -2,18 +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, 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 { 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 { getRolesManagementPermissions } from './TeamSettings.utils'
import { useGetRolesManagementPermissions } from './TeamSettings.utils'
interface Props {
member: Member
@@ -23,7 +23,13 @@ interface Props {
const MemberActions: FC<Props> = ({ member, roles }) => {
const { ui } = useStore()
const { slug } = useParams()
const { rolesRemovable } = getRolesManagementPermissions(roles)
const selectedOrganization = useSelectedOrganization()
const { data: permissions } = usePermissionsQuery()
const { rolesRemovable } = useGetRolesManagementPermissions(
selectedOrganization?.id,
roles,
permissions ?? []
)
const isExpired = isInviteExpired(member?.invited_at ?? '')
const isPendingInviteAcceptance = member.invited_id
@@ -1,23 +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 { 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 { 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
@@ -31,8 +31,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 +54,11 @@ const MembersView = ({ searchString }: MembersViewProps) => {
const roles = rolesData?.roles ?? []
const members = detailData?.members ?? []
const { rolesAddable, rolesRemovable } = getRolesManagementPermissions(roles)
const { rolesAddable, rolesRemovable } = useGetRolesManagementPermissions(
selectedOrganization?.id,
roles,
permissions ?? []
)
const [selectedMember, setSelectedMember] = useState<SelectedMember>()
const [userRoleChangeModalVisible, setUserRoleChangeModalVisible] = useState(false)
@@ -6,13 +6,14 @@ 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'
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()
@@ -22,13 +23,18 @@ 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 } = useGetRolesManagementPermissions(
selectedOrganization?.id,
roles,
permissions ?? []
)
const [isLeaving, setIsLeaving] = useState(false)
const [searchString, setSearchString] = useState('')
@@ -1,23 +1,43 @@
import { Member, Role } from 'types'
import { checkPermissions } from 'hooks'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { doPermissionsCheck, useGetPermissions } from 'hooks'
import { Member, Permission, Role } from 'types'
export const getRolesManagementPermissions = (
roles: Role[]
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 = checkPermissions(PermissionAction.CREATE, 'auth.subject_roles', {
resource: { role_id: role.id },
})
const canAdd = doPermissionsCheck(
allPermissions,
PermissionAction.CREATE,
'auth.subject_roles',
{
resource: { role_id: role.id },
},
organizationId
)
if (canAdd) rolesAddable.push(role.id)
const canRemove = checkPermissions(PermissionAction.DELETE, 'auth.subject_roles', {
resource: { role_id: role.id },
})
const canRemove = doPermissionsCheck(
allPermissions,
PermissionAction.DELETE,
'auth.subject_roles',
{
resource: { role_id: role.id },
},
organizationId
)
if (canRemove) rolesRemovable.push(role.id)
})
+2 -2
View File
@@ -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 = <TData = PermissionsData>(
useQuery<PermissionsData, PermissionsError, TData>(
permissionKeys.list(),
({ signal }) => getPermissions(signal),
options
{ ...options, enabled: IS_PLATFORM && options.enabled }
)
export const usePermissionsPrefetch = () => {
+49 -11
View File
@@ -2,30 +2,23 @@ import jsonLogic from 'json-logic-js'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { IS_PLATFORM } from 'lib/constants'
import { Permission } from 'types'
import { useSelectedOrganization } from './useSelectedOrganization'
const toRegexpString = (actionOrResource: string) =>
`^${actionOrResource.replace('.', '\\.').replace('%', '.*')}$`
export function useCheckPermissions(
export function doPermissionsCheck(
permissions: Permission[] | undefined,
action: string,
resource: string,
data?: object,
organizationId?: number
) {
const { data: permissions } = usePermissionsQuery({
enabled: IS_PLATFORM,
})
const organization = useSelectedOrganization()
if (!IS_PLATFORM) return true
const orgId = organizationId ?? organization?.id
return (permissions ?? [])
.filter(
(permission) =>
permission.organization_id === orgId &&
permission.organization_id === organizationId &&
permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) &&
permission.resources.some((res) => resource.match(toRegexpString(res)))
)
@@ -35,6 +28,51 @@ export function useCheckPermissions(
)
}
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,
data?: object,
// [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 { permissions: allPermissions, organizationId: orgId } = useGetPermissions(
permissions,
organizationId
)
if (!IS_PLATFORM) return true
return doPermissionsCheck(allPermissions, action, resource, data, orgId)
}
/**
* @deprecated use useCheckPermissions instead
*/
+2 -2
View File
@@ -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(() => {
-1
View File
@@ -41,7 +41,6 @@ export function withAuth<T>(
const redirectIfFound = options?.redirectIfFound
usePermissionsQuery({
enabled: IS_PLATFORM,
onSuccess(permissions) {
ui.setPermissions(permissions)
},