diff --git a/apps/studio/components/interfaces/Docs/LangSelector.tsx b/apps/studio/components/interfaces/Docs/LangSelector.tsx
index b523ded9d5312..341071c2b7ecb 100644
--- a/apps/studio/components/interfaces/Docs/LangSelector.tsx
+++ b/apps/studio/components/interfaces/Docs/LangSelector.tsx
@@ -1,15 +1,18 @@
-import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Key } from 'lucide-react'
+import { useMemo } from 'react'
import { useParams } from 'common'
import type { showApiKey } from 'components/interfaces/Docs/Docs.types'
-import { getAPIKeys, useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
-import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
+import { useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import {
Button,
DropdownMenu,
DropdownMenuContent,
- DropdownMenuItem,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
DropdownMenuTrigger,
} from 'ui'
@@ -17,25 +20,30 @@ const DEFAULT_KEY = { name: 'hide', key: 'SUPABASE_KEY' }
interface LangSelectorProps {
selectedLang: string
- showApiKey: showApiKey
+ selectedApiKey: showApiKey
setSelectedLang: (selectedLang: string) => void
- setShowApiKey: (showApiKey: showApiKey) => void
+ setSelectedApiKey: (showApiKey: showApiKey) => void
}
const LangSelector = ({
selectedLang,
- showApiKey,
+ selectedApiKey,
setSelectedLang,
- setShowApiKey,
+ setSelectedApiKey,
}: LangSelectorProps) => {
const { ref: projectRef } = useParams()
- const canReadServiceKey = useCheckPermissions(
- PermissionAction.READ,
- 'service_api_keys.service_role_key'
- )
- const { data: settings } = useProjectSettingsV2Query({ projectRef })
- const { anonKey: anonApiKey, serviceKey: serviceApiKey } = getAPIKeys(settings)
+ const { data: apiKeys = [], isLoading: isLoadingAPIKeys } = useAPIKeysQuery({
+ projectRef,
+ reveal: false,
+ })
+
+ const legacyKeys = useMemo(() => apiKeys.filter(({ type }) => type === 'legacy'), [apiKeys])
+ const publishableKeys = useMemo(
+ () => apiKeys.filter(({ type }) => type === 'publishable'),
+ [apiKeys]
+ )
+ const secretKeys = useMemo(() => apiKeys.filter(({ type }) => type === 'secret'), [apiKeys])
return (
@@ -62,48 +70,93 @@ const LangSelector = ({
>
Bash
- {selectedLang == 'bash' && (
-
+ {selectedLang == 'bash' && !isLoadingAPIKeys && apiKeys && apiKeys.length > 0 && (
+
- Project API key :
+ Project API key:
- {showApiKey.name}
+
+ {selectedApiKey.name === 'hide' ? 'Hide keys' : selectedApiKey.name}
+
- <>
- setShowApiKey(DEFAULT_KEY)}>
- hide
-
- {anonApiKey && (
-
- setShowApiKey({
- key: anonApiKey.api_key ?? '-',
- name: 'anon (public)',
- })
- }
- >
- anon (public)
-
+
+ setSelectedApiKey(DEFAULT_KEY)}
+ >
+ Hide keys
+
+
+ {publishableKeys.length > 0 && (
+ <>
+
+ Publishable keys
+ {publishableKeys.map((key) => {
+ const value = key.api_key
+ return (
+
+ setSelectedApiKey({
+ name: `Publishable key: ${key.name}`,
+ key: value,
+ })
+ }
+ >
+ {key.name}
+
+ )
+ })}
+ >
)}
- {canReadServiceKey && (
-
- setShowApiKey({
- key: serviceApiKey?.api_key ?? '-',
- name: 'service_role (secret)',
- })
- }
- >
- service_role (secret)
-
+
+ {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/EdgeFunctionTesterSheet.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx
index 11cd1dd9e04c1..7c65503db22ec 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx
@@ -15,7 +15,10 @@ import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
import { prettifyJSON } from 'lib/helpers'
import { getRoleImpersonationJWT } from 'lib/role-impersonation'
-import { useGetImpersonatedRoleState } from 'state/role-impersonation-state'
+import {
+ RoleImpersonationStateContextProvider,
+ useGetImpersonatedRoleState,
+} from 'state/role-impersonation-state'
import {
Badge,
Button,
@@ -416,7 +419,12 @@ export const EdgeFunctionTesterSheet = ({ visible, onClose }: EdgeFunctionTester
-
+ {/* [Alaister]: We're using a fresh context here as edge functions don't allow impersonating users. */}
+
+
+
{
>
)}
- {isBillingCreditsEnabledOnProfileLevel && isNotOrgWithPartnerBilling && (
+ {isBillingCreditsEnabledOnProfileLevel && (
<>
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx
index 1fa2c2d6af279..5dafba5472326 100644
--- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx
+++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx
@@ -554,7 +554,7 @@ export const SubscriptionPlanUpdateDialog = ({
{}}
+ onSelectPaymentMethod={(pm) => setSelectedPaymentMethod(pm)}
createPaymentMethodInline={
subscriptionPreview.pending_subscription_flow === true
}
diff --git a/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover.tsx b/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover.tsx
index 318f03e3546d8..adbe74201f6a7 100644
--- a/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover.tsx
+++ b/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover.tsx
@@ -12,6 +12,7 @@ export interface RoleImpersonationPopoverProps {
serviceRoleLabel?: string
variant?: 'regular' | 'connected-on-right' | 'connected-on-left' | 'connected-on-both'
align?: 'center' | 'start' | 'end'
+ disallowAuthenticatedOption?: boolean
}
const RoleImpersonationPopover = ({
@@ -19,6 +20,7 @@ const RoleImpersonationPopover = ({
serviceRoleLabel,
variant = 'regular',
align = 'end',
+ disallowAuthenticatedOption = false,
}: RoleImpersonationPopoverProps) => {
const state = useRoleImpersonationStateSnapshot()
@@ -64,7 +66,10 @@ const RoleImpersonationPopover = ({
side="bottom"
align={align}
>
-
+
)
diff --git a/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationSelector.tsx b/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationSelector.tsx
index 17b121074ca96..fa591e1e8a029 100644
--- a/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationSelector.tsx
+++ b/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationSelector.tsx
@@ -10,11 +10,13 @@ import UserImpersonationSelector from './UserImpersonationSelector'
export interface RoleImpersonationSelectorProps {
serviceRoleLabel?: string
padded?: boolean
+ disallowAuthenticatedOption?: boolean
}
const RoleImpersonationSelector = ({
serviceRoleLabel,
padded = true,
+ disallowAuthenticatedOption = false,
}: RoleImpersonationSelectorProps) => {
const state = useRoleImpersonationStateSnapshot()
@@ -81,21 +83,25 @@ const RoleImpersonationSelector = ({
icon={ }
/>
- }
- />
+ {!disallowAuthenticatedOption && (
+ }
+ />
+ )}
{selectedOption === 'service_role' && (
- The default Postgres/superuser role. This has admin privileges.
+ The default Postgres/superuser role.
+ {disallowAuthenticatedOption ? : ' '}
+ This has admin privileges.
It will bypass Row Level Security (RLS) policies.
@@ -103,16 +109,21 @@ const RoleImpersonationSelector = ({
{selectedOption === 'anon' && (
- For "anonymous access". This is the role which the API (PostgREST) will use when a user
+ For "anonymous access".
+ {disallowAuthenticatedOption ? : ' '}
+ This is the role which the API (PostgREST)
- is not logged in. It will respect Row Level Security (RLS) policies.
+ will use when a user is not logged in.
+ {disallowAuthenticatedOption ? : ' '}
+ It will respect Row Level Security (RLS) policies.
)}
{selectedOption === 'authenticated' && (
- For "authenticated access". This is the role which the API (PostgREST) will use when
- a user is logged in. It will respect Row Level Security (RLS) policies.
+ For "authenticated access". This is the role which the API (PostgREST)
+
+ will use when a user is logged in. It will respect Row Level Security (RLS) policies.
)}
diff --git a/apps/studio/data/config/project-settings-v2-query.ts b/apps/studio/data/config/project-settings-v2-query.ts
index b76d50f85f945..9b8d65e071d41 100644
--- a/apps/studio/data/config/project-settings-v2-query.ts
+++ b/apps/studio/data/config/project-settings-v2-query.ts
@@ -63,6 +63,9 @@ export const useProjectSettingsV2Query = (
)
}
+/**
+ * @deprecated Use api-keys-query instead!
+ */
export const getAPIKeys = (settings?: ProjectSettings) => {
const anonKey = (settings?.service_api_keys ?? []).find((x) => x.tags === 'anon')
const serviceKey = (settings?.service_api_keys ?? []).find((x) => x.tags === 'service_role')
diff --git a/apps/studio/pages/project/[ref]/api/index.tsx b/apps/studio/pages/project/[ref]/api/index.tsx
index 9e34e08a2b7d0..b324749bdb1bd 100644
--- a/apps/studio/pages/project/[ref]/api/index.tsx
+++ b/apps/studio/pages/project/[ref]/api/index.tsx
@@ -3,13 +3,13 @@ import { useState } from 'react'
import { GeneralContent, ResourceContent, RpcContent } from 'components/interfaces/Docs'
import LangSelector from 'components/interfaces/Docs/LangSelector'
+import DefaultLayout from 'components/layouts/DefaultLayout'
import DocsLayout from 'components/layouts/DocsLayout/DocsLayout'
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query'
import { snakeToCamel } from 'lib/helpers'
import type { NextPageWithLayout } from 'types'
-import DefaultLayout from 'components/layouts/DefaultLayout'
const PageConfig: NextPageWithLayout = () => {
return
@@ -29,7 +29,7 @@ const DocView = () => {
const { ref: projectRef, page, resource, rpc } = useParams()
const [selectedLang, setSelectedLang] = useState('js')
- const [showApiKey, setShowApiKey] = useState(DEFAULT_KEY)
+ const [selectedApikey, setSelectedApiKey] = useState(DEFAULT_KEY)
const { data: settings, error: settingsError } = useProjectSettingsV2Query({ projectRef })
const {
@@ -112,9 +112,9 @@ const DocView = () => {
@@ -124,7 +124,7 @@ const DocView = () => {
selectedLang={selectedLang}
resourceId={resource}
resources={resources}
- showApiKey={showApiKey.key}
+ showApiKey={selectedApikey.key}
refreshDocs={refreshDocs}
/>
) : rpc ? (
@@ -133,11 +133,15 @@ const DocView = () => {
rpcId={rpc}
paths={paths}
rpcs={rpcs}
- showApiKey={showApiKey.key}
+ showApiKey={selectedApikey.key}
refreshDocs={refreshDocs}
/>
) : (
-
+
)}
diff --git a/apps/studio/public/img/libraries/nextjs-dark-icon.svg b/apps/studio/public/img/libraries/nextjs-dark-icon.svg
index 99afa3413d30f..60ef95ea6b075 100644
--- a/apps/studio/public/img/libraries/nextjs-dark-icon.svg
+++ b/apps/studio/public/img/libraries/nextjs-dark-icon.svg
@@ -1,3 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/apps/studio/public/img/libraries/nextjs-icon.svg b/apps/studio/public/img/libraries/nextjs-icon.svg
index c45d6e63fb907..60ef95ea6b075 100644
--- a/apps/studio/public/img/libraries/nextjs-icon.svg
+++ b/apps/studio/public/img/libraries/nextjs-icon.svg
@@ -1,3 +1 @@
-
-
-
+
\ No newline at end of file