diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.test.ts b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.test.ts new file mode 100644 index 00000000000..862069939ae --- /dev/null +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest' + +import { INVOCATION_TABS } from './EdgeFunctionDetails.constants' + +const curlTab = INVOCATION_TABS.find((tab) => tab.id === 'curl')! + +const baseProps = { + showKey: true, + functionUrl: 'https://project-ref.supabase.co/functions/v1/health', + functionName: 'health', +} + +describe('cURL invocation snippet', () => { + it('sends a publishable key on the apikey header, not Authorization', () => { + const code = curlTab.code({ ...baseProps, apiKey: 'sb_publishable_abc123' }) + + expect(code).toContain("-H 'apikey: sb_publishable_abc123'") + expect(code).not.toContain('Authorization') + }) + + it('sends a legacy anon key on the Authorization header, not apikey', () => { + const anonJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.anon' + const code = curlTab.code({ ...baseProps, apiKey: anonJwt }) + + expect(code).toContain(`-H 'Authorization: Bearer ${anonJwt}'`) + expect(code).not.toContain('apikey:') + }) + + it('obfuscates the key name when showKey is false', () => { + const publishable = curlTab.code({ + ...baseProps, + showKey: false, + apiKey: 'sb_publishable_abc123', + }) + expect(publishable).toContain("-H 'apikey: SUPABASE_PUBLISHABLE_KEY'") + + const anon = curlTab.code({ ...baseProps, showKey: false, apiKey: 'eyJ.anon' }) + expect(anon).toContain("-H 'Authorization: Bearer SUPABASE_ANON_KEY'") + }) +}) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts index 0af0084c088..7822861a76f 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts @@ -17,13 +17,19 @@ export const INVOCATION_TABS: InvocationTab[] = [ label: 'cURL', language: 'bash', code: ({ showKey, functionUrl, apiKey }) => { - const obfuscatedName = apiKey.includes('publishable') - ? 'SUPABASE_PUBLISHABLE_KEY' - : 'SUPABASE_ANON_KEY' + const isPublishableKey = apiKey.includes('publishable') + const obfuscatedName = isPublishableKey ? 'SUPABASE_PUBLISHABLE_KEY' : 'SUPABASE_ANON_KEY' const keyValue = showKey ? apiKey : obfuscatedName + // Publishable and secret keys aren't JWTs, so they must be sent on the `apikey` + // header. Passing them on `Authorization: Bearer` makes the platform reject the + // request with `Invalid JWT`. Legacy `anon` keys are JWTs and use `Authorization`. + const authHeader = isPublishableKey + ? `-H 'apikey: ${keyValue}'` + : `-H 'Authorization: Bearer ${keyValue}'` + return `curl -L -X POST '${functionUrl}' \\ - -H 'Authorization: Bearer ${keyValue}' \\${apiKey.includes('publishable') ? `\n -H 'apikey: ${keyValue}' \\` : ''} + ${authHeader} \\ -H 'Content-Type: application/json' \\ --data '{"name":"Functions"}'` }, diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx index a0feafae502..f56aaa437f5 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -101,6 +101,7 @@ export const EdgeFunctionDetails = () => { const { anonKey, publishableKey } = getKeys(apiKeys) const apiKey = publishableKey?.api_key ?? anonKey?.api_key ?? '[YOUR ANON KEY]' + const apiKeyLabel = publishableKey?.api_key ? 'publishable key' : 'anon key' const { managementCommands } = generateCLICommands({ selectedFunction, @@ -263,7 +264,7 @@ export const EdgeFunctionDetails = () => { className="ml-auto -translate-y-2 translate-x-3" onClick={() => setShowKey(!showKey)} > - {showKey ? 'Hide' : 'Show'} anon key + {showKey ? 'Hide' : 'Show'} {apiKeyLabel} )}