mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 14:19:11 +03:00
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:
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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -41,7 +41,6 @@ export function withAuth<T>(
|
||||
const redirectIfFound = options?.redirectIfFound
|
||||
|
||||
usePermissionsQuery({
|
||||
enabled: IS_PLATFORM,
|
||||
onSuccess(permissions) {
|
||||
ui.setPermissions(permissions)
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user