From 80bea0660e4e1671ec3224a9fe942e6129edecb6 Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 5 Oct 2026 14:24:02 +0700 Subject: [PATCH] refactor: complete replacing useAsyncCheckPermissions with V2 --- .../jwt-secret-keys-table/index.tsx | 2 +- .../interfaces/JwtSecrets/jwt-settings.tsx | 4 +- .../LogicalBackupCliInstructions.tsx | 25 ++--- .../ProjectLayout/PauseFailedState.tsx | 10 +- .../ProjectLayout/RestoreFailedState.tsx | 10 +- .../layouts/ProjectLayout/UnhealthyState.tsx | 8 +- .../TableEditorLayout/TableEditorLayout.tsx | 13 +-- .../TableEditorLayout/TableEditorMenu.tsx | 8 +- .../ui/AIAssistantPanel/AIOptInModal.tsx | 8 +- .../components/ui/AutoEnableRLSNotice.tsx | 8 +- .../ui/ProjectSettings/DisplayApiSettings.tsx | 10 +- .../ProjectSettings/ToggleLegacyApiKeys.tsx | 96 +++++++++---------- apps/studio/components/ui/SchemaSelector.tsx | 8 +- .../components/ui/UpgradePlanButton.tsx | 9 +- .../data/config/project-settings-v2-query.ts | 8 +- .../organization-customer-profile-query.ts | 9 +- .../organization-payment-methods-query.ts | 8 +- .../organization-tax-id-query.ts | 8 +- .../iceberg-wrapper-create-mutation.ts | 13 ++- .../s3-vectors-wrapper-create-mutation.ts | 8 +- .../data/subscriptions/org-balance-query.ts | 8 +- .../data/subscriptions/org-plans-query.ts | 8 +- .../subscriptions/org-subscription-query.ts | 8 +- .../data/table-rows/table-rows-count-query.ts | 8 +- apps/studio/hooks/forms/useAIOptInForm.ts | 8 +- .../hooks/misc/useCheckPermissionsV2.ts | 1 - .../pages/project/[ref]/auth/audit-logs.tsx | 8 +- .../studio/pages/project/[ref]/auth/hooks.tsx | 8 +- apps/studio/pages/project/[ref]/auth/mfa.tsx | 8 +- .../pages/project/[ref]/auth/performance.tsx | 8 +- .../pages/project/[ref]/auth/protection.tsx | 8 +- .../pages/project/[ref]/auth/rate-limits.tsx | 8 +- .../pages/project/[ref]/auth/sessions.tsx | 8 +- apps/studio/pages/project/[ref]/auth/smtp.tsx | 8 +- .../[ref]/auth/templates/[templateId].tsx | 25 +++-- .../project/[ref]/auth/templates/index.tsx | 8 +- .../pages/project/[ref]/auth/third-party.tsx | 8 +- .../project/[ref]/auth/url-configuration.tsx | 8 +- .../pages/project/[ref]/branches/index.tsx | 16 +--- .../project/[ref]/branches/merge-requests.tsx | 8 +- .../project/[ref]/database/backups/pitr.tsx | 8 +- .../[ref]/database/backups/scheduled.tsx | 8 +- .../project/[ref]/database/extensions.tsx | 8 +- .../project/[ref]/database/functions.tsx | 8 +- .../pages/project/[ref]/database/policies.tsx | 8 +- .../[ref]/database/publications/[id].tsx | 8 +- .../[ref]/database/publications/index.tsx | 8 +- .../[ref]/functions/[functionSlug]/code.tsx | 13 +-- .../[ref]/functions/[functionSlug]/index.tsx | 8 +- .../pages/project/[ref]/logs/auth-logs.tsx | 9 +- .../project/[ref]/observability/database.tsx | 23 ++--- .../project/[ref]/observability/index.tsx | 12 +-- .../project/[ref]/settings/api-keys/index.tsx | 7 +- .../project/[ref]/settings/jwt/index.tsx | 8 +- .../project/[ref]/settings/log-drains.tsx | 8 +- .../routes/project/$ref/database/triggers.tsx | 8 +- .../TeamSettings/InviteMemberButton.test.tsx | 24 ++--- .../[ref]/settings/api-keys/index.test.tsx | 5 +- .../project/[ref]/settings/jwt/index.test.tsx | 5 +- 59 files changed, 262 insertions(+), 375 deletions(-) diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/index.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/index.tsx index f071106503e..209ad971879 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/index.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/index.tsx @@ -65,7 +65,7 @@ export const JWTSecretKeysTable = () => { const showApiKeysLastUsed = useFlag('showApiKeysLastUsed') const { can: canReadAPIKeys, isLoading: isLoadingCanReadAPIKeys } = useAsyncCheckPermissionsV2( - FGA_PERMISSIONS.PROJECT.API_GATEWAY_KEYS_READ + FGA_PERMISSIONS.PROJECT.AUTH_SIGNING_KEYS_READ ) const now = useRef(new Date()).current const { diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx index f8fca088043..ddf23698da5 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx @@ -86,7 +86,7 @@ export const JWTSettings = () => { const [isRegeneratingKey, setIsGeneratingKey] = useState(false) const { can: canReadJWTSecret } = useAsyncCheckPermissionsV2( - FGA_PERMISSIONS.PROJECT.DATA_API_CONFIG_SECRET_READ + FGA_PERMISSIONS.PROJECT.AUTH_SIGNING_KEYS_READ ) const { can: canGenerateNewJWTSecret } = useAsyncCheckPermissionsV2( FGA_PERMISSIONS.PROJECT.ADMIN_WRITE @@ -97,7 +97,7 @@ export const JWTSettings = () => { const { mutateAsync: updateJwt, isPending: isSubmittingJwtSecretUpdateRequest } = useJwtSecretUpdateMutation() - const { can: canReadAPIKeys } = useAsyncCheckPermissionsV2(FGA_PERMISSIONS.PROJECT.API_GATEWAY_KEYS_READ) + const { can: canReadAPIKeys } = useAsyncCheckPermissionsV2(FGA_PERMISSIONS.PROJECT.AUTH_SIGNING_KEYS_READ) const { data: legacyKey, isPending } = useLegacyJWTSigningKeyQuery( { projectRef }, { enabled: IS_PLATFORM && canReadAPIKeys, retry: false } diff --git a/apps/studio/components/layouts/ProjectLayout/LogicalBackupCliInstructions.tsx b/apps/studio/components/layouts/ProjectLayout/LogicalBackupCliInstructions.tsx index 459b6dbcf1c..6f08c72632e 100644 --- a/apps/studio/components/layouts/ProjectLayout/LogicalBackupCliInstructions.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LogicalBackupCliInstructions.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { useRouter } from 'next/router' import { cn } from 'ui' @@ -14,9 +13,8 @@ import { import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { InlineLink } from '@/components/ui/InlineLink' import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' -import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' +import { FGA_PERMISSIONS, useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export type LogicalBackupCliInstructionsProps = { enabled?: boolean @@ -33,15 +31,8 @@ export const LogicalBackupCliInstructions = ({ }: LogicalBackupCliInstructionsProps) => { const router = useRouter() const { ref } = useParams() - const { data: project } = useSelectedProjectQuery() - const { can: canResetDbPassword } = useAsyncCheckPermissions( - PermissionAction.UPDATE, - 'projects', - { - resource: { - project_id: project?.id, - }, - } + const { can: canResetDbPassword } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_CONFIG_WRITE ) const { @@ -53,11 +44,11 @@ export const LogicalBackupCliInstructions = ({ const connectionUri = isSuccess && settings ? buildDirectPostgresConnectionUri({ - db_user: settings.db_user, - db_host: settings.db_host, - db_port: settings.db_port, - db_name: settings.db_name, - }) + db_user: settings.db_user, + db_host: settings.db_host, + db_port: settings.db_port, + db_name: settings.db_name, + }) : null const shellScript = connectionUri ? buildLogicalBackupShellScript(connectionUri) : '' diff --git a/apps/studio/components/layouts/ProjectLayout/PauseFailedState.tsx b/apps/studio/components/layouts/ProjectLayout/PauseFailedState.tsx index a242be1b432..2e057906a01 100644 --- a/apps/studio/components/layouts/ProjectLayout/PauseFailedState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/PauseFailedState.tsx @@ -1,4 +1,4 @@ -import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants' +import { SupportCategories } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { Download, MoreVertical, Trash } from 'lucide-react' import { useState } from 'react' @@ -24,8 +24,8 @@ import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip import { InlineLink } from '@/components/ui/InlineLink' import { useBackupDownloadMutation } from '@/data/database/backup-download-mutation' import { useDownloadableBackupQuery } from '@/data/database/backup-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const PauseFailedState = () => { const { ref } = useParams() @@ -33,9 +33,9 @@ export const PauseFailedState = () => { const [visible, setVisible] = useState(false) const [showCliBackup, setShowCliBackup] = useState(false) - const { can: canDeleteProject } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', { - resource: { project_id: project?.id }, - }) + const { can: canDeleteProject } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.ADMIN_WRITE + ) const { data, isPending: isLoadingBackups } = useDownloadableBackupQuery({ projectRef: ref }) const backups = data?.backups ?? [] diff --git a/apps/studio/components/layouts/ProjectLayout/RestoreFailedState.tsx b/apps/studio/components/layouts/ProjectLayout/RestoreFailedState.tsx index 0ee426f49c7..75c9c745788 100644 --- a/apps/studio/components/layouts/ProjectLayout/RestoreFailedState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/RestoreFailedState.tsx @@ -1,4 +1,4 @@ -import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants' +import { SupportCategories } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { Download, MoreVertical, Trash } from 'lucide-react' import { useState } from 'react' @@ -24,8 +24,8 @@ import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip import { InlineLink } from '@/components/ui/InlineLink' import { useBackupDownloadMutation } from '@/data/database/backup-download-mutation' import { useDownloadableBackupQuery } from '@/data/database/backup-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const RestoreFailedState = () => { const { ref } = useParams() @@ -33,9 +33,9 @@ export const RestoreFailedState = () => { const [visible, setVisible] = useState(false) const [showCliBackup, setShowCliBackup] = useState(false) - const { can: canDeleteProject } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', { - resource: { project_id: project?.id }, - }) + const { can: canDeleteProject } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.ADMIN_WRITE + ) const { data, isPending: isLoadingBackups } = useDownloadableBackupQuery({ projectRef: ref }) const backups = data?.backups ?? [] diff --git a/apps/studio/components/layouts/ProjectLayout/UnhealthyState.tsx b/apps/studio/components/layouts/ProjectLayout/UnhealthyState.tsx index 8372fd67c9d..932402f7902 100644 --- a/apps/studio/components/layouts/ProjectLayout/UnhealthyState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/UnhealthyState.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { AlertTriangle } from 'lucide-react' import { useRouter } from 'next/router' @@ -9,9 +8,9 @@ import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { useProjectDetailQuery, useSetProjectStatus } from '@/data/projects/project-detail-query' import { useProjectRestartMutation } from '@/data/projects/project-restart-mutation' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { PROJECT_STATUS } from '@/lib/constants' +import { useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export const UnhealthyState = () => { const router = useRouter() @@ -20,9 +19,8 @@ export const UnhealthyState = () => { const { setProjectStatus } = useSetProjectStatus() const [showConfirm, setShowConfirm] = useState(false) - const { can: canRestartProject } = useAsyncCheckPermissions( - PermissionAction.INFRA_EXECUTE, - 'reboot' + const { can: canRestartProject } = useAsyncCheckPermissionsV2( + 'project_operations_write' ) useProjectDetailQuery( diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index f9b7fb32cef..0debaf4ea8d 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { LOCAL_STORAGE_KEYS, useParams } from 'common' import { PropsWithChildren, useEffect } from 'react' @@ -8,8 +7,8 @@ import { useIsQueueOperationsEnabled } from '@/components/interfaces/Account/Pre import { BannerTableEditorQueueOperations } from '@/components/ui/BannerStack/Banners/BannerTableEditorQueueOperations' import { useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const TableEditorLayout = ({ children }: PropsWithChildren<{}>) => { const { ref } = useParams() @@ -21,14 +20,12 @@ export const TableEditorLayout = ({ children }: PropsWithChildren<{}>) => { false ) - const { can: canReadTables, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_READ, - 'tables' + const { can: canReadTables, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_READ ) - const { can: canWriteTables } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - 'tables' + const { can: canWriteTables } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_WRITE ) useEffect(() => { diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index be3a545b185..24cbeeaad81 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { keepPreviousData } from '@tanstack/react-query' import { useParams } from 'common' import { Filter, Plus } from 'lucide-react' @@ -31,7 +30,6 @@ import { ENTITY_TYPE } from '@/data/entity-types/entity-type-constants' import { useEntityTypesQuery } from '@/data/entity-types/entity-types-infinite-query' import { useTableApiAccessQuery } from '@/data/privileges/table-api-access-query' import { getTableEditor, useTableEditorQuery } from '@/data/table-editor/table-editor-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useLocalStorage } from '@/hooks/misc/useLocalStorage' import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' @@ -39,6 +37,7 @@ import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useShortcut } from '@/state/shortcuts/useShortcut' import { useTableEditorStateSnapshot } from '@/state/table-editor' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' type Sort = 'alphabetical' | 'grouped-alphabetical' @@ -95,9 +94,8 @@ export const TableEditorMenu = () => { { enabled: Boolean(selectedSchema && entityNames.length > 0) } ) - const { can: canCreateTables } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - 'tables' + const { can: canCreateTables } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_WRITE ) const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema }) diff --git a/apps/studio/components/ui/AIAssistantPanel/AIOptInModal.tsx b/apps/studio/components/ui/AIAssistantPanel/AIOptInModal.tsx index d1cf07b466a..99963ddc598 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIOptInModal.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIOptInModal.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useEffect } from 'react' import { Button, @@ -15,7 +14,7 @@ import { import { AIOptInLevelSelector } from '@/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector' import { useAIOptInForm } from '@/hooks/forms/useAIOptInForm' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' interface AIOptInModalProps { visible: boolean @@ -24,9 +23,8 @@ interface AIOptInModalProps { export const AIOptInModal = ({ visible, onCancel }: AIOptInModalProps) => { const { form, onSubmit, isUpdating, currentOptInLevel } = useAIOptInForm(onCancel) - const { can: canUpdateOrganization } = useAsyncCheckPermissions( - PermissionAction.UPDATE, - 'organizations' + const { can: canUpdateOrganization } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.ORGANIZATION.ADMIN_WRITE ) const onOpenChange = (open: boolean) => { diff --git a/apps/studio/components/ui/AutoEnableRLSNotice.tsx b/apps/studio/components/ui/AutoEnableRLSNotice.tsx index 2cb1070dc97..ed22f817f1b 100644 --- a/apps/studio/components/ui/AutoEnableRLSNotice.tsx +++ b/apps/studio/components/ui/AutoEnableRLSNotice.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { LOCAL_STORAGE_KEYS, useParams } from 'common' import { ShieldCheck, X } from 'lucide-react' import { useMemo, useState } from 'react' @@ -22,10 +21,10 @@ import { AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL } from '@/components/interfaces/Datab import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { useDatabaseEventTriggerCreateMutation } from '@/data/database-event-triggers/database-event-trigger-create-mutation' import { useDatabaseEventTriggersQuery } from '@/data/database-event-triggers/database-event-triggers-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useTrack } from '@/lib/telemetry/track' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const AutoEnableRLSNotice = ({ iconOnly }: { iconOnly?: boolean }) => { const { ref } = useParams() @@ -87,9 +86,8 @@ const CreateEnsureRLSTriggerDialog = ({ iconOnly }: { iconOnly?: boolean }) => { const [open, setOpen] = useState(false) - const { can: canCreateTriggers } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - 'triggers' + const { can: canCreateTriggers } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_WRITE ) const { mutate: createEventTrigger, isPending: isCreating } = diff --git a/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx b/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx index fd81395b8bb..537b320c369 100644 --- a/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx +++ b/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events' import { IS_PLATFORM, useFlag, useParams } from 'common' import { AlertCircle, Loader2 } from 'lucide-react' @@ -13,7 +12,7 @@ import Panel from '@/components/ui/Panel' import { useApiKeysLastUsedQuery } from '@/data/analytics/api-keys-last-used-query' import { useJwtSecretUpdatingStatusQuery } from '@/data/config/jwt-secret-updating-status-query' import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const DisplayApiSettings = ({ showTitle = true, @@ -38,9 +37,8 @@ export const DisplayApiSettings = ({ } = useJwtSecretUpdatingStatusQuery({ projectRef }) const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus - const { isLoading: isLoadingPermissions, can: canReadAPIKeys } = useAsyncCheckPermissions( - PermissionAction.READ, - 'service_api_keys' + const { isLoading: isLoadingPermissions, can: canReadAPIKeys } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.API_GATEWAY_KEYS_READ ) const isLoading = isProjectSettingsLoading || isLoadingPermissions @@ -207,7 +205,7 @@ export const DisplayApiSettings = ({ ? 'Updating JWT secret...' : (x?.api_key ?? 'You need additional permissions to view API keys') } - onChange={() => {}} + onChange={() => { }} /> diff --git a/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx b/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx index c17890c255b..4a02255b257 100644 --- a/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx +++ b/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { useState } from 'react' import { toast } from 'sonner' @@ -20,8 +19,8 @@ import { useToggleLegacyAPIKeysMutation } from '@/data/api-keys/legacy-api-key-t import { useLegacyAPIKeysStatusQuery } from '@/data/api-keys/legacy-api-keys-status-query' import { useLegacyJWTSigningKeyQuery } from '@/data/jwt-signing-keys/legacy-jwt-signing-key-query' import { useAuthorizedAppsQuery } from '@/data/oauth/authorized-apps-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const ToggleLegacyApiKeysPanel = () => { const { ref: projectRef } = useParams() @@ -30,10 +29,11 @@ export const ToggleLegacyApiKeysPanel = () => { const [isConfirmOpen, setIsConfirmOpen] = useState(false) const [isAppsWarningOpen, setIsAppsWarningOpen] = useState(false) - const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*') - const { can: canUpdateAPIKeys, isSuccess: isPermissionsSuccess } = useAsyncCheckPermissions( - PermissionAction.SECRETS_WRITE, - '*' + const { can: canReadAPIKeys } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_SIGNING_KEYS_READ + ) + const { can: canUpdateAPIKeys, isSuccess: isPermissionsSuccess } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_SIGNING_KEYS_WRITE ) const { data: legacyAPIKeysStatusData, isSuccess: isLegacyAPIKeysStatusSuccess } = @@ -63,25 +63,25 @@ export const ToggleLegacyApiKeysPanel = () => { const appsWarning = isAuthorizedAppsError ? { - title: 'Check your OAuth apps before continuing', - description: ( - <> - Disabling legacy API keys can break apps that integrate with Supabase. Before - continuing, check your organization's {oauthAppsLink} to ensure none of them depend on - the legacy API keys. - - ), - } + title: 'Check your OAuth apps before continuing', + description: ( + <> + Disabling legacy API keys can break apps that integrate with Supabase. Before + continuing, check your organization's {oauthAppsLink} to ensure none of them depend on + the legacy API keys. + + ), + } : { - title: 'Apps using Supabase may break', - description: ( - <> - Your project uses apps that integrate with Supabase. Disabling the legacy API keys is a - brand new feature and the apps you're using may not have added support for this yet. It - can cause them to stop functioning. Check your {oauthAppsLink} before continuing. - - ), - } + title: 'Apps using Supabase may break', + description: ( + <> + Your project uses apps that integrate with Supabase. Disabling the legacy API keys is a + brand new feature and the apps you're using may not have added support for this yet. It + can cause them to stop functioning. Check your {oauthAppsLink} before continuing. + + ), + } if (!(isLegacyAPIKeysStatusSuccess && isPermissionsSuccess)) { return null @@ -205,32 +205,32 @@ const ToggleApiKeysModal = ({ alert={ isLegacyKeysEnabled ? { - title: 'Ensure legacy keys are no longer in use before disabling', - description: ( - - Disabling anon and service_role keys while they are in - use will cause downtime for your application. Ensure they are no longer in use - before proceeding. If you have not created a publishable and at least one secret - API key, some dashboard functionality may become unavailable. -
-
- - This disables API keys when used in the apikey header. They remain - valid as a JWT. - + title: 'Ensure legacy keys are no longer in use before disabling', + description: ( + + Disabling anon and service_role keys while they are in + use will cause downtime for your application. Ensure they are no longer in use + before proceeding. If you have not created a publishable and at least one secret + API key, some dashboard functionality may become unavailable. +
+
+ + This disables API keys when used in the apikey header. They remain + valid as a JWT. - ), - } +
+ ), + } : { - title: 'Publishable and secret keys are preferred', - description: ( - - Re-enabling anon and service_role keys may be - appropriate in certain cases, but using a publishable and secret key is more - secure. We recommend against re-enabling legacy API keys. - - ), - } + title: 'Publishable and secret keys are preferred', + description: ( + + Re-enabling anon and service_role keys may be + appropriate in certain cases, but using a publishable and secret key is more + secure. We recommend against re-enabling legacy API keys. + + ), + } } /> ) diff --git a/apps/studio/components/ui/SchemaSelector.tsx b/apps/studio/components/ui/SchemaSelector.tsx index cc1fd8a5fff..47b89f499b0 100644 --- a/apps/studio/components/ui/SchemaSelector.tsx +++ b/apps/studio/components/ui/SchemaSelector.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { Check, Plus } from 'lucide-react' import { ComponentPropsWithoutRef, forwardRef, useMemo, useState } from 'react' import { @@ -23,9 +22,9 @@ import { import { RestartProjectDialog } from '@/components/interfaces/ErrorHandling/RestartProjectDialog' import { useSchemasQuery } from '@/data/database/schemas-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSchemasFilteredForHighAvailability } from '@/hooks/misc/useHighAvailability' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' type SchemaSelectorProps = Omit, 'onSelect'> & { disabled?: boolean @@ -72,9 +71,8 @@ export const SchemaSelector = forwardRef( if (!isControlled) setInternalOpen(next) onOpenChange?.(next) } - const { can: canCreateSchemas } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - 'schemas' + const { can: canCreateSchemas } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_WRITE ) const { data: project } = useSelectedProjectQuery() diff --git a/apps/studio/components/ui/UpgradePlanButton.tsx b/apps/studio/components/ui/UpgradePlanButton.tsx index 2fe24002eff..91ac12c08ca 100644 --- a/apps/studio/components/ui/UpgradePlanButton.tsx +++ b/apps/studio/components/ui/UpgradePlanButton.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useFlag, useParams } from 'common' import Link from 'next/link' import { PropsWithChildren } from 'react' @@ -8,9 +7,9 @@ import { ButtonTooltip } from './ButtonTooltip' import { RequestUpgradeToBillingOwners } from './RequestUpgradeToBillingOwners' import { getInfrastructurePath } from '@/components/interfaces/Settings/Infrastructure/Infrastructure.utils' import { SupportLink } from '@/components/interfaces/Support/SupportLink' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' +import { useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export const PLAN_REQUEST_EMPTY_PLACEHOLDER = '' @@ -54,10 +53,8 @@ export const UpgradePlanButton = ({ const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') const { billingAll } = useIsFeatureEnabled(['billing:all']) - const { can: canUpdateSubscription } = useAsyncCheckPermissions( - PermissionAction.BILLING_WRITE, - 'stripe.subscriptions', - undefined, + const { can: canUpdateSubscription } = useAsyncCheckPermissionsV2( + 'billing_write', { organizationSlug: slug } ) diff --git a/apps/studio/data/config/project-settings-v2-query.ts b/apps/studio/data/config/project-settings-v2-query.ts index 2172cad83ce..de1ae61960c 100644 --- a/apps/studio/data/config/project-settings-v2-query.ts +++ b/apps/studio/data/config/project-settings-v2-query.ts @@ -1,11 +1,10 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery } from '@tanstack/react-query' import { configKeys } from './keys' import type { components } from '@/data/api' import { get, handleError } from '@/data/fetchers' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { ResponseError, UseCustomQueryOptions } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export type ProjectSettingsVariables = { projectRef?: string } @@ -46,9 +45,8 @@ export const useProjectSettingsV2Query = ( ) => { // [Joshen] Sync with API perms checking here - shouldReturnApiKeys // https://github.com/supabase/platform/blob/develop/api/src/routes/platform/projects/ref/settings.controller.ts#L92 - const { can: canReadAPIKeys } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - '*' + const { can: canReadAPIKeys } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.API_GATEWAY_KEYS_READ ) return useQuery({ diff --git a/apps/studio/data/organizations/organization-customer-profile-query.ts b/apps/studio/data/organizations/organization-customer-profile-query.ts index 9966011f9a1..df7fce526c4 100644 --- a/apps/studio/data/organizations/organization-customer-profile-query.ts +++ b/apps/studio/data/organizations/organization-customer-profile-query.ts @@ -1,11 +1,10 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery } from '@tanstack/react-query' import { IS_PLATFORM } from 'common' import { organizationKeys } from './keys' import { get, handleError } from '@/data/fetchers' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { ResponseError, UseCustomQueryOptions } from '@/types' +import { useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export type OrganizationCustomerProfileVariables = { slug?: string @@ -48,10 +47,8 @@ export const useOrganizationCustomerProfileQuery = { // [Joshen] Thinking it makes sense to add this check at the RQ level - prevent // unnecessary requests, although this behaviour still needs handling on the UI - const { can: canReadCustomerProfile } = useAsyncCheckPermissions( - PermissionAction.BILLING_READ, - 'stripe.customer', - undefined, + const { can: canReadCustomerProfile } = useAsyncCheckPermissionsV2( + 'billing_read', { organizationSlug: slug } ) diff --git a/apps/studio/data/organizations/organization-payment-methods-query.ts b/apps/studio/data/organizations/organization-payment-methods-query.ts index 420f361f948..e9a406617e5 100644 --- a/apps/studio/data/organizations/organization-payment-methods-query.ts +++ b/apps/studio/data/organizations/organization-payment-methods-query.ts @@ -1,11 +1,10 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery } from '@tanstack/react-query' import { components } from 'api-types' import { organizationKeys } from './keys' import { get, handleError } from '@/data/fetchers' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { ResponseError, UseCustomQueryOptions } from '@/types' +import { useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export type OrganizationPaymentMethodsVariables = { slug?: string } export type OrganizationPaymentMethod = components['schemas']['PaymentsResponse_Output']['data'][0] @@ -48,9 +47,8 @@ export const useOrganizationPaymentMethodsQuery = = {} ) => { - const { can: canReadSubscriptions } = useAsyncCheckPermissions( - PermissionAction.BILLING_READ, - 'stripe.payment_methods' + const { can: canReadSubscriptions } = useAsyncCheckPermissionsV2( + 'billing_read' ) return useQuery({ queryKey: organizationKeys.paymentMethods(slug), diff --git a/apps/studio/data/organizations/organization-tax-id-query.ts b/apps/studio/data/organizations/organization-tax-id-query.ts index 21d3c9a1080..b834caa2ac2 100644 --- a/apps/studio/data/organizations/organization-tax-id-query.ts +++ b/apps/studio/data/organizations/organization-tax-id-query.ts @@ -1,11 +1,10 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery } from '@tanstack/react-query' import { components } from 'api-types' import { organizationKeys } from './keys' import { get, handleError } from '@/data/fetchers' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { ResponseError, UseCustomQueryOptions } from '@/types' +import { useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export type OrganizationTaxIdVariables = { slug?: string @@ -36,9 +35,8 @@ export const useOrganizationTaxIdQuery = ( ...options }: UseCustomQueryOptions = {} ) => { - const { can: canReadSubscriptions } = useAsyncCheckPermissions( - PermissionAction.BILLING_READ, - 'stripe.tax_ids' + const { can: canReadSubscriptions } = useAsyncCheckPermissionsV2( + 'billing_read' ) return useQuery({ diff --git a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts index c98ad6d0217..978ab892082 100644 --- a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts +++ b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts @@ -1,5 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' - import { useS3AccessKeyCreateMutation } from './s3-access-key-create-mutation' import { WRAPPERS } from '@/components/interfaces/Integrations/Wrappers/Wrappers.constants' import { @@ -13,13 +11,15 @@ import { import { useAPIKeys } from '@/data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' import { FDWCreateVariables, useFDWCreateMutation } from '@/data/fdw/fdw-create-mutation' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const useIcebergWrapperCreateMutation = () => { const { data: project } = useSelectedProjectQuery() - const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*') + const { can: canReadAPIKeys } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.API_GATEWAY_KEYS_SECRET_READ + ) const { data: apiKeysData } = useAPIKeys( { projectRef: project?.ref, reveal: true }, { enabled: canReadAPIKeys } @@ -33,9 +33,8 @@ export const useIcebergWrapperCreateMutation = () => { const wrapperMeta = WRAPPERS.find((wrapper) => wrapper.name === 'iceberg_wrapper') - const { can: canCreateCredentials } = useAsyncCheckPermissions( - PermissionAction.STORAGE_ADMIN_WRITE, - '*' + const { can: canCreateCredentials } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.STORAGE_CONFIG_WRITE ) const { mutateAsync: createS3AccessKey, isPending: isCreatingS3AccessKey } = diff --git a/apps/studio/data/storage/s3-vectors-wrapper-create-mutation.ts b/apps/studio/data/storage/s3-vectors-wrapper-create-mutation.ts index c77b8c329a6..9315fc1e513 100644 --- a/apps/studio/data/storage/s3-vectors-wrapper-create-mutation.ts +++ b/apps/studio/data/storage/s3-vectors-wrapper-create-mutation.ts @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { IS_PLATFORM } from 'common' import { useLocalS3KeysQuery } from '../misc/local-s3-keys-query' @@ -12,9 +11,9 @@ import { } from '@/components/interfaces/Storage/VectorBuckets/VectorBuckets.utils' import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' import { FDWCreateVariables, useFDWCreateMutation } from '@/data/fdw/fdw-create-mutation' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useDeploymentMode } from '@/hooks/misc/useDeploymentMode' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const useS3VectorsWrapperCreateMutation = () => { const { data: project } = useSelectedProjectQuery() @@ -39,9 +38,8 @@ export const useS3VectorsWrapperCreateMutation = () => { const wrapperMeta = WRAPPERS.find((wrapper) => wrapper.name === 's3_vectors_wrapper') - const { can: canCreateCredentials } = useAsyncCheckPermissions( - PermissionAction.STORAGE_ADMIN_WRITE, - '*' + const { can: canCreateCredentials } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.STORAGE_CONFIG_WRITE ) const { mutateAsync: createS3AccessKey, isPending: isCreatingS3AccessKey } = diff --git a/apps/studio/data/subscriptions/org-balance-query.ts b/apps/studio/data/subscriptions/org-balance-query.ts index af00ac0f39d..133baad0555 100644 --- a/apps/studio/data/subscriptions/org-balance-query.ts +++ b/apps/studio/data/subscriptions/org-balance-query.ts @@ -1,10 +1,9 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery } from '@tanstack/react-query' import { subscriptionKeys } from './keys' import { get, handleError } from '@/data/fetchers' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { ResponseError, UseCustomQueryOptions } from '@/types' +import { useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export type OrgBalanceVariables = { orgSlug?: string @@ -36,9 +35,8 @@ export const useOrgBalanceQuery = ( ) => { // [Joshen] Thinking it makes sense to add this check at the RQ level - prevent // unnecessary requests, although this behaviour still needs handling on the UI - const { can: canReadBalance } = useAsyncCheckPermissions( - PermissionAction.BILLING_READ, - 'stripe.subscriptions' + const { can: canReadBalance } = useAsyncCheckPermissionsV2( + 'billing_read' ) return useQuery({ diff --git a/apps/studio/data/subscriptions/org-plans-query.ts b/apps/studio/data/subscriptions/org-plans-query.ts index 2c35509d2df..7b19bc8b8c0 100644 --- a/apps/studio/data/subscriptions/org-plans-query.ts +++ b/apps/studio/data/subscriptions/org-plans-query.ts @@ -1,10 +1,9 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery } from '@tanstack/react-query' import { subscriptionKeys } from './keys' import { get, handleError } from '@/data/fetchers' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { UseCustomQueryOptions } from '@/types' +import { useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export type OrgPlansVariables = { orgSlug?: string @@ -28,9 +27,8 @@ export const useOrgPlansQuery = ( { orgSlug }: OrgPlansVariables, { enabled = true, ...options }: UseCustomQueryOptions = {} ) => { - const { can: canReadSubscriptions } = useAsyncCheckPermissions( - PermissionAction.BILLING_READ, - 'stripe.subscriptions' + const { can: canReadSubscriptions } = useAsyncCheckPermissionsV2( + 'billing_read' ) return useQuery({ diff --git a/apps/studio/data/subscriptions/org-subscription-query.ts b/apps/studio/data/subscriptions/org-subscription-query.ts index 8e3cfc47439..6feedf20128 100644 --- a/apps/studio/data/subscriptions/org-subscription-query.ts +++ b/apps/studio/data/subscriptions/org-subscription-query.ts @@ -1,11 +1,10 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery } from '@tanstack/react-query' import { subscriptionKeys } from './keys' import { get, handleError } from '@/data/fetchers' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { ResponseError, UseCustomQueryOptions } from '@/types' +import { useAsyncCheckPermissionsV2 } from '@/hooks/misc/useCheckPermissionsV2' export type OrgSubscriptionVariables = { orgSlug?: string @@ -40,9 +39,8 @@ export const useOrgSubscriptionQuery = ( ) => { // [Joshen] Thinking it makes sense to add this check at the RQ level - prevent // unnecessary requests, although this behaviour still needs handling on the UI - const { can: canReadSubscriptions } = useAsyncCheckPermissions( - PermissionAction.BILLING_READ, - 'stripe.subscriptions' + const { can: canReadSubscriptions } = useAsyncCheckPermissionsV2( + 'billing_read' ) return useQuery({ diff --git a/apps/studio/data/table-rows/table-rows-count-query.ts b/apps/studio/data/table-rows/table-rows-count-query.ts index d002da4aef5..243e23701e0 100644 --- a/apps/studio/data/table-rows/table-rows-count-query.ts +++ b/apps/studio/data/table-rows/table-rows-count-query.ts @@ -1,5 +1,4 @@ import { getTableRowsCountSql } from '@supabase/pg-meta' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { QueryClient, useQuery, useQueryClient } from '@tanstack/react-query' import { IS_PLATFORM, useFlag } from 'common' @@ -13,10 +12,10 @@ import { PG_META_SCOPED_INTROSPECTION_FLAG, prefetchTableEditor, } from '@/data/table-editor/table-editor-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { RoleImpersonationState, wrapWithRoleImpersonation } from '@/lib/role-impersonation' import { isRoleImpersonationEnabled } from '@/state/role-impersonation-state' import { ResponseError, UseCustomQueryOptions } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export type GetTableRowsCountArgs = { table?: SupaTable @@ -112,9 +111,8 @@ export const useTableRowsCountQuery = ( identifier: readReplicaIdentifier, type, } = useConnectionStringForReadOps() - const { can: canSQLAdminWrite, isLoading: isPermissionsLoading } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - 'tables' + const { can: canSQLAdminWrite, isLoading: isPermissionsLoading } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_WRITE ) const scoped = !!useFlag(PG_META_SCOPED_INTROSPECTION_FLAG) diff --git a/apps/studio/hooks/forms/useAIOptInForm.ts b/apps/studio/hooks/forms/useAIOptInForm.ts index 57361a16f26..297c4322c80 100644 --- a/apps/studio/hooks/forms/useAIOptInForm.ts +++ b/apps/studio/hooks/forms/useAIOptInForm.ts @@ -1,5 +1,4 @@ import { zodResolver } from '@hookform/resolvers/zod' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import { LOCAL_STORAGE_KEYS } from 'common' import { useForm } from 'react-hook-form' @@ -8,12 +7,12 @@ import * as z from 'zod' import { useOrganizationUpdateMutation } from '@/data/organizations/organization-update-mutation' import { invalidateOrganizationsQuery } from '@/data/organizations/organizations-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' import { getAiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { OPT_IN_TAGS } from '@/lib/constants' import type { ResponseError } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '../misc/useCheckPermissionsV2' // Shared schema definition export const AIOptInSchema = z.object({ @@ -31,9 +30,8 @@ export type AIOptInFormValues = z.infer export const useAIOptInForm = (onSuccessCallback?: () => void) => { const queryClient = useQueryClient() const { data: selectedOrganization } = useSelectedOrganizationQuery() - const { can: canUpdateOrganization } = useAsyncCheckPermissions( - PermissionAction.UPDATE, - 'organizations' + const { can: canUpdateOrganization } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.ORGANIZATION.ADMIN_WRITE ) const [, setUpdatedOptInSinceMCP] = useLocalStorageQuery( diff --git a/apps/studio/hooks/misc/useCheckPermissionsV2.ts b/apps/studio/hooks/misc/useCheckPermissionsV2.ts index e6b23bcc708..5156dd47daa 100644 --- a/apps/studio/hooks/misc/useCheckPermissionsV2.ts +++ b/apps/studio/hooks/misc/useCheckPermissionsV2.ts @@ -121,7 +121,6 @@ function useGetProjectPermissionsV2( } } -// Useful when you want to avoid layout changes while waiting for permissions to load export function useAsyncCheckPermissionsV2( permission: FgaPermissions | FgaPermissions[], overrides?: { diff --git a/apps/studio/pages/project/[ref]/auth/audit-logs.tsx b/apps/studio/pages/project/[ref]/auth/audit-logs.tsx index 723c8047e81..3369a42f023 100644 --- a/apps/studio/pages/project/[ref]/auth/audit-logs.tsx +++ b/apps/studio/pages/project/[ref]/auth/audit-logs.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageHeader, @@ -16,14 +15,13 @@ import AuthLayout from '@/components/layouts/AuthLayout/AuthLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { DocsButton } from '@/components/ui/DocsButton' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const AuditLogsPage: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (isPermissionsLoaded && !canReadAuthSettings) { diff --git a/apps/studio/pages/project/[ref]/auth/hooks.tsx b/apps/studio/pages/project/[ref]/auth/hooks.tsx index c941e734e0d..2139b97a544 100644 --- a/apps/studio/pages/project/[ref]/auth/hooks.tsx +++ b/apps/studio/pages/project/[ref]/auth/hooks.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageHeader, @@ -16,14 +15,13 @@ import AuthLayout from '@/components/layouts/AuthLayout/AuthLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { DocsButton } from '@/components/ui/DocsButton' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const Hooks: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (isPermissionsLoaded && !canReadAuthSettings) { diff --git a/apps/studio/pages/project/[ref]/auth/mfa.tsx b/apps/studio/pages/project/[ref]/auth/mfa.tsx index 4cd007eaf79..fa42310ad6e 100644 --- a/apps/studio/pages/project/[ref]/auth/mfa.tsx +++ b/apps/studio/pages/project/[ref]/auth/mfa.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { PageContainer } from 'ui-patterns/PageContainer' import { @@ -16,17 +15,16 @@ import AuthLayout from '@/components/layouts/AuthLayout/AuthLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { NoPermission } from '@/components/ui/NoPermission' import { UnknownInterface } from '@/components/ui/UnknownInterface' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const MfaPage: NextPageWithLayout = () => { const { ref } = useParams() const showMFA = useIsFeatureEnabled('authentication:multi_factor') - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (!showMFA) { diff --git a/apps/studio/pages/project/[ref]/auth/performance.tsx b/apps/studio/pages/project/[ref]/auth/performance.tsx index ffd935d7c03..09e4828eb23 100644 --- a/apps/studio/pages/project/[ref]/auth/performance.tsx +++ b/apps/studio/pages/project/[ref]/auth/performance.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { PageContainer } from 'ui-patterns/PageContainer' import { @@ -16,17 +15,16 @@ import AuthLayout from '@/components/layouts/AuthLayout/AuthLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { NoPermission } from '@/components/ui/NoPermission' import { UnknownInterface } from '@/components/ui/UnknownInterface' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const PerformancePage: NextPageWithLayout = () => { const { ref } = useParams() const showPerformance = useIsFeatureEnabled('authentication:performance') - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (!showPerformance) { diff --git a/apps/studio/pages/project/[ref]/auth/protection.tsx b/apps/studio/pages/project/[ref]/auth/protection.tsx index 02cdfd2ce47..2d40fa4aa83 100644 --- a/apps/studio/pages/project/[ref]/auth/protection.tsx +++ b/apps/studio/pages/project/[ref]/auth/protection.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { PageContainer } from 'ui-patterns/PageContainer' import { @@ -16,17 +15,16 @@ import AuthLayout from '@/components/layouts/AuthLayout/AuthLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { NoPermission } from '@/components/ui/NoPermission' import { UnknownInterface } from '@/components/ui/UnknownInterface' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const ProtectionPage: NextPageWithLayout = () => { const { ref } = useParams() const showAttackProtection = useIsFeatureEnabled('authentication:attack_protection') - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (!showAttackProtection) { diff --git a/apps/studio/pages/project/[ref]/auth/rate-limits.tsx b/apps/studio/pages/project/[ref]/auth/rate-limits.tsx index f09a72daccb..f4fde6357b0 100644 --- a/apps/studio/pages/project/[ref]/auth/rate-limits.tsx +++ b/apps/studio/pages/project/[ref]/auth/rate-limits.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { PageContainer } from 'ui-patterns/PageContainer' import { @@ -18,18 +17,17 @@ import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { DocsButton } from '@/components/ui/DocsButton' import { NoPermission } from '@/components/ui/NoPermission' import { UnknownInterface } from '@/components/ui/UnknownInterface' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const RateLimitsPage: NextPageWithLayout = () => { const { ref } = useParams() const showRateLimits = useIsFeatureEnabled('authentication:rate_limits') - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (!showRateLimits) { diff --git a/apps/studio/pages/project/[ref]/auth/sessions.tsx b/apps/studio/pages/project/[ref]/auth/sessions.tsx index 0fe62065225..ee05cd7bbeb 100644 --- a/apps/studio/pages/project/[ref]/auth/sessions.tsx +++ b/apps/studio/pages/project/[ref]/auth/sessions.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageHeader, @@ -14,13 +13,12 @@ import { SessionsAuthSettingsForm } from '@/components/interfaces/Auth/SessionsA import AuthLayout from '@/components/layouts/AuthLayout/AuthLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const SessionsPage: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (isPermissionsLoaded && !canReadAuthSettings) { diff --git a/apps/studio/pages/project/[ref]/auth/smtp.tsx b/apps/studio/pages/project/[ref]/auth/smtp.tsx index e6856e5a38e..43a6f36b396 100644 --- a/apps/studio/pages/project/[ref]/auth/smtp.tsx +++ b/apps/studio/pages/project/[ref]/auth/smtp.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionContent } from 'ui-patterns/PageSection' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' @@ -7,13 +6,12 @@ import { SmtpForm } from '@/components/interfaces/Auth/SmtpForm/SmtpForm' import { AuthEmailsLayout } from '@/components/layouts/AuthLayout/AuthEmailsLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const SmtpPage: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (isPermissionsLoaded && !canReadAuthSettings) { diff --git a/apps/studio/pages/project/[ref]/auth/templates/[templateId].tsx b/apps/studio/pages/project/[ref]/auth/templates/[templateId].tsx index d8195556aa9..e34aff6e637 100644 --- a/apps/studio/pages/project/[ref]/auth/templates/[templateId].tsx +++ b/apps/studio/pages/project/[ref]/auth/templates/[templateId].tsx @@ -1,5 +1,4 @@ import { zodResolver } from '@hookform/resolvers/zod' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import Link from 'next/link' import { useRouter } from 'next/router' @@ -59,11 +58,11 @@ import { DocsButton } from '@/components/ui/DocsButton' import { NoPermission } from '@/components/ui/NoPermission' import { useAuthConfigQuery } from '@/data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const TemplatePage: NextPageWithLayout = () => { return @@ -74,14 +73,12 @@ const RedirectToTemplates = () => { const { templateId, ref } = router.query const { ref: projectRef } = useParams() - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) - const { can: canUpdateConfig } = useAsyncCheckPermissions( - PermissionAction.UPDATE, - 'custom_config_gotrue' + const { can: canUpdateConfig } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_WRITE ) const { data: authConfig, isPending: isLoadingConfig } = useAuthConfigQuery({ projectRef }) @@ -132,16 +129,16 @@ const RedirectToTemplates = () => { // Create form schema for security templates const TemplateFormSchema = templateEnabledKey ? z.object({ - [templateEnabledKey]: z.boolean(), - }) + [templateEnabledKey]: z.boolean(), + }) : z.object({}) const defaultValues = templateEnabledKey ? { - [templateEnabledKey]: authConfig - ? Boolean(authConfig[templateEnabledKey as keyof typeof authConfig]) - : false, - } + [templateEnabledKey]: authConfig + ? Boolean(authConfig[templateEnabledKey as keyof typeof authConfig]) + : false, + } : {} const templateForm = useForm>({ diff --git a/apps/studio/pages/project/[ref]/auth/templates/index.tsx b/apps/studio/pages/project/[ref]/auth/templates/index.tsx index f02769b959f..b85eab00a8e 100644 --- a/apps/studio/pages/project/[ref]/auth/templates/index.tsx +++ b/apps/studio/pages/project/[ref]/auth/templates/index.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionContent } from 'ui-patterns/PageSection' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' @@ -7,13 +6,12 @@ import { EmailTemplates } from '@/components/interfaces/Auth/EmailTemplates/Emai import { AuthEmailsLayout } from '@/components/layouts/AuthLayout/AuthEmailsLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const TemplatesPage: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (isPermissionsLoaded && !canReadAuthSettings) { diff --git a/apps/studio/pages/project/[ref]/auth/third-party.tsx b/apps/studio/pages/project/[ref]/auth/third-party.tsx index 188bdd3abd4..6577083815f 100644 --- a/apps/studio/pages/project/[ref]/auth/third-party.tsx +++ b/apps/studio/pages/project/[ref]/auth/third-party.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { PageContainer } from 'ui-patterns/PageContainer' @@ -8,15 +7,14 @@ import { AuthProvidersLayout } from '@/components/layouts/AuthLayout/AuthProvide import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { NoPermission } from '@/components/ui/NoPermission' import { UnknownInterface } from '@/components/ui/UnknownInterface' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const ThirdPartyPage: NextPageWithLayout = () => { const { ref } = useParams() - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) const showThirdPartyAuth = useIsFeatureEnabled('authentication:third_party_auth') diff --git a/apps/studio/pages/project/[ref]/auth/url-configuration.tsx b/apps/studio/pages/project/[ref]/auth/url-configuration.tsx index 81f42779c0c..1173757bc12 100644 --- a/apps/studio/pages/project/[ref]/auth/url-configuration.tsx +++ b/apps/studio/pages/project/[ref]/auth/url-configuration.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageHeader, @@ -15,13 +14,12 @@ import SiteUrl from '@/components/interfaces/Auth/SiteUrl/SiteUrl' import AuthLayout from '@/components/layouts/AuthLayout/AuthLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const URLConfiguration: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'custom_config_gotrue' + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_CONFIG_READ ) if (isPermissionsLoaded && !canReadAuthSettings) { diff --git a/apps/studio/pages/project/[ref]/branches/index.tsx b/apps/studio/pages/project/[ref]/branches/index.tsx index f16f58b939c..b89c7b3740f 100644 --- a/apps/studio/pages/project/[ref]/branches/index.tsx +++ b/apps/studio/pages/project/[ref]/branches/index.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { partition } from 'lodash' import { MessageCircle } from 'lucide-react' @@ -18,13 +17,13 @@ import { DocsButton } from '@/components/ui/DocsButton' import { NoPermission } from '@/components/ui/NoPermission' import { Branch, useBranchesQuery } from '@/data/branches/branches-query' import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' import { useAppStateSnapshot } from '@/state/app-state' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const BranchesPage: NextPageWithLayout = () => { const router = useRouter() @@ -41,9 +40,8 @@ const BranchesPage: NextPageWithLayout = () => { const projectRef = project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined - const { can: canReadBranches, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'preview_branches' + const { can: canReadBranches, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.BRANCHING_DEVELOPMENT_READ ) const { @@ -156,12 +154,8 @@ const BranchesPage: NextPageWithLayout = () => { // content as `children` instead of capturing it from a closure. export const BranchesPageWrapper = ({ children }: PropsWithChildren) => { const snap = useAppStateSnapshot() - const { can: canCreateBranches } = useAsyncCheckPermissions( - PermissionAction.CREATE, - 'preview_branches', - { - resource: { is_default: false }, - } + const { can: canCreateBranches } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.BRANCHING_DEVELOPMENT_CREATE ) const primaryActions = ( diff --git a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx index fa1d83f6f99..cc73f92a44a 100644 --- a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx +++ b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { partition } from 'lodash' import { ArrowRight, GitMerge, MessageCircle, MoreVertical, Shield, X } from 'lucide-react' @@ -31,12 +30,12 @@ import { NoPermission } from '@/components/ui/NoPermission' import { useBranchUpdateMutation } from '@/data/branches/branch-update-mutation' import { Branch, useBranchesQuery } from '@/data/branches/branches-query' import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const MergeRequestsPage: NextPageWithLayout = () => { const router = useRouter() @@ -48,9 +47,8 @@ const MergeRequestsPage: NextPageWithLayout = () => { const projectRef = project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined - const { can: canReadBranches, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'preview_branches' + const { can: canReadBranches, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.BRANCHING_DEVELOPMENT_READ ) const { diff --git a/apps/studio/pages/project/[ref]/database/backups/pitr.tsx b/apps/studio/pages/project/[ref]/database/backups/pitr.tsx index 13d752e3640..69119dfc15a 100644 --- a/apps/studio/pages/project/[ref]/database/backups/pitr.tsx +++ b/apps/studio/pages/project/[ref]/database/backups/pitr.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { AlertCircle, DatabaseBackup } from 'lucide-react' import { Alert, AlertDescription, AlertTitle } from 'ui' @@ -26,11 +25,11 @@ import { NoPermission } from '@/components/ui/NoPermission' import { UpgradeToPro } from '@/components/ui/UpgradeToPro' import { useBackupsQuery } from '@/data/database/backups-query' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useHighAvailability } from '@/hooks/misc/useHighAvailability' import { useIsOrioleDbInAws, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL, PROJECT_STATUS } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const DatabasePhysicalBackups: NextPageWithLayout = () => { return ( @@ -83,9 +82,8 @@ const PITR = () => { const isEnabled = backups?.pitr_enabled const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY - const { can: canReadPhysicalBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'physical_backups' + const { can: canReadPhysicalBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.BACKUPS_READ ) if (isPermissionsLoaded && !canReadPhysicalBackups) { diff --git a/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx b/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx index 27366719149..ba33c74d2d9 100644 --- a/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx +++ b/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { DatabaseBackup, Info } from 'lucide-react' import { Admonition } from 'ui-patterns/Admonition' @@ -23,10 +22,10 @@ import { HighAvailabilityDisabledEmptyState } from '@/components/ui/HighAvailabi import InformationBox from '@/components/ui/InformationBox' import { NoPermission } from '@/components/ui/NoPermission' import { useBackupsQuery } from '@/data/database/backups-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsHighAvailability, useIsOrioleDbInAws } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const DatabaseScheduledBackups: NextPageWithLayout = () => { return ( @@ -67,9 +66,8 @@ const ScheduledBackups = () => { const isHighAvailability = useIsHighAvailability() const isPitrEnabled = backups?.pitr_enabled - const { can: canReadScheduledBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'back_ups' + const { can: canReadScheduledBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.BACKUPS_READ ) if (isOrioleDbInAws) { diff --git a/apps/studio/pages/project/[ref]/database/extensions.tsx b/apps/studio/pages/project/[ref]/database/extensions.tsx index 36e4c673362..f98670d4ad1 100644 --- a/apps/studio/pages/project/[ref]/database/extensions.tsx +++ b/apps/studio/pages/project/[ref]/database/extensions.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageHeader, @@ -15,14 +14,13 @@ import { DatabaseLayout } from '@/components/layouts/DatabaseLayout/DatabaseLayo import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { DocsButton } from '@/components/ui/DocsButton' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const DatabaseExtensions: NextPageWithLayout = () => { - const { can: canReadExtensions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_READ, - 'extensions' + const { can: canReadExtensions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_READ ) if (isPermissionsLoaded && !canReadExtensions) { diff --git a/apps/studio/pages/project/[ref]/database/functions.tsx b/apps/studio/pages/project/[ref]/database/functions.tsx index dc08e5e688c..391b47bf259 100644 --- a/apps/studio/pages/project/[ref]/database/functions.tsx +++ b/apps/studio/pages/project/[ref]/database/functions.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageHeader, @@ -14,14 +13,13 @@ import { DatabaseLayout } from '@/components/layouts/DatabaseLayout/DatabaseLayo import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { DocsButton } from '@/components/ui/DocsButton' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const DatabaseFunctionsPage: NextPageWithLayout = () => { - const { can: canReadFunctions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_READ, - 'functions' + const { can: canReadFunctions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_READ ) if (isPermissionsLoaded && !canReadFunctions) { diff --git a/apps/studio/pages/project/[ref]/database/policies.tsx b/apps/studio/pages/project/[ref]/database/policies.tsx index 38d133de4f2..f6560488738 100644 --- a/apps/studio/pages/project/[ref]/database/policies.tsx +++ b/apps/studio/pages/project/[ref]/database/policies.tsx @@ -1,6 +1,5 @@ import type { PGTable } from '@supabase/pg-meta' import { ident, safeSql } from '@supabase/pg-meta' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { LOCAL_STORAGE_KEYS, useParams } from 'common' import { Search, X } from 'lucide-react' import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' @@ -42,7 +41,6 @@ import { Shortcut } from '@/components/ui/Shortcut' import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query' import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query' import { useTablesQuery } from '@/data/tables/tables-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' @@ -53,6 +51,7 @@ import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useShortcut } from '@/state/shortcuts/useShortcut' import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' /** * Filter tables by table name and policy name @@ -182,9 +181,8 @@ const DatabasePoliciesPage: NextPageWithLayout = () => { ) const exposedSchemas = getExposedSchemas(dbSchema) - const { can: canReadPolicies, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_READ, - 'policies' + const { can: canReadPolicies, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_READ ) const handleSelectCreatePolicy = useCallback( diff --git a/apps/studio/pages/project/[ref]/database/publications/[id].tsx b/apps/studio/pages/project/[ref]/database/publications/[id].tsx index 079ef296979..404a3f1d615 100644 --- a/apps/studio/pages/project/[ref]/database/publications/[id].tsx +++ b/apps/studio/pages/project/[ref]/database/publications/[id].tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionContent } from 'ui-patterns/PageSection' @@ -11,16 +10,15 @@ import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { PageLayout } from '@/components/layouts/PageLayout/PageLayout' import { NoPermission } from '@/components/ui/NoPermission' import { useDatabasePublicationsQuery } from '@/data/database-publications/database-publications-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const DatabasePublicationsContent = () => { const { ref, id } = useParams() const { data: project } = useSelectedProjectQuery() - const { can: canViewPublications, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_READ, - 'publications' + const { can: canViewPublications, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_READ ) const { data: publications = [], isPending } = useDatabasePublicationsQuery({ diff --git a/apps/studio/pages/project/[ref]/database/publications/index.tsx b/apps/studio/pages/project/[ref]/database/publications/index.tsx index 7ef15f0471b..58840627604 100644 --- a/apps/studio/pages/project/[ref]/database/publications/index.tsx +++ b/apps/studio/pages/project/[ref]/database/publications/index.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection } from 'ui-patterns/PageSection' @@ -8,13 +7,12 @@ import { DatabaseLayout } from '@/components/layouts/DatabaseLayout/DatabaseLayo import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { PageLayout } from '@/components/layouts/PageLayout/PageLayout' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const DatabasePublicationsContent = () => { - const { can: canViewPublications, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_READ, - 'publications' + const { can: canViewPublications, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_READ ) if (isPermissionsLoaded && !canViewPublications) { diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx index a9caf52da8e..e59cccb49f5 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { IS_PLATFORM, useParams } from 'common' import { isEqual } from 'lodash' import { AlertCircle, CornerDownLeft, Loader2 } from 'lucide-react' @@ -18,12 +17,12 @@ import { InlineLink } from '@/components/ui/InlineLink' import { useEdgeFunctionBodyQuery } from '@/data/edge-functions/edge-function-body-query' import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query' import { useEdgeFunctionDeployMutation } from '@/data/edge-functions/edge-functions-deploy-mutation' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { usePreventNavigationOnUnsavedChanges } from '@/hooks/ui/usePreventNavigationOnUnsavedChanges' import { BASE_PATH, DOCS_URL } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const CodePage = () => { const { ref, functionSlug } = useParams() @@ -33,7 +32,9 @@ const CodePage = () => { const track = useTrack() const [showDeployWarning, setShowDeployWarning] = useState(false) - const { can: canDeployFunction } = useAsyncCheckPermissions(PermissionAction.FUNCTIONS_WRITE, '*') + const { can: canDeployFunction } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.EDGE_FUNCTIONS_WRITE + ) const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef: ref, @@ -68,9 +69,9 @@ const CodePage = () => { const initialFiles = useMemo(() => { return !!functionBody ? formatFunctionBodyToFiles({ - functionBody, - entrypointPath: selectedFunction?.entrypoint_path, - }) + functionBody, + entrypointPath: selectedFunction?.entrypoint_path, + }) : [] }, [functionBody, selectedFunction?.entrypoint_path]) diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx index 6af3b79b1ad..1f67bb020ce 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { IS_PLATFORM, useFeatureFlags, useFlag, useParams } from 'common' import dayjs, { Dayjs } from 'dayjs' import maxBy from 'lodash/maxBy' @@ -24,8 +23,8 @@ import { } from '@/data/analytics/functions-combined-stats-query' import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query' import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import type { ChartIntervals, NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const CHART_INTERVALS: ChartIntervals[] = [ { @@ -121,9 +120,8 @@ const LegacyEdgeFunctionOverview = () => { endDate: endDate.toISOString(), }) - const { isLoading: permissionsLoading, can: canReadFunction } = useAsyncCheckPermissions( - PermissionAction.FUNCTIONS_READ, - functionSlug as string + const { isLoading: permissionsLoading, can: canReadFunction } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.EDGE_FUNCTIONS_READ ) if (!canReadFunction && !permissionsLoading) { return diff --git a/apps/studio/pages/project/[ref]/logs/auth-logs.tsx b/apps/studio/pages/project/[ref]/logs/auth-logs.tsx index 1720f34c65c..4761ad0ec17 100644 --- a/apps/studio/pages/project/[ref]/logs/auth-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/auth-logs.tsx @@ -1,18 +1,15 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' - import { LogsPreviewer } from '@/components/interfaces/Settings/Logs/LogsPreviewer' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import LogsLayout from '@/components/layouts/LogsLayout/LogsLayout' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const LogsPage: NextPageWithLayout = () => { const { data: project } = useSelectedProjectQuery() - const { can: canReadAuthLogs } = useAsyncCheckPermissions( - PermissionAction.ANALYTICS_READ, - 'logflare' + const { can: canReadAuthLogs } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.ANALYTICS_LOGS_READ ) return !canReadAuthLogs ? ( diff --git a/apps/studio/pages/project/[ref]/observability/database.tsx b/apps/studio/pages/project/[ref]/observability/database.tsx index 49dddf651c3..5f78a807aa7 100644 --- a/apps/studio/pages/project/[ref]/observability/database.tsx +++ b/apps/studio/pages/project/[ref]/observability/database.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import { useFlag, useParams } from 'common' import dayjs from 'dayjs' @@ -40,7 +39,6 @@ import { getReportAttributesV2 } from '@/data/reports/database-charts' import { useDatabaseReport } from '@/data/reports/database-report-query' import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useRefreshHandler, useReportDateRange } from '@/hooks/misc/useReportDateRange' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useIsHighAvailability, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' @@ -50,6 +48,7 @@ import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useShortcut } from '@/state/shortcuts/useShortcut' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const DatabaseReport: NextPageWithLayout = () => { return ( @@ -195,19 +194,13 @@ const DatabaseUsage = () => { const computeInstance = addons?.selected_addons.find((addon) => addon.type === 'compute_instance') const poolingOptimizations = POOLING_OPTIMIZATIONS[ - (computeInstance?.variant.identifier as keyof typeof POOLING_OPTIMIZATIONS) ?? - (project?.infra_compute_size === 'nano' ? 'ci_nano' : 'ci_micro') + (computeInstance?.variant.identifier as keyof typeof POOLING_OPTIMIZATIONS) ?? + (project?.infra_compute_size === 'nano' ? 'ci_nano' : 'ci_micro') ] const defaultMaxClientConn = poolingOptimizations.maxClientConn ?? 200 - const { can: canUpdateDiskSizeConfig } = useAsyncCheckPermissions( - PermissionAction.UPDATE, - 'projects', - { - resource: { - project_id: project?.id, - }, - } + const { can: canUpdateDiskSizeConfig } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.INFRA_DISK_CONFIG_WRITE ) const { getEntitlementSetValues, isLoading: isEntitlementLoading } = useCheckEntitlements( @@ -363,8 +356,8 @@ const DatabaseUsage = () => { syncId="database-charts" showMaxValue={ chart.id === 'client-connections' || - chart.id === 'client-connections-basic' || - chart.id === 'pgbouncer-connections' + chart.id === 'client-connections-basic' || + chart.id === 'pgbouncer-connections' ? true : chart.showMaxValue } @@ -432,7 +425,7 @@ const DatabaseUsage = () => {
- {/* + {/* [Joshen] TODO: Check if this check is still relevant The DiskSizeConfigurationModal is old and might be obsolete */} diff --git a/apps/studio/pages/project/[ref]/observability/index.tsx b/apps/studio/pages/project/[ref]/observability/index.tsx index 38fc4039644..9d982a6714a 100644 --- a/apps/studio/pages/project/[ref]/observability/index.tsx +++ b/apps/studio/pages/project/[ref]/observability/index.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useFeatureFlags, useFlag, useParams } from 'common' import { useRouter } from 'next/router' import { parseAsBoolean, useQueryState } from 'nuqs' @@ -11,9 +10,9 @@ import { DefaultLayout } from '@/components/layouts/DefaultLayout' import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout' import ProductEmptyState from '@/components/to-be-cleaned/ProductEmptyState' import { useContentQuery } from '@/data/content/content-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useProfile } from '@/lib/profile' import type { NextPageWithLayout } from '@/types' +import { FGA_PERMISSIONS, useAsyncCheckUserContentPermissions } from '@/hooks/misc/useCheckPermissionsV2' export const UserReportPage: NextPageWithLayout = () => { const router = useRouter() @@ -48,12 +47,11 @@ export const UserReportPage: NextPageWithLayout = () => { if (reports.length === 0) router.replace(`/project/${ref}/observability/api-overview`) }, [isSuccess, data, router, ref, showOverview, flagsLoaded]) - const { can: canCreateReport } = useAsyncCheckPermissions( - PermissionAction.CREATE, - 'user_content', + const { can: canCreateReport } = useAsyncCheckUserContentPermissions( + FGA_PERMISSIONS.PROJECT.SNIPPETS_WRITE, { - resource: { type: 'report', owner_id: profile?.id }, - subject: { id: profile?.id }, + mode: 'create', + type: 'report', } ) diff --git a/apps/studio/pages/project/[ref]/settings/api-keys/index.tsx b/apps/studio/pages/project/[ref]/settings/api-keys/index.tsx index 6e9cd5389c7..3378d548412 100644 --- a/apps/studio/pages/project/[ref]/settings/api-keys/index.tsx +++ b/apps/studio/pages/project/[ref]/settings/api-keys/index.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { IS_PLATFORM, useParams } from 'common' import { useMemo } from 'react' import { Separator } from 'ui' @@ -16,15 +15,17 @@ import SettingsLayout from '@/components/layouts/ProjectSettingsLayout/SettingsL import { DisableInteraction } from '@/components/ui/DisableInteraction' import { DocsButton } from '@/components/ui/DocsButton' import { useAPIKeysQuery } from '@/data/api-keys/api-keys-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useDeploymentMode } from '@/hooks/misc/useDeploymentMode' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const ApiKeysNewPage: NextPageWithLayout = () => { const { ref: projectRef } = useParams() const { isCli, isSelfHosted } = useDeploymentMode() - const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*') + const { can: canReadAPIKeys } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.API_GATEWAY_KEYS_READ + ) const { data: apiKeysData = [] } = useAPIKeysQuery( { projectRef, diff --git a/apps/studio/pages/project/[ref]/settings/jwt/index.tsx b/apps/studio/pages/project/[ref]/settings/jwt/index.tsx index 010c6880259..2b985423266 100644 --- a/apps/studio/pages/project/[ref]/settings/jwt/index.tsx +++ b/apps/studio/pages/project/[ref]/settings/jwt/index.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { IS_PLATFORM } from 'common' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' @@ -8,16 +7,15 @@ import JWTKeysLayout from '@/components/layouts/JWTKeys/JWTKeysLayout' import SettingsLayout from '@/components/layouts/ProjectSettingsLayout/SettingsLayout' import { LocalSetupGuide } from '@/components/ui/LocalSetupGuide' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useDeploymentMode } from '@/hooks/misc/useDeploymentMode' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const JWTSigningKeysPage: NextPageWithLayout = () => { const { isCli, isSelfHosted } = useDeploymentMode() - const { can: canReadAPIKeys, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'auth_signing_keys' + const { can: canReadAPIKeys, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.AUTH_SIGNING_KEYS_READ ) if (!IS_PLATFORM) { diff --git a/apps/studio/pages/project/[ref]/settings/log-drains.tsx b/apps/studio/pages/project/[ref]/settings/log-drains.tsx index 42b784d40df..0738316f944 100644 --- a/apps/studio/pages/project/[ref]/settings/log-drains.tsx +++ b/apps/studio/pages/project/[ref]/settings/log-drains.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { IS_PLATFORM, useParams } from 'common' import { ChevronDown } from 'lucide-react' import { cloneElement, useState, type ReactElement } from 'react' @@ -31,16 +30,15 @@ import { import { LogDrainData, useLogDrainsQuery } from '@/data/log-drains/log-drains-query' import { useUpdateLogDrainMutation } from '@/data/log-drains/update-log-drain-mutation' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { DOCS_URL } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import type { NextPageWithLayout } from '@/types' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' const LogDrainsSettings: NextPageWithLayout = () => { - const { can: canManageLogDrains, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( - PermissionAction.ANALYTICS_ADMIN_WRITE, - 'logflare' + const { can: canManageLogDrains, isLoading: isLoadingPermissions } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.ANALYTICS_CONFIG_WRITE ) const track = useTrack() diff --git a/apps/studio/routes/project/$ref/database/triggers.tsx b/apps/studio/routes/project/$ref/database/triggers.tsx index 6770eaef2f9..f641d6d0207 100644 --- a/apps/studio/routes/project/$ref/database/triggers.tsx +++ b/apps/studio/routes/project/$ref/database/triggers.tsx @@ -1,9 +1,8 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { createFileRoute, Outlet } from '@tanstack/react-router' import { PageLayout } from '@/components/layouts/PageLayout/PageLayout' import { NoPermission } from '@/components/ui/NoPermission' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissionsV2, FGA_PERMISSIONS } from '@/hooks/misc/useCheckPermissionsV2' export const Route = createFileRoute('/project/$ref/database/triggers')({ component: TriggersShell, @@ -14,9 +13,8 @@ export const Route = createFileRoute('/project/$ref/database/triggers')({ function TriggersShell() { const { ref } = Route.useParams() - const { can: canReadTriggers, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_READ, - 'triggers' + const { can: canReadTriggers, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissionsV2( + FGA_PERMISSIONS.PROJECT.DATABASE_READ ) if (isPermissionsLoaded && !canReadTriggers) { diff --git a/apps/studio/tests/components/Organization/TeamSettings/InviteMemberButton.test.tsx b/apps/studio/tests/components/Organization/TeamSettings/InviteMemberButton.test.tsx index 0efc0e0a1d3..85b0c9c61f8 100644 --- a/apps/studio/tests/components/Organization/TeamSettings/InviteMemberButton.test.tsx +++ b/apps/studio/tests/components/Organization/TeamSettings/InviteMemberButton.test.tsx @@ -25,10 +25,12 @@ vi.mock('@/hooks/misc/useSelectedOrganization', () => ({ }), })) -vi.mock('@/hooks/misc/useCheckPermissions', () => ({ - useGetPermissions: () => ({ permissions: [], organizationSlug: 'test-org' }), - doPermissionsCheck: () => true, - useAsyncCheckPermissions: () => ({ can: true, isSuccess: true }), +vi.mock('@/data/permissions/permissions-query-v2', () => ({ + usePermissionsQueryV2: () => ({ + data: { + organizations: [{ slug: 'test-org', role: 'owner', permissions: [], projects: [] }], + }, + }), })) vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({ @@ -76,13 +78,14 @@ vi.mock('@/hooks/misc/useCheckEntitlements', () => ({ useCheckEntitlements: () => ({ hasAccess: false }), })) -const { mockRolesManagementPermissions, mockIsFeatureEnabled } = vi.hoisted(() => ({ - mockRolesManagementPermissions: vi.fn(), +const { mockGetAssignableRoleIds, mockIsFeatureEnabled } = vi.hoisted(() => ({ + mockGetAssignableRoleIds: vi.fn(), mockIsFeatureEnabled: vi.fn(), })) vi.mock('@/components/interfaces/Organization/TeamSettings/TeamSettings.utils', () => ({ - useGetRolesManagementPermissions: mockRolesManagementPermissions, + getOrgRole: () => 'owner', + getAssignableRoleIds: mockGetAssignableRoleIds, })) const mockInvite = vi.fn().mockResolvedValue({ succeeded: [], failed: [] }) @@ -124,10 +127,7 @@ async function submitForm(emailValue: string) { describe('InviteMemberButton', () => { beforeEach(() => { mockInvite.mockResolvedValue({ succeeded: [], failed: [] }) - mockRolesManagementPermissions.mockReturnValue({ - rolesAddable: [1, 2, 3, 4], - rolesRemovable: [1, 2, 3, 4], - }) + mockGetAssignableRoleIds.mockReturnValue([1, 2, 3, 4]) mockIsFeatureEnabled.mockReturnValue({ organizationMembersCreate: true }) }) @@ -148,7 +148,7 @@ describe('InviteMemberButton', () => { describe('when the user cannot invite members', () => { beforeEach(() => { - mockRolesManagementPermissions.mockReturnValue({ rolesAddable: [], rolesRemovable: [] }) + mockGetAssignableRoleIds.mockReturnValue([]) }) it('shows only the permission warning, not the shortcut tooltip', async () => { diff --git a/apps/studio/tests/pages/project/[ref]/settings/api-keys/index.test.tsx b/apps/studio/tests/pages/project/[ref]/settings/api-keys/index.test.tsx index 54cc1449d44..de2a2a4ccb0 100644 --- a/apps/studio/tests/pages/project/[ref]/settings/api-keys/index.test.tsx +++ b/apps/studio/tests/pages/project/[ref]/settings/api-keys/index.test.tsx @@ -23,8 +23,9 @@ vi.mock('common', async () => { } }) -vi.mock('@/hooks/misc/useCheckPermissions', () => ({ - useAsyncCheckPermissions: mockUseAsyncCheckPermissions, +vi.mock('@/hooks/misc/useCheckPermissionsV2', () => ({ + useAsyncCheckPermissionsV2: mockUseAsyncCheckPermissions, + FGA_PERMISSIONS: { PROJECT: { API_GATEWAY_KEYS_READ: 'api_gateway_keys_read' } }, })) vi.mock('@/data/api-keys/api-keys-query', () => ({ diff --git a/apps/studio/tests/pages/project/[ref]/settings/jwt/index.test.tsx b/apps/studio/tests/pages/project/[ref]/settings/jwt/index.test.tsx index 8bffb73aab1..e1abf4f5b33 100644 --- a/apps/studio/tests/pages/project/[ref]/settings/jwt/index.test.tsx +++ b/apps/studio/tests/pages/project/[ref]/settings/jwt/index.test.tsx @@ -20,8 +20,9 @@ vi.mock('common', async () => { } }) -vi.mock('@/hooks/misc/useCheckPermissions', () => ({ - useAsyncCheckPermissions: mockUseAsyncCheckPermissions, +vi.mock('@/hooks/misc/useCheckPermissionsV2', () => ({ + useAsyncCheckPermissionsV2: mockUseAsyncCheckPermissions, + FGA_PERMISSIONS: { PROJECT: { AUTH_SIGNING_KEYS_READ: 'auth_signing_keys_read' } }, })) vi.mock('@/hooks/misc/useDeploymentMode', () => ({