From a5e2c62ef47ea9e0dfb0fd102ee799fe07c9bafe Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:43:34 -0500 Subject: [PATCH] fix: check for api key permissions before requesting (#40452) To prevent sending unnecessary requests for API keys when the user can't view them (Read-Only Role), first check the permissions. --- .../interfaces/APIKeys/PublishableAPIKeys.tsx | 40 ++-- .../interfaces/APIKeys/SecretAPIKeys.tsx | 18 +- .../APIKeys/hooks/useApiKeysVisibility.ts | 20 +- .../interfaces/App/CommandMenu/ApiKeys.tsx | 165 +++++++++-------- .../components/interfaces/Connect/Connect.tsx | 2 +- .../ETL/DestinationPanel/DestinationPanel.tsx | 7 +- .../DestinationPanelFields.tsx | 7 +- .../Database/Hooks/FormContents.tsx | 7 +- .../Database/Hooks/HTTPRequestFields.tsx | 7 +- .../interfaces/Docs/Authentication.tsx | 4 +- .../interfaces/Docs/LangSelector.tsx | 173 +++++++++--------- .../EdgeFunctionDetails.tsx | 9 +- .../EdgeFunctionTesterSheet.tsx | 4 +- .../Functions/TerminalInstructions.tsx | 4 +- .../Home/NewProjectPanel/APIKeys.tsx | 12 +- .../CronJobs/HttpHeaderFieldsSection.tsx | 7 +- .../Integrations/GraphQL/GraphiQLTab.tsx | 7 +- .../jwt-secret-keys-table/index.tsx | 40 +++- .../interfaces/JwtSecrets/jwt-settings.tsx | 23 ++- .../ProjectAPIDocs/Content/Introduction.tsx | 4 +- .../ProjectAPIDocs/ProjectAPIDocs.tsx | 4 +- .../Inspector/RealtimeTokensPopover.tsx | 13 +- .../ConnectTablesDialog.tsx | 7 +- .../ProjectSettings/ToggleLegacyApiKeys.tsx | 15 +- .../iceberg-wrapper-create-mutation.ts | 7 +- 25 files changed, 350 insertions(+), 256 deletions(-) diff --git a/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx b/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx index cd0cda84cca..55d7a6d3c6b 100644 --- a/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx +++ b/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useMemo } from 'react' import { InputVariants } from '@ui/components/shadcn/ui/input' @@ -6,7 +5,6 @@ import { useParams } from 'common' import CopyButton from 'components/ui/CopyButton' import { FormHeader } from 'components/ui/Forms/FormHeader' import { useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { cn, EyeOffIcon, @@ -17,6 +15,7 @@ import { TooltipTrigger, WarningIcon, } from 'ui' +import { useApiKeysVisibility } from './hooks/useApiKeysVisibility' // to add in later with follow up PR // import CreatePublishableAPIKeyDialog from './CreatePublishableAPIKeyDialog' @@ -25,22 +24,19 @@ import { export const PublishableAPIKeys = () => { const { ref: projectRef } = useParams() + + const { canReadAPIKeys, isLoading: isLoadingVisibility } = useApiKeysVisibility() const { data: apiKeysData, isLoading: isLoadingApiKeys, error, - } = useAPIKeysQuery({ projectRef, reveal: false }) + } = useAPIKeysQuery({ projectRef, reveal: false }, { enabled: canReadAPIKeys }) const publishableApiKeys = useMemo( () => apiKeysData?.filter(({ type }) => type === 'publishable') ?? [], [apiKeysData] ) - const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - '*' - ) - // The default publisahble key will always be the first one const apiKey = publishableApiKeys[0] @@ -63,7 +59,7 @@ export const PublishableAPIKeys = () => { size="tiny" type="default" className="px-2 rounded-full" - disabled={isPermissionsLoading || isLoadingApiKeys || !canReadAPIKeys} + disabled={isLoadingVisibility || isLoadingApiKeys || !canReadAPIKeys} text={apiKey?.api_key} /> @@ -95,21 +91,17 @@ export const PublishableAPIKeys = () => { const ApiKeyInput = () => { const { ref: projectRef } = useParams() + const { canReadAPIKeys, isLoading: isPermissionsLoading } = useApiKeysVisibility() const { data: apiKeysData, isLoading: isApiKeysLoading, error, - } = useAPIKeysQuery({ projectRef, reveal: false }) + } = useAPIKeysQuery({ projectRef, reveal: false }, { enabled: canReadAPIKeys }) const publishableApiKeys = useMemo( () => apiKeysData?.filter(({ type }) => type === 'publishable') ?? [], [apiKeysData] ) - - const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - '*' - ) // The default publisahble key will always be the first one const apiKey = publishableApiKeys[0] @@ -117,15 +109,7 @@ const ApiKeyInput = () => { 'flex-1 grow gap-1 rounded-full min-w-0 max-w-[200px] sm:max-w-[300px] md:max-w-[400px] lg:min-w-[24rem]' const size = 'tiny' - if (isApiKeysLoading || isPermissionsLoading) { - return ( -
- -
- ) - } - - if (!canReadAPIKeys) { + if (!canReadAPIKeys && !isPermissionsLoading) { return (
@@ -134,6 +118,14 @@ const ApiKeyInput = () => { ) } + if (isApiKeysLoading || isPermissionsLoading) { + return ( +
+ +
+ ) + } + if (error) { return (
diff --git a/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx b/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx index 9bd053a40ce..9497705b5e3 100644 --- a/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx +++ b/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import dayjs from 'dayjs' import { useMemo, useRef } from 'react' @@ -7,7 +6,6 @@ import AlertError from 'components/ui/AlertError' import { FormHeader } from 'components/ui/Forms/FormHeader' import { APIKeysData, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import useLogsQuery from 'hooks/analytics/useLogsQuery' -import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Card, EyeOffIcon } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { @@ -19,6 +17,7 @@ import { } from 'ui/src/components/shadcn/ui/table' import { APIKeyRow } from './APIKeyRow' import CreateSecretAPIKeyDialog from './CreateSecretAPIKeyDialog' +import { useApiKeysVisibility } from './hooks/useApiKeysVisibility' interface LastSeenData { [hash: string]: { timestamp: string } @@ -51,17 +50,14 @@ function useLastSeen(projectRef: string): LastSeenData { export const SecretAPIKeys = () => { const { ref: projectRef } = useParams() + + const { canReadAPIKeys, isLoading: isLoadingPermissions } = useApiKeysVisibility() const { data: apiKeysData, error, isLoading: isLoadingApiKeys, isError: isErrorApiKeys, - } = useAPIKeysQuery({ projectRef, reveal: false }) - - const { can: canReadAPIKeys, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( - PermissionAction.TENANT_SQL_ADMIN_WRITE, - '*' - ) + } = useAPIKeysQuery({ projectRef, reveal: false }, { enabled: canReadAPIKeys }) const lastSeen = useLastSeen(projectRef!) @@ -83,9 +79,7 @@ export const SecretAPIKeys = () => { actions={} /> - {isLoadingApiKeys || isLoadingPermissions ? ( - - ) : !canReadAPIKeys ? ( + {!canReadAPIKeys && !isLoadingPermissions ? (
@@ -97,6 +91,8 @@ export const SecretAPIKeys = () => {

+ ) : isLoadingApiKeys || isLoadingPermissions ? ( + ) : isErrorApiKeys ? ( ) : empty ? ( diff --git a/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts b/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts index 7cedd8a9f1b..accb9ac4a09 100644 --- a/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts +++ b/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts @@ -19,12 +19,18 @@ interface ApiKeysVisibilityState { */ export function useApiKeysVisibility(): ApiKeysVisibilityState { const { ref: projectRef } = useParams() - const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.READ, 'api_keys') + const { can: canReadAPIKeys, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.SECRETS_READ, + '*' + ) - const { data: apiKeysData, isLoading } = useAPIKeysQuery({ - projectRef, - reveal: false, - }) + const { data: apiKeysData, isLoading: isLoadingApiKeys } = useAPIKeysQuery( + { + projectRef, + reveal: false, + }, + { enabled: canReadAPIKeys } + ) const publishableApiKeys = useMemo( () => apiKeysData?.filter(({ type }) => type === 'publishable') ?? [], @@ -36,14 +42,14 @@ export function useApiKeysVisibility(): ApiKeysVisibilityState { const hasApiKeys = publishableApiKeys.length > 0 // Can initialize API keys when in rollout, has permissions, not loading, and no API keys yet - const canInitApiKeys = canReadAPIKeys && !isLoading && !hasApiKeys + const canInitApiKeys = canReadAPIKeys && !isLoadingApiKeys && !hasApiKeys // Disable UI for publishable keys and secrets keys if flag is not enabled OR no API keys created yet const shouldDisableUI = !hasApiKeys return { hasApiKeys, - isLoading, + isLoading: isLoadingPermissions || (canReadAPIKeys && isLoadingApiKeys), canReadAPIKeys, canInitApiKeys, shouldDisableUI, diff --git a/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx b/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx index 10c0e5df990..07ecb3652d1 100644 --- a/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx +++ b/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx @@ -1,6 +1,7 @@ import { Key } from 'lucide-react' import { useMemo } from 'react' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Badge, copyToClipboard } from 'ui' @@ -24,88 +25,92 @@ export function useApiKeysCommands() { const { data: project } = useSelectedProjectQuery() const ref = project?.ref || '_' - const { data: apiKeys } = useAPIKeysQuery({ projectRef: project?.ref, reveal: true }) - const { anonKey, serviceKey, publishableKey, allSecretKeys } = getKeys(apiKeys) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { projectRef: project?.ref, reveal: true }, + { enabled: canReadAPIKeys } + ) + const commands = useMemo(() => { + const { anonKey, serviceKey, publishableKey, allSecretKeys } = canReadAPIKeys + ? getKeys(apiKeys) + : {} - const commands = useMemo( - () => - [ - project && - anonKey && { - id: 'anon-key', - name: `Copy anonymous API key`, - action: () => { - copyToClipboard(anonKey.api_key ?? '') - setIsOpen(false) - }, - badge: () => ( - - Project: {project?.name} - Public - {anonKey.type} - - ), - icon: () => , + return [ + project && + anonKey && { + id: 'anon-key', + name: `Copy anonymous API key`, + action: () => { + copyToClipboard(anonKey.api_key ?? '') + setIsOpen(false) }, - project && - serviceKey && { - id: 'service-key', - name: `Copy service API key`, - action: () => { - copyToClipboard(serviceKey.api_key ?? '') - setIsOpen(false) - }, - badge: () => ( - - Project: {project?.name} - Secret - {serviceKey.type} - - ), - icon: () => , - }, - project && - publishableKey && { - id: 'publishable-key', - name: `Copy publishable key`, - action: () => { - copyToClipboard(publishableKey.api_key ?? '') - setIsOpen(false) - }, - badge: () => ( - - Project: {project?.name} - {publishableKey.type} - - ), - icon: () => , - }, - ...(project && allSecretKeys - ? allSecretKeys.map((key) => ({ - id: key.id, - name: `Copy secret key (${key.name})`, - action: () => { - copyToClipboard(key.api_key ?? '') - setIsOpen(false) - }, - badge: () => ( - - Project: {project?.name} - {key.type} - - ), - icon: () => , - })) - : []), - !(anonKey || serviceKey) && { - id: 'api-keys-project-settings', - name: 'See API keys in Project Settings', - route: `/project/${ref}/settings/api`, + badge: () => ( + + Project: {project?.name} + Public + {anonKey.type} + + ), icon: () => , }, - ].filter(Boolean) as ICommand[], - [anonKey, serviceKey, project, setIsOpen] - ) + project && + serviceKey && { + id: 'service-key', + name: `Copy service API key`, + action: () => { + copyToClipboard(serviceKey.api_key ?? '') + setIsOpen(false) + }, + badge: () => ( + + Project: {project?.name} + Secret + {serviceKey.type} + + ), + icon: () => , + }, + project && + publishableKey && { + id: 'publishable-key', + name: `Copy publishable key`, + action: () => { + copyToClipboard(publishableKey.api_key ?? '') + setIsOpen(false) + }, + badge: () => ( + + Project: {project?.name} + {publishableKey.type} + + ), + icon: () => , + }, + ...(project && allSecretKeys + ? allSecretKeys.map((key) => ({ + id: key.id, + name: `Copy secret key (${key.name})`, + action: () => { + copyToClipboard(key.api_key ?? '') + setIsOpen(false) + }, + badge: () => ( + + Project: {project?.name} + {key.type} + + ), + icon: () => , + })) + : []), + !(anonKey || serviceKey) && { + id: 'api-keys-project-settings', + name: 'See API keys in Project Settings', + route: `/project/${ref}/settings/api-keys`, + icon: () => , + }, + ].filter(Boolean) as ICommand[] + }, [apiKeys, canReadAPIKeys, project, ref, setIsOpen]) useRegisterPage( API_KEYS_PAGE_NAME, @@ -119,7 +124,7 @@ export function useApiKeysCommands() { }, ], }, - { deps: [commands], enabled: !!project } + { deps: [commands], enabled: !!project && commands.length > 0 } ) useRegisterCommands( @@ -133,7 +138,7 @@ export function useApiKeysCommands() { }, ], { - enabled: !!project, + enabled: !!project && commands.length > 0, orderSection: orderCommandSectionsByPriority, sectionMeta: { priority: 3 }, } diff --git a/apps/studio/components/interfaces/Connect/Connect.tsx b/apps/studio/components/interfaces/Connect/Connect.tsx index 792661303b9..86c648b9746 100644 --- a/apps/studio/components/interfaces/Connect/Connect.tsx +++ b/apps/studio/components/interfaces/Connect/Connect.tsx @@ -217,7 +217,7 @@ export const Connect = () => { return [] } - const { data: apiKeys } = useAPIKeysQuery({ projectRef }) + const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys }) const { anonKey, publishableKey } = canReadAPIKeys ? getKeys(apiKeys) : { anonKey: null, publishableKey: null } diff --git a/apps/studio/components/interfaces/Database/ETL/DestinationPanel/DestinationPanel.tsx b/apps/studio/components/interfaces/Database/ETL/DestinationPanel/DestinationPanel.tsx index 1cb9cd8f7c0..c37832da73a 100644 --- a/apps/studio/components/interfaces/Database/ETL/DestinationPanel/DestinationPanel.tsx +++ b/apps/studio/components/interfaces/Database/ETL/DestinationPanel/DestinationPanel.tsx @@ -44,6 +44,7 @@ import { AnalyticsBucketFields, BigQueryFields } from './DestinationPanelFields' import { DestinationTypeSelection } from './DestinationTypeSelection' import { NoDestinationsAvailable } from './NoDestinationsAvailable' import { PublicationSelection } from './PublicationSelection' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' const formId = 'destination-editor' @@ -126,7 +127,11 @@ export const DestinationPanel = ({ pipelineId: existingDestination?.pipelineId, }) - const { data: apiKeys } = useAPIKeysQuery({ projectRef, reveal: true }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { projectRef, reveal: true }, + { enabled: canReadAPIKeys } + ) const { serviceKey } = getKeys(apiKeys) const catalogToken = serviceKey?.api_key ?? '' diff --git a/apps/studio/components/interfaces/Database/ETL/DestinationPanel/DestinationPanelFields.tsx b/apps/studio/components/interfaces/Database/ETL/DestinationPanel/DestinationPanelFields.tsx index 4bba98af5c7..67810925baa 100644 --- a/apps/studio/components/interfaces/Database/ETL/DestinationPanel/DestinationPanelFields.tsx +++ b/apps/studio/components/interfaces/Database/ETL/DestinationPanel/DestinationPanelFields.tsx @@ -3,6 +3,7 @@ import { useMemo, useState } from 'react' import type { UseFormReturn } from 'react-hook-form' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { getCatalogURI } from 'components/interfaces/Storage/StorageSettings/StorageSettings.utils' import { InlineLink } from 'components/ui/InlineLink' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' @@ -125,7 +126,11 @@ export const AnalyticsBucketFields = ({ const { ref: projectRef } = useParams() const { data: project } = useSelectedProjectQuery() - const { data: apiKeys } = useAPIKeysQuery({ projectRef, reveal: true }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { projectRef, reveal: true }, + { enabled: canReadAPIKeys } + ) const { serviceKey } = getKeys(apiKeys) const serviceApiKey = serviceKey?.api_key ?? '' diff --git a/apps/studio/components/interfaces/Database/Hooks/FormContents.tsx b/apps/studio/components/interfaces/Database/Hooks/FormContents.tsx index eedcb49b1cc..b72c5594ea8 100644 --- a/apps/studio/components/interfaces/Database/Hooks/FormContents.tsx +++ b/apps/studio/components/interfaces/Database/Hooks/FormContents.tsx @@ -3,6 +3,7 @@ import Image from 'next/legacy/image' import { MutableRefObject, useEffect } from 'react' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection' import { useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' @@ -50,7 +51,11 @@ export const FormContents = ({ const restUrl = project?.restUrl const restUrlTld = restUrl ? new URL(restUrl).hostname.split('.').pop() : 'co' - const { data: keys = [] } = useAPIKeysQuery({ projectRef: ref, reveal: true }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: keys = [] } = useAPIKeysQuery( + { projectRef: ref, reveal: true }, + { enabled: canReadAPIKeys } + ) const { data: functions = [], isSuccess: isSuccessEdgeFunctions } = useEdgeFunctionsQuery({ projectRef: ref, }) diff --git a/apps/studio/components/interfaces/Database/Hooks/HTTPRequestFields.tsx b/apps/studio/components/interfaces/Database/Hooks/HTTPRequestFields.tsx index fbf3f3e3dd1..d15c54b571e 100644 --- a/apps/studio/components/interfaces/Database/Hooks/HTTPRequestFields.tsx +++ b/apps/studio/components/interfaces/Database/Hooks/HTTPRequestFields.tsx @@ -2,6 +2,7 @@ import { ChevronDown, Plus, X } from 'lucide-react' import Link from 'next/link' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' @@ -51,7 +52,11 @@ const HTTPRequestFields = ({ const { data: selectedProject } = useSelectedProjectQuery() const { data: functions } = useEdgeFunctionsQuery({ projectRef: ref }) - const { data: apiKeys } = useAPIKeysQuery({ projectRef: ref, reveal: true }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { projectRef: ref, reveal: true }, + { enabled: canReadAPIKeys } + ) const edgeFunctions = functions ?? [] const { serviceKey, secretKey } = getKeys(apiKeys) diff --git a/apps/studio/components/interfaces/Docs/Authentication.tsx b/apps/studio/components/interfaces/Docs/Authentication.tsx index 3662e4572f5..43ec3fefbab 100644 --- a/apps/studio/components/interfaces/Docs/Authentication.tsx +++ b/apps/studio/components/interfaces/Docs/Authentication.tsx @@ -3,6 +3,7 @@ import Link from 'next/link' import { useParams } from 'common' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' +import { useApiKeysVisibility } from '../APIKeys/hooks/useApiKeysVisibility' import CodeSnippet from './CodeSnippet' import Snippets from './Snippets' @@ -13,7 +14,8 @@ interface AuthenticationProps { const Authentication = ({ selectedLang, showApiKey }: AuthenticationProps) => { const { ref: projectRef } = useParams() - const { data: apiKeys } = useAPIKeysQuery({ projectRef }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys }) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { anonKey, serviceKey } = getKeys(apiKeys) diff --git a/apps/studio/components/interfaces/Docs/LangSelector.tsx b/apps/studio/components/interfaces/Docs/LangSelector.tsx index 5b8c7655910..6a053ecfab5 100644 --- a/apps/studio/components/interfaces/Docs/LangSelector.tsx +++ b/apps/studio/components/interfaces/Docs/LangSelector.tsx @@ -15,6 +15,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from 'ui' +import { useApiKeysVisibility } from '../APIKeys/hooks/useApiKeysVisibility' const DEFAULT_KEY = { name: 'hide', key: 'SUPABASE_KEY' } @@ -33,10 +34,14 @@ export const LangSelector = ({ }: LangSelectorProps) => { const { ref: projectRef } = useParams() - const { data: apiKeys = [], isLoading: isLoadingAPIKeys } = useAPIKeysQuery({ - projectRef, - reveal: false, - }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys = [], isLoading: isLoadingAPIKeys } = useAPIKeysQuery( + { + projectRef, + reveal: false, + }, + { enabled: canReadAPIKeys } + ) const legacyKeys = useMemo(() => apiKeys.filter(({ type }) => type === 'legacy'), [apiKeys]) const publishableKeys = useMemo( @@ -70,97 +75,101 @@ export const LangSelector = ({ > Bash - {selectedLang == 'bash' && !isLoadingAPIKeys && apiKeys && apiKeys.length > 0 && ( -
-
- - Project API key: -
- - - - - - - setSelectedApiKey(DEFAULT_KEY)} - > - Hide keys - + {selectedLang == 'bash' && + canReadAPIKeys && + !isLoadingAPIKeys && + apiKeys && + apiKeys.length > 0 && ( +
+
+ + Project API key: +
+ + + + + + + setSelectedApiKey(DEFAULT_KEY)} + > + Hide keys + - {publishableKeys.length > 0 && ( - <> - - Publishable keys - {publishableKeys.map((key) => { + {publishableKeys.length > 0 && ( + <> + + Publishable keys + {publishableKeys.map((key) => { + const value = key.api_key + return ( + + setSelectedApiKey({ + name: `Publishable key: ${key.name}`, + key: value, + }) + } + > + {key.name} + + ) + })} + + )} + + {secretKeys.length > 0 && ( + <> + + Secret keys + {secretKeys.map((key) => { + const value = key.prefix + '...' + return ( + + setSelectedApiKey({ name: `Secret key: ${key.name}`, key: value }) + } + > + {key.name} + + ) + })} + + )} + + + + + JWT-based legacy keys + {legacyKeys.map((key) => { const value = key.api_key return ( - setSelectedApiKey({ - name: `Publishable key: ${key.name}`, - key: value, - }) + setSelectedApiKey({ name: `Legacy key: ${key.name}`, key: value }) } > {key.name} ) })} - - )} - - {secretKeys.length > 0 && ( - <> - - Secret keys - {secretKeys.map((key) => { - const value = key.prefix + '...' - return ( - - setSelectedApiKey({ name: `Secret key: ${key.name}`, key: value }) - } - > - {key.name} - - ) - })} - - )} - - - - - JWT-based legacy keys - {legacyKeys.map((key) => { - const value = key.api_key - return ( - - setSelectedApiKey({ name: `Legacy key: ${key.name}`, key: value }) - } - > - {key.name} - - ) - })} - - - - -
- )} + +
+
+
+
+ )}
) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx index e3ee1011ba7..f32bddfdc47 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -10,6 +10,7 @@ import { toast } from 'sonner' import z from 'zod' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import AlertError from 'components/ui/AlertError' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' @@ -83,7 +84,13 @@ export const EdgeFunctionDetails = () => { '*' ) - const { data: apiKeys } = useAPIKeysQuery({ projectRef }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { + projectRef, + }, + { enabled: canReadAPIKeys } + ) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: customDomainData } = useCustomDomainsQuery({ projectRef }) const { diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx index 11366aa59ea..06585eba6e0 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx @@ -5,6 +5,7 @@ import { useFieldArray, useForm } from 'react-hook-form' import * as z from 'zod' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useSessionAccessTokenQuery } from 'data/auth/session-access-token-query' @@ -87,7 +88,8 @@ export const EdgeFunctionTesterSheet = ({ visible, onClose }: EdgeFunctionTester const [response, setResponse] = useState(null) const [error, setError] = useState(null) - const { data: apiKeys } = useAPIKeysQuery({ projectRef }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys }) const { data: config } = useProjectPostgrestConfigQuery({ projectRef }) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: accessToken } = useSessionAccessTokenQuery({ enabled: IS_PLATFORM }) diff --git a/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx b/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx index 3de5cd9e0b0..42d3abc5dcf 100644 --- a/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx +++ b/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx @@ -16,6 +16,7 @@ import { CollapsibleTrigger_Shadcn_, Collapsible_Shadcn_, } from 'ui' +import { useApiKeysVisibility } from '../APIKeys/hooks/useApiKeysVisibility' import type { Commands } from './Functions.types' interface TerminalInstructionsProps extends ComponentPropsWithoutRef { @@ -32,7 +33,8 @@ export const TerminalInstructions = forwardRef< const [showInstructions, setShowInstructions] = useState(!closable) const { data: tokens } = useAccessTokensQuery() - const { data: apiKeys } = useAPIKeysQuery({ projectRef }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys }) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: customDomainData } = useCustomDomainsQuery({ projectRef }) diff --git a/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx b/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx index 60298fafa9d..49ca5fafef1 100644 --- a/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx +++ b/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx @@ -1,15 +1,14 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events' import { AlertCircle, Loader } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import Panel from 'components/ui/Panel' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-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 { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { Input, SimpleCodeBlock } from 'ui' @@ -56,10 +55,10 @@ export const APIKeys = () => { isLoading: isProjectSettingsLoading, } = useProjectSettingsV2Query({ projectRef }) - const { data: apiKeys } = useAPIKeysQuery({ projectRef }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys }) const { anonKey, serviceKey } = getKeys(apiKeys) - // API keys should not be empty. However it can be populated with a delay on project creation const isApiKeysEmpty = !anonKey && !serviceKey const { @@ -77,11 +76,6 @@ export const APIKeys = () => { const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus - const { can: canReadAPIKeys } = useAsyncCheckPermissions( - PermissionAction.READ, - 'service_api_keys' - ) - const isNotUpdatingJwtSecret = jwtSecretUpdateStatus === undefined || jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updated diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx index fb4cb999813..e1c3b042322 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx @@ -2,6 +2,7 @@ import { ChevronDown, Plus, Trash } from 'lucide-react' import { useFieldArray } from 'react-hook-form' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { Button, @@ -32,7 +33,11 @@ export const HTTPHeaderFieldsSection = ({ variant }: HTTPHeaderFieldsSectionProp }) const { ref } = useParams() - const { data: apiKeys } = useAPIKeysQuery({ projectRef: ref, reveal: true }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { projectRef: ref, reveal: true }, + { enabled: canReadAPIKeys } + ) const { serviceKey, secretKey } = getKeys(apiKeys) const apiKey = secretKey?.api_key ?? serviceKey?.api_key ?? '[YOUR API KEY]' diff --git a/apps/studio/components/interfaces/Integrations/GraphQL/GraphiQLTab.tsx b/apps/studio/components/interfaces/Integrations/GraphQL/GraphiQLTab.tsx index 2f5cd906fc9..303779f0a3f 100644 --- a/apps/studio/components/interfaces/Integrations/GraphQL/GraphiQLTab.tsx +++ b/apps/studio/components/interfaces/Integrations/GraphQL/GraphiQLTab.tsx @@ -5,6 +5,7 @@ import { useMemo } from 'react' import { toast } from 'sonner' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import GraphiQL from 'components/interfaces/GraphQL/GraphiQL' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useSessionAccessTokenQuery } from 'data/auth/session-access-token-query' @@ -21,7 +22,11 @@ export const GraphiQLTab = () => { const { data: accessToken } = useSessionAccessTokenQuery({ enabled: IS_PLATFORM }) - const { data: apiKeys, isFetched } = useAPIKeysQuery({ projectRef, reveal: true }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys, isFetched } = useAPIKeysQuery( + { projectRef, reveal: true }, + { enabled: canReadAPIKeys } + ) const { serviceKey, secretKey } = getKeys(apiKeys) const { data: config } = useProjectPostgrestConfigQuery({ projectRef }) 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 9a4375be508..933e854a0f2 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 @@ -1,9 +1,10 @@ import { AnimatePresence } from 'framer-motion' -import { RotateCw, Timer } from 'lucide-react' +import { AlertCircle, RotateCw, Timer } from 'lucide-react' import { useMemo, useState } from 'react' import { toast } from 'sonner' import { useFlag, useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useLegacyAPIKeysStatusQuery } from 'data/api-keys/legacy-api-keys-status-query' import { useJWTSigningKeyDeleteMutation } from 'data/jwt-signing-keys/jwt-signing-key-delete-mutation' @@ -57,14 +58,21 @@ export const JWTSecretKeysTable = () => { const [selectedKeyToUpdate, setSelectedKeyToUpdate] = useState() const [shownDialog, setShownDialog] = useState() - const { data: signingKeys, isLoading: isLoadingSigningKeys } = useJWTSigningKeysQuery({ - projectRef, - }) - const { data: legacyKey, isLoading: isLoadingLegacyKey } = useLegacyJWTSigningKeyQuery({ - projectRef, - }) + const { canReadAPIKeys, isLoading: isLoadingCanReadAPIKeys } = useApiKeysVisibility() + const { data: signingKeys, isLoading: isLoadingSigningKeys } = useJWTSigningKeysQuery( + { + projectRef, + }, + { enabled: canReadAPIKeys } + ) + const { data: legacyKey, isLoading: isLoadingLegacyKey } = useLegacyJWTSigningKeyQuery( + { + projectRef, + }, + { enabled: canReadAPIKeys } + ) const { data: legacyAPIKeysStatus, isLoading: isLoadingLegacyAPIKeysStatus } = - useLegacyAPIKeysStatusQuery({ projectRef }) + useLegacyAPIKeysStatusQuery({ projectRef }, { enabled: canReadAPIKeys }) const { mutate: migrateJWTSecret, isLoading: isMigrating } = useLegacyJWTSigningKeyCreateMutation( { @@ -152,6 +160,20 @@ export const JWTSecretKeysTable = () => { ) } + if (!canReadAPIKeys && !isLoadingCanReadAPIKeys) { + return ( +
+
+ +

+ You don't have permission to view JWT signing keys. These keys are restricted to users + with higher access levels. +

+
+
+ ) + } + if (isLoading) { return } @@ -163,7 +185,7 @@ export const JWTSecretKeysTable = () => { return ( <>
- {legacyKey ? ( + {!canReadAPIKeys ? null : legacyKey ? ( <> {standbyKey && ( { const { mutateAsync: updateJwt, isLoading: isSubmittingJwtSecretUpdateRequest } = useJwtSecretUpdateMutation() - const { data: legacyKey } = useLegacyJWTSigningKeyQuery({ - projectRef, - }) - const { data: legacyAPIKeysStatus } = useLegacyAPIKeysStatusQuery({ projectRef }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: legacyKey } = useLegacyJWTSigningKeyQuery( + { + projectRef, + }, + { enabled: canReadAPIKeys } + ) + const { data: legacyAPIKeysStatus } = useLegacyAPIKeysStatusQuery( + { projectRef }, + { enabled: canReadAPIKeys } + ) - const { - data: authConfig, - error: authConfigError, - isLoading: isLoadingAuthConfig, - isSuccess: isSuccessAuthConfig, - } = useAuthConfigQuery({ projectRef }) + const { data: authConfig, isLoading: isLoadingAuthConfig } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isLoading: isUpdatingAuthConfig } = useAuthConfigUpdateMutation() diff --git a/apps/studio/components/interfaces/ProjectAPIDocs/Content/Introduction.tsx b/apps/studio/components/interfaces/ProjectAPIDocs/Content/Introduction.tsx index cd12e9babf8..0f17f7a6fbd 100644 --- a/apps/studio/components/interfaces/ProjectAPIDocs/Content/Introduction.tsx +++ b/apps/studio/components/interfaces/ProjectAPIDocs/Content/Introduction.tsx @@ -1,6 +1,7 @@ import { useParams } from 'common' import { Button, Input, copyToClipboard } from 'ui' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' @@ -13,7 +14,8 @@ import type { ContentProps } from './Content.types' export const Introduction = ({ showKeys, language, apikey, endpoint }: ContentProps) => { const { ref } = useParams() - const { data: apiKeys } = useAPIKeysQuery({ projectRef: ref }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery({ projectRef: ref }, { enabled: canReadAPIKeys }) const { data } = useProjectSettingsV2Query({ projectRef: ref }) const { data: org } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() diff --git a/apps/studio/components/interfaces/ProjectAPIDocs/ProjectAPIDocs.tsx b/apps/studio/components/interfaces/ProjectAPIDocs/ProjectAPIDocs.tsx index ac99c9d48a2..f2b73315aeb 100644 --- a/apps/studio/components/interfaces/ProjectAPIDocs/ProjectAPIDocs.tsx +++ b/apps/studio/components/interfaces/ProjectAPIDocs/ProjectAPIDocs.tsx @@ -6,6 +6,7 @@ import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' import { useAppStateSnapshot } from 'state/app-state' +import { useApiKeysVisibility } from '../APIKeys/hooks/useApiKeysVisibility' import { Bucket } from './Content/Bucket' import { EdgeFunction } from './Content/EdgeFunction' import { EdgeFunctions } from './Content/EdgeFunctions' @@ -43,9 +44,10 @@ export const ProjectAPIDocs = () => { const [showKeys, setShowKeys] = useState(false) const language = snap.docsLanguage + const { canReadAPIKeys } = useApiKeysVisibility() const { data: apiKeys } = useAPIKeysQuery( { projectRef: ref }, - { enabled: snap.showProjectApiDocs } + { enabled: snap.showProjectApiDocs && canReadAPIKeys } ) const { data: settings } = useProjectSettingsV2Query( { projectRef: ref }, diff --git a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover.tsx b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover.tsx index 096529b7163..6e6e23c3723 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover.tsx @@ -2,6 +2,7 @@ import { Dispatch, SetStateAction, useEffect, useRef } from 'react' import { toast } from 'sonner' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { getTemporaryAPIKey } from 'data/api-keys/temp-api-keys-query' @@ -23,10 +24,14 @@ export const RealtimeTokensPopover = ({ config, onChangeConfig }: RealtimeTokens const { data: org } = useSelectedOrganizationQuery() const snap = useRoleImpersonationStateSnapshot() - const { data: apiKeys } = useAPIKeysQuery({ - projectRef: config.projectRef, - reveal: true, - }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { + projectRef: config.projectRef, + reveal: true, + }, + { enabled: canReadAPIKeys } + ) const { anonKey, publishableKey } = getKeys(apiKeys) const { data: postgrestConfig } = useProjectPostgrestConfigQuery( diff --git a/apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/ConnectTablesDialog.tsx b/apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/ConnectTablesDialog.tsx index 68cbd5a1b59..db8302bf601 100644 --- a/apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/ConnectTablesDialog.tsx +++ b/apps/studio/components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/ConnectTablesDialog.tsx @@ -8,6 +8,7 @@ import { toast } from 'sonner' import z from 'zod' import { useFlag, useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { convertKVStringArrayToJson } from 'components/interfaces/Integrations/Wrappers/Wrappers.utils' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' @@ -156,7 +157,11 @@ export const ConnectTablesDialogContent = ({ const wrapperValues = convertKVStringArrayToJson(wrapperInstance?.server_options ?? []) const { data: projectSettings } = useProjectSettingsV2Query({ projectRef }) - const { data: apiKeys } = useAPIKeysQuery({ projectRef, reveal: true }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { projectRef, reveal: true }, + { enabled: canReadAPIKeys } + ) const { serviceKey } = getKeys(apiKeys) const { sourceId, pipeline, publication } = useAnalyticsBucketAssociatedEntities({ diff --git a/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx b/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx index 8998ab5c145..ecbd079f512 100644 --- a/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx +++ b/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useToggleLegacyAPIKeysMutation } from 'data/api-keys/legacy-api-key-toggle-mutation' import { useLegacyAPIKeysStatusQuery } from 'data/api-keys/legacy-api-keys-status-query' @@ -30,16 +31,20 @@ export const ToggleLegacyApiKeysPanel = () => { const [isConfirmOpen, setIsConfirmOpen] = useState(false) const [isAppsWarningOpen, setIsAppsWarningOpen] = useState(false) - const { data: legacyAPIKeysStatusData, isSuccess: isLegacyAPIKeysStatusSuccess } = - useLegacyAPIKeysStatusQuery({ projectRef }) - - const { data: legacyJWTSecret } = useLegacyJWTSigningKeyQuery({ projectRef }) - + const { canReadAPIKeys } = useApiKeysVisibility() const { can: canUpdateAPIKeys, isSuccess: isPermissionsSuccess } = useAsyncCheckPermissions( PermissionAction.SECRETS_WRITE, '*' ) + const { data: legacyAPIKeysStatusData, isSuccess: isLegacyAPIKeysStatusSuccess } = + useLegacyAPIKeysStatusQuery({ projectRef }, { enabled: canReadAPIKeys }) + + const { data: legacyJWTSecret } = useLegacyJWTSigningKeyQuery( + { projectRef }, + { enabled: canReadAPIKeys } + ) + const { data: authorizedApps = [], isSuccess: isAuthorizedAppsSuccess } = useAuthorizedAppsQuery({ slug: org?.slug, }) diff --git a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts index d8703c18241..15f0c37d46c 100644 --- a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts +++ b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts @@ -1,5 +1,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import { WRAPPERS } from 'components/interfaces/Integrations/Wrappers/Wrappers.constants' import { getAnalyticsBucketFDWName, @@ -19,7 +20,11 @@ import { useS3AccessKeyCreateMutation } from './s3-access-key-create-mutation' export const useIcebergWrapperCreateMutation = () => { const { data: project } = useSelectedProjectQuery() - const { data: apiKeys } = useAPIKeysQuery({ projectRef: project?.ref, reveal: true }) + const { canReadAPIKeys } = useApiKeysVisibility() + const { data: apiKeys } = useAPIKeysQuery( + { projectRef: project?.ref, reveal: true }, + { enabled: canReadAPIKeys } + ) const { secretKey, serviceKey } = getKeys(apiKeys) const { data: settings } = useProjectSettingsV2Query({ projectRef: project?.ref })