Files
supabase/apps/studio/components/interfaces/Functions/httpHeaderAddActions.ts
Vaibhav 1cffe632e3 fix: webhook apikey (#47317)
## TL;DR
Database webhooks/Cron jobs now add `apikey: <secret-key>` for edge
function auth..

## ref:
- related to: https://github.com/supabase/supabase/pull/46890
- towards COM-269

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## New Features
* Improved edge function webhook authentication by automatically
selecting the appropriate API key or authorization header format.
* Authorization headers are now added or normalized when required, while
preserving existing custom headers and supported credentials.

## Improvements
* Simplified “Add header” and “Add parameter” controls with clearer
labels.
* Updated authentication actions to clearly describe the selected header
type.

## Tests
* Expanded coverage for key formats, authorization behavior, header
preservation, and revised control labels.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Tomás Pozo <tomaspozo@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-08-24 10:06:36 -06:00

85 lines
2.7 KiB
TypeScript

import type { KeyValueFieldArrayAction } from 'ui-patterns/form/KeyValueFieldArray/KeyValueFieldArray'
interface BuildEdgeFunctionHeaderAddActionsParams<TRow> {
apiKey: string
createRow: (name: string, value: string) => TRow
}
interface HTTPHeader {
name: string
value: string
}
interface EnsureEdgeFunctionAuthorizationHeaderParams<TRow extends HTTPHeader> {
headers: TRow[]
serviceRoleKey?: string
verifyJwt?: boolean
createRow: (name: string, value: string) => TRow
}
const isNewApiKey = (apiKey: string) =>
apiKey.startsWith('sb_secret_') || apiKey.startsWith('sb_publishable_')
export const getEdgeFunctionAuthHeader = (apiKey: string) =>
isNewApiKey(apiKey)
? { name: 'apikey', value: apiKey }
: { name: 'Authorization', value: `Bearer ${apiKey}` }
export const ensureEdgeFunctionAuthorizationHeader = <TRow extends HTTPHeader>({
headers,
serviceRoleKey,
verifyJwt,
createRow,
}: EnsureEdgeFunctionAuthorizationHeaderParams<TRow>): TRow[] => {
if (!verifyJwt || !serviceRoleKey || isNewApiKey(serviceRoleKey)) return headers
const isAuthorization = (header: HTTPHeader) =>
header.name.trim().toLowerCase() === 'authorization'
const authorizationIndex = headers.findIndex(isAuthorization)
if (authorizationIndex === -1) {
return [...headers, createRow('Authorization', `Bearer ${serviceRoleKey}`)]
}
const authorizationHeader = headers[authorizationIndex]
const normalizedHeaders = headers.filter(
(header, index) => index === authorizationIndex || !isAuthorization(header)
)
if (normalizedHeaders.length === headers.length && authorizationHeader.name === 'Authorization') {
return headers
}
normalizedHeaders[authorizationIndex] = {
...authorizationHeader,
name: 'Authorization',
}
return normalizedHeaders
}
export const buildEdgeFunctionHeaderAddActions = <TRow>({
apiKey,
createRow,
}: BuildEdgeFunctionHeaderAddActionsParams<TRow>): KeyValueFieldArrayAction<TRow>[] => {
const authHeader = getEdgeFunctionAuthHeader(apiKey)
return [
{
key: 'add-auth-header',
label: 'Add secret key',
description:
authHeader.name === 'apikey'
? 'Requires JWT verification to be disabled and authorization handled by the function'
: 'Required for edge functions that enforce JWT verification',
createRows: () => [createRow(authHeader.name, authHeader.value)],
},
{
key: 'add-source-header',
label: 'Add custom source',
description: 'Useful to verify that the edge function was triggered from this webhook',
createRows: () => createRow('x-supabase-webhook-source', '[Use a secret value]'),
separatorAbove: true,
},
]
}