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:
- + - <> - 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. */} + + +
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