mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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.
This commit is contained in:
1 parent
d9e5851467
commit
a5e2c62ef4
25 files changed
+350
-256
No files matched your search
@@ -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}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
@@ -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 (
|
||||
<div className={cn(InputVariants({ size }), baseClasses, 'items-center')}>
|
||||
<Skeleton className="h-2 w-48 rounded-full bg-foreground-muted" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!canReadAPIKeys) {
|
||||
if (!canReadAPIKeys && !isPermissionsLoading) {
|
||||
return (
|
||||
<div className={cn(InputVariants({ size }), baseClasses, 'items-center gap-2 font-normal')}>
|
||||
<EyeOffIcon />
|
||||
@@ -134,6 +118,14 @@ const ApiKeyInput = () => {
|
||||
)
|
||||
}
|
||||
|
||||
if (isApiKeysLoading || isPermissionsLoading) {
|
||||
return (
|
||||
<div className={cn(InputVariants({ size }), baseClasses, 'items-center')}>
|
||||
<Skeleton className="h-2 w-48 rounded-full bg-foreground-muted" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={cn(InputVariants({ size }), baseClasses, 'items-center gap-2 font-normal')}>
|
||||
|
||||
@@ -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={<CreateSecretAPIKeyDialog />}
|
||||
/>
|
||||
|
||||
{isLoadingApiKeys || isLoadingPermissions ? (
|
||||
<GenericSkeletonLoader />
|
||||
) : !canReadAPIKeys ? (
|
||||
{!canReadAPIKeys && !isLoadingPermissions ? (
|
||||
<Card>
|
||||
<div className="!rounded-b-md overflow-hidden py-12 flex flex-col gap-1 items-center justify-center">
|
||||
<EyeOffIcon />
|
||||
@@ -97,6 +91,8 @@ export const SecretAPIKeys = () => {
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
) : isLoadingApiKeys || isLoadingPermissions ? (
|
||||
<GenericSkeletonLoader />
|
||||
) : isErrorApiKeys ? (
|
||||
<AlertError error={error} subject="Failed to load secret API keys" />
|
||||
) : empty ? (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: () => (
|
||||
<span className="flex items-center gap-x-1">
|
||||
<Badge>Project: {project?.name}</Badge>
|
||||
<Badge>Public</Badge>
|
||||
<Badge className="capitalize">{anonKey.type}</Badge>
|
||||
</span>
|
||||
),
|
||||
icon: () => <Key />,
|
||||
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: () => (
|
||||
<span className="flex items-center gap-x-1">
|
||||
<Badge>Project: {project?.name}</Badge>
|
||||
<Badge variant="destructive">Secret</Badge>
|
||||
<Badge className="capitalize">{serviceKey.type}</Badge>
|
||||
</span>
|
||||
),
|
||||
icon: () => <Key />,
|
||||
},
|
||||
project &&
|
||||
publishableKey && {
|
||||
id: 'publishable-key',
|
||||
name: `Copy publishable key`,
|
||||
action: () => {
|
||||
copyToClipboard(publishableKey.api_key ?? '')
|
||||
setIsOpen(false)
|
||||
},
|
||||
badge: () => (
|
||||
<span className="flex items-center gap-x-1">
|
||||
<Badge>Project: {project?.name}</Badge>
|
||||
<Badge className="capitalize">{publishableKey.type}</Badge>
|
||||
</span>
|
||||
),
|
||||
icon: () => <Key />,
|
||||
},
|
||||
...(project && allSecretKeys
|
||||
? allSecretKeys.map((key) => ({
|
||||
id: key.id,
|
||||
name: `Copy secret key (${key.name})`,
|
||||
action: () => {
|
||||
copyToClipboard(key.api_key ?? '')
|
||||
setIsOpen(false)
|
||||
},
|
||||
badge: () => (
|
||||
<span className="flex items-center gap-x-1">
|
||||
<Badge>Project: {project?.name}</Badge>
|
||||
<Badge className="capitalize">{key.type}</Badge>
|
||||
</span>
|
||||
),
|
||||
icon: () => <Key />,
|
||||
}))
|
||||
: []),
|
||||
!(anonKey || serviceKey) && {
|
||||
id: 'api-keys-project-settings',
|
||||
name: 'See API keys in Project Settings',
|
||||
route: `/project/${ref}/settings/api`,
|
||||
badge: () => (
|
||||
<span className="flex items-center gap-x-1">
|
||||
<Badge>Project: {project?.name}</Badge>
|
||||
<Badge>Public</Badge>
|
||||
<Badge className="capitalize">{anonKey.type}</Badge>
|
||||
</span>
|
||||
),
|
||||
icon: () => <Key />,
|
||||
},
|
||||
].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: () => (
|
||||
<span className="flex items-center gap-x-1">
|
||||
<Badge>Project: {project?.name}</Badge>
|
||||
<Badge variant="destructive">Secret</Badge>
|
||||
<Badge className="capitalize">{serviceKey.type}</Badge>
|
||||
</span>
|
||||
),
|
||||
icon: () => <Key />,
|
||||
},
|
||||
project &&
|
||||
publishableKey && {
|
||||
id: 'publishable-key',
|
||||
name: `Copy publishable key`,
|
||||
action: () => {
|
||||
copyToClipboard(publishableKey.api_key ?? '')
|
||||
setIsOpen(false)
|
||||
},
|
||||
badge: () => (
|
||||
<span className="flex items-center gap-x-1">
|
||||
<Badge>Project: {project?.name}</Badge>
|
||||
<Badge className="capitalize">{publishableKey.type}</Badge>
|
||||
</span>
|
||||
),
|
||||
icon: () => <Key />,
|
||||
},
|
||||
...(project && allSecretKeys
|
||||
? allSecretKeys.map((key) => ({
|
||||
id: key.id,
|
||||
name: `Copy secret key (${key.name})`,
|
||||
action: () => {
|
||||
copyToClipboard(key.api_key ?? '')
|
||||
setIsOpen(false)
|
||||
},
|
||||
badge: () => (
|
||||
<span className="flex items-center gap-x-1">
|
||||
<Badge>Project: {project?.name}</Badge>
|
||||
<Badge className="capitalize">{key.type}</Badge>
|
||||
</span>
|
||||
),
|
||||
icon: () => <Key />,
|
||||
}))
|
||||
: []),
|
||||
!(anonKey || serviceKey) && {
|
||||
id: 'api-keys-project-settings',
|
||||
name: 'See API keys in Project Settings',
|
||||
route: `/project/${ref}/settings/api-keys`,
|
||||
icon: () => <Key />,
|
||||
},
|
||||
].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 },
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
+6
-1
@@ -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 ?? ''
|
||||
|
||||
|
||||
+6
-1
@@ -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 ?? ''
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
</button>
|
||||
{selectedLang == 'bash' && !isLoadingAPIKeys && apiKeys && apiKeys.length > 0 && (
|
||||
<div className="flex gap-x-1">
|
||||
<div className="flex items-center gap-2 p-1 pl-2 text-xs text-foreground-lighter">
|
||||
<Key size={12} strokeWidth={1.5} />
|
||||
<span>Project API key:</span>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="outline">
|
||||
{selectedApiKey.name === 'hide' ? 'Hide keys' : selectedApiKey.name}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" side="bottom">
|
||||
<DropdownMenuRadioGroup value={selectedApiKey.key}>
|
||||
<DropdownMenuRadioItem
|
||||
key="hide"
|
||||
value={DEFAULT_KEY.key}
|
||||
onClick={() => setSelectedApiKey(DEFAULT_KEY)}
|
||||
>
|
||||
Hide keys
|
||||
</DropdownMenuRadioItem>
|
||||
{selectedLang == 'bash' &&
|
||||
canReadAPIKeys &&
|
||||
!isLoadingAPIKeys &&
|
||||
apiKeys &&
|
||||
apiKeys.length > 0 && (
|
||||
<div className="flex gap-x-1">
|
||||
<div className="flex items-center gap-2 p-1 pl-2 text-xs text-foreground-lighter">
|
||||
<Key size={12} strokeWidth={1.5} />
|
||||
<span>Project API key:</span>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="outline">
|
||||
{selectedApiKey.name === 'hide' ? 'Hide keys' : selectedApiKey.name}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" side="bottom">
|
||||
<DropdownMenuRadioGroup value={selectedApiKey.key}>
|
||||
<DropdownMenuRadioItem
|
||||
key="hide"
|
||||
value={DEFAULT_KEY.key}
|
||||
onClick={() => setSelectedApiKey(DEFAULT_KEY)}
|
||||
>
|
||||
Hide keys
|
||||
</DropdownMenuRadioItem>
|
||||
|
||||
{publishableKeys.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Publishable keys</DropdownMenuLabel>
|
||||
{publishableKeys.map((key) => {
|
||||
{publishableKeys.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Publishable keys</DropdownMenuLabel>
|
||||
{publishableKeys.map((key) => {
|
||||
const value = key.api_key
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
key={key.id}
|
||||
value={value}
|
||||
onClick={() =>
|
||||
setSelectedApiKey({
|
||||
name: `Publishable key: ${key.name}`,
|
||||
key: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{key.name}
|
||||
</DropdownMenuRadioItem>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{secretKeys.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Secret keys</DropdownMenuLabel>
|
||||
{secretKeys.map((key) => {
|
||||
const value = key.prefix + '...'
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
key={key.id}
|
||||
value={value}
|
||||
onClick={() =>
|
||||
setSelectedApiKey({ name: `Secret key: ${key.name}`, key: value })
|
||||
}
|
||||
>
|
||||
{key.name}
|
||||
</DropdownMenuRadioItem>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>JWT-based legacy keys</DropdownMenuLabel>
|
||||
{legacyKeys.map((key) => {
|
||||
const value = key.api_key
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
key={key.id}
|
||||
value={value}
|
||||
onClick={() =>
|
||||
setSelectedApiKey({
|
||||
name: `Publishable key: ${key.name}`,
|
||||
key: value,
|
||||
})
|
||||
setSelectedApiKey({ name: `Legacy key: ${key.name}`, key: value })
|
||||
}
|
||||
>
|
||||
{key.name}
|
||||
</DropdownMenuRadioItem>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{secretKeys.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Secret keys</DropdownMenuLabel>
|
||||
{secretKeys.map((key) => {
|
||||
const value = key.prefix + '...'
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
key={key.id}
|
||||
value={value}
|
||||
onClick={() =>
|
||||
setSelectedApiKey({ name: `Secret key: ${key.name}`, key: value })
|
||||
}
|
||||
>
|
||||
{key.name}
|
||||
</DropdownMenuRadioItem>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>JWT-based legacy keys</DropdownMenuLabel>
|
||||
{legacyKeys.map((key) => {
|
||||
const value = key.api_key
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
key={key.id}
|
||||
value={value}
|
||||
onClick={() =>
|
||||
setSelectedApiKey({ name: `Legacy key: ${key.name}`, key: value })
|
||||
}
|
||||
>
|
||||
{key.name}
|
||||
</DropdownMenuRadioItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+8
-1
@@ -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 {
|
||||
|
||||
+3
-1
@@ -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<ResponseData | null>(null)
|
||||
const [error, setError] = useState<string | null>(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 })
|
||||
|
||||
@@ -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<typeof Collapsible_Shadcn_> {
|
||||
@@ -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 })
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]'
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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<string>()
|
||||
const [shownDialog, setShownDialog] = useState<DialogType>()
|
||||
|
||||
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 (
|
||||
<div className="bg-surface-100 rounded-md border shadow-sm">
|
||||
<div className="flex items-center py-8 px-8 space-x-2">
|
||||
<AlertCircle size={16} strokeWidth={1.5} />
|
||||
<p className="text-sm text-foreground-light">
|
||||
You don't have permission to view JWT signing keys. These keys are restricted to users
|
||||
with higher access levels.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <GenericSkeletonLoader />
|
||||
}
|
||||
@@ -163,7 +185,7 @@ export const JWTSecretKeysTable = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="-space-y-px">
|
||||
{legacyKey ? (
|
||||
{!canReadAPIKeys ? null : legacyKey ? (
|
||||
<>
|
||||
{standbyKey && (
|
||||
<ActionPanel
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal'
|
||||
import { number, object } from 'yup'
|
||||
import { useApiKeysVisibility } from '../APIKeys/hooks/useApiKeysVisibility'
|
||||
import {
|
||||
JWT_SECRET_UPDATE_ERROR_MESSAGES,
|
||||
JWT_SECRET_UPDATE_PROGRESS_MESSAGES,
|
||||
@@ -91,17 +92,19 @@ const JWTSettings = () => {
|
||||
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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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(
|
||||
|
||||
+6
-1
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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 })
|
||||
|
||||
Reference in new issue
Block a user