Skip to content

Commit 88dca02

Browse files
authored
Webhooks automatically append authorization header if edge function with verify JWT enabled is selected (supabase#36759)
* Webhooks automatically append authorization header if edge function with verify JWT enabled is selected * Fix when selecting http request to remove auth header as well * fixy fix
1 parent 9f42dd7 commit 88dca02

File tree

5 files changed

+296
-248
lines changed

5 files changed

+296
-248
lines changed

apps/studio/components/interfaces/Database/Hooks/EditHookPanel.tsx

Lines changed: 20 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
1-
import type { PostgresTable, PostgresTrigger } from '@supabase/postgres-meta'
2-
import Image from 'next/legacy/image'
3-
import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'
1+
import { PGTriggerCreate } from '@supabase/pg-meta/src/pg-meta-triggers'
2+
import type { PostgresTrigger } from '@supabase/postgres-meta'
3+
import { useEffect, useMemo, useRef, useState } from 'react'
44
import { toast } from 'sonner'
55

66
import { useParams } from 'common'
77
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
8-
import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection'
98
import { useDatabaseTriggerCreateMutation } from 'data/database-triggers/database-trigger-create-mutation'
109
import { useDatabaseTriggerUpdateMutation } from 'data/database-triggers/database-trigger-update-transaction-mutation'
11-
import {
12-
EdgeFunctionsResponse,
13-
useEdgeFunctionsQuery,
14-
} from 'data/edge-functions/edge-functions-query'
1510
import { getTableEditor } from 'data/table-editor/table-editor-query'
1611
import { useTablesQuery } from 'data/tables/tables-query'
1712
import { isValidHttpUrl, uuidv4 } from 'lib/helpers'
18-
import { Button, Checkbox, Form, Input, Listbox, Radio, SidePanel } from 'ui'
13+
import { Button, Form, SidePanel } from 'ui'
1914
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
20-
import HTTPRequestFields from './HTTPRequestFields'
21-
import { AVAILABLE_WEBHOOK_TYPES, HOOK_EVENTS } from './Hooks.constants'
22-
import { PGTriggerCreate } from '@supabase/pg-meta/src/pg-meta-triggers'
15+
import { FormContents } from './FormContents'
2316

2417
export interface EditHookPanelProps {
2518
visible: boolean
@@ -29,7 +22,19 @@ export interface EditHookPanelProps {
2922

3023
export type HTTPArgument = { id: string; name: string; value: string }
3124

32-
const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) => {
25+
export const isEdgeFunction = ({
26+
ref,
27+
restUrlTld,
28+
url,
29+
}: {
30+
ref?: string
31+
restUrlTld?: string
32+
url: string
33+
}) =>
34+
url.includes(`https://${ref}.functions.supabase.${restUrlTld}/`) ||
35+
url.includes(`https://${ref}.supabase.${restUrlTld}/functions/`)
36+
37+
export const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) => {
3338
const { ref } = useParams()
3439
const submitRef = useRef<any>(null)
3540
const [isEdited, setIsEdited] = useState(false)
@@ -49,7 +54,6 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) =
4954
projectRef: project?.ref,
5055
connectionString: project?.connectionString,
5156
})
52-
const { data: functions } = useEdgeFunctionsQuery({ projectRef: ref })
5357
const [isSubmitting, setIsSubmitting] = useState(false)
5458
const { mutate: createDatabaseTrigger } = useDatabaseTriggerCreateMutation({
5559
onSuccess: (res) => {
@@ -81,16 +85,12 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) =
8185
const restUrl = project?.restUrl
8286
const restUrlTld = restUrl ? new URL(restUrl).hostname.split('.').pop() : 'co'
8387

84-
const isEdgeFunction = (url: string) =>
85-
url.includes(`https://${ref}.functions.supabase.${restUrlTld}/`) ||
86-
url.includes(`https://${ref}.supabase.${restUrlTld}/functions/`)
87-
8888
const initialValues = {
8989
name: selectedHook?.name ?? '',
9090
table_id: selectedHook?.table_id ?? '',
9191
http_url: selectedHook?.function_args?.[0] ?? '',
9292
http_method: selectedHook?.function_args?.[1] ?? 'POST',
93-
function_type: isEdgeFunction(selectedHook?.function_args?.[0] ?? '')
93+
function_type: isEdgeFunction({ ref, restUrlTld, url: selectedHook?.function_args?.[0] ?? '' })
9494
? 'supabase_function'
9595
: 'http_request',
9696
timeout_ms: Number(selectedHook?.function_args?.[4] ?? 5000),
@@ -104,7 +104,7 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) =
104104
if (selectedHook !== undefined) {
105105
setEvents(selectedHook.events)
106106

107-
const [url, method, headers, parameters] = selectedHook.function_args
107+
const [_, __, headers, parameters] = selectedHook.function_args
108108

109109
let parsedParameters: Record<string, string> = {}
110110

@@ -314,10 +314,6 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) =
314314
values={values}
315315
resetForm={resetForm}
316316
errors={errors}
317-
projectRef={ref}
318-
restUrlTld={restUrlTld}
319-
functions={functions}
320-
isEdgeFunction={isEdgeFunction}
321317
tables={tables}
322318
events={events}
323319
eventsError={eventsError}
@@ -352,211 +348,3 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) =
352348
</>
353349
)
354350
}
355-
356-
export default EditHookPanel
357-
358-
interface FormContentsProps {
359-
values: any
360-
resetForm: any
361-
errors: any
362-
projectRef?: string
363-
restUrlTld?: string
364-
selectedHook?: PostgresTrigger
365-
functions: EdgeFunctionsResponse[] | undefined
366-
isEdgeFunction: (url: string) => boolean
367-
tables: PostgresTable[]
368-
events: string[]
369-
eventsError?: string
370-
onUpdateSelectedEvents: (event: string) => void
371-
httpHeaders: HTTPArgument[]
372-
httpParameters: HTTPArgument[]
373-
setHttpHeaders: (arr: HTTPArgument[]) => void
374-
setHttpParameters: (arr: HTTPArgument[]) => void
375-
submitRef: MutableRefObject<any>
376-
}
377-
378-
const FormContents = ({
379-
values,
380-
resetForm,
381-
errors,
382-
projectRef,
383-
restUrlTld,
384-
selectedHook,
385-
functions,
386-
isEdgeFunction,
387-
tables,
388-
events,
389-
eventsError,
390-
onUpdateSelectedEvents,
391-
httpHeaders,
392-
httpParameters,
393-
setHttpHeaders,
394-
setHttpParameters,
395-
submitRef,
396-
}: FormContentsProps) => {
397-
useEffect(() => {
398-
if (values.function_type === 'http_request') {
399-
if (selectedHook !== undefined) {
400-
const [url, method] = selectedHook.function_args
401-
const updatedValues = { ...values, http_url: url, http_method: method }
402-
resetForm({ values: updatedValues, initialValues: updatedValues })
403-
} else {
404-
const updatedValues = { ...values, http_url: '' }
405-
resetForm({ values: updatedValues, initialValues: updatedValues })
406-
}
407-
} else if (values.function_type === 'supabase_function') {
408-
const fnSlug = (functions ?? [])[0]?.slug
409-
const defaultFunctionUrl = `https://${projectRef}.supabase.${restUrlTld}/functions/v1/${fnSlug}`
410-
const updatedValues = {
411-
...values,
412-
http_url: isEdgeFunction(values.http_url) ? values.http_url : defaultFunctionUrl,
413-
}
414-
resetForm({ values: updatedValues, initialValues: updatedValues })
415-
}
416-
}, [values.function_type])
417-
418-
return (
419-
<div>
420-
<FormSection header={<FormSectionLabel className="lg:!col-span-4">General</FormSectionLabel>}>
421-
<FormSectionContent loading={false} className="lg:!col-span-8">
422-
<Input
423-
id="name"
424-
name="name"
425-
label="Name"
426-
descriptionText="Do not use spaces/whitespaces"
427-
/>
428-
</FormSectionContent>
429-
</FormSection>
430-
<SidePanel.Separator />
431-
<FormSection
432-
header={
433-
<FormSectionLabel
434-
className="lg:!col-span-4"
435-
description={
436-
<p className="text-sm text-foreground-light">
437-
Select which table and events will trigger your webhook
438-
</p>
439-
}
440-
>
441-
Conditions to fire webhook
442-
</FormSectionLabel>
443-
}
444-
>
445-
<FormSectionContent loading={false} className="lg:!col-span-8">
446-
<Listbox
447-
size="medium"
448-
id="table_id"
449-
name="table_id"
450-
label="Table"
451-
descriptionText="This is the table the trigger will watch for changes. You can only select 1 table for a trigger."
452-
>
453-
<Listbox.Option
454-
key={'table-no-selection'}
455-
id={'table-no-selection'}
456-
label={'---'}
457-
value={'no-selection'}
458-
>
459-
---
460-
</Listbox.Option>
461-
{tables.map((table) => (
462-
<Listbox.Option
463-
key={table.id}
464-
id={table.id.toString()}
465-
value={table.id}
466-
label={table.name}
467-
>
468-
<div className="flex items-center space-x-2">
469-
<p className="text-foreground-light">{table.schema}</p>
470-
<p className="text-foreground">{table.name}</p>
471-
</div>
472-
</Listbox.Option>
473-
))}
474-
</Listbox>
475-
<Checkbox.Group
476-
id="events"
477-
name="events"
478-
label="Events"
479-
error={eventsError}
480-
descriptionText="These are the events that are watched by the webhook, only the events selected above will fire the webhook on the table you've selected."
481-
>
482-
{HOOK_EVENTS.map((event) => (
483-
<Checkbox
484-
key={event.value}
485-
value={event.value}
486-
label={event.label}
487-
description={event.description}
488-
checked={events.includes(event.value)}
489-
onChange={() => onUpdateSelectedEvents(event.value)}
490-
/>
491-
))}
492-
</Checkbox.Group>
493-
</FormSectionContent>
494-
</FormSection>
495-
<SidePanel.Separator />
496-
<FormSection
497-
header={
498-
<FormSectionLabel className="lg:!col-span-4">Webhook configuration</FormSectionLabel>
499-
}
500-
>
501-
<FormSectionContent loading={false} className="lg:!col-span-8">
502-
<Radio.Group id="function_type" name="function_type" label="Type of webhook" type="cards">
503-
{AVAILABLE_WEBHOOK_TYPES.map((webhook) => (
504-
<Radio
505-
key={webhook.value}
506-
id={webhook.value}
507-
value={webhook.value}
508-
label=""
509-
beforeLabel={
510-
<div className="flex items-center space-x-5">
511-
<Image src={webhook.icon} layout="fixed" width="32" height="32" />
512-
<div className="flex-col space-y-0">
513-
<div className="flex space-x-2">
514-
<p className="text-foreground">{webhook.label}</p>
515-
</div>
516-
<p className="text-foreground-light">{webhook.description}</p>
517-
</div>
518-
</div>
519-
}
520-
/>
521-
))}
522-
</Radio.Group>
523-
</FormSectionContent>
524-
</FormSection>
525-
<SidePanel.Separator />
526-
527-
<HTTPRequestFields
528-
type={values.function_type}
529-
errors={errors}
530-
httpHeaders={httpHeaders}
531-
httpParameters={httpParameters}
532-
onAddHeader={(header?: any) => {
533-
if (header) setHttpHeaders(httpHeaders.concat(header))
534-
else setHttpHeaders(httpHeaders.concat({ id: uuidv4(), name: '', value: '' }))
535-
}}
536-
onUpdateHeader={(idx, property, value) =>
537-
setHttpHeaders(
538-
httpHeaders.map((header, i) => {
539-
if (idx === i) return { ...header, [property]: value }
540-
else return header
541-
})
542-
)
543-
}
544-
onRemoveHeader={(idx) => setHttpHeaders(httpHeaders.filter((_, i) => idx !== i))}
545-
onAddParameter={() =>
546-
setHttpParameters(httpParameters.concat({ id: uuidv4(), name: '', value: '' }))
547-
}
548-
onUpdateParameter={(idx, property, value) =>
549-
setHttpParameters(
550-
httpParameters.map((param, i) => {
551-
if (idx === i) return { ...param, [property]: value }
552-
else return param
553-
})
554-
)
555-
}
556-
onRemoveParameter={(idx) => setHttpParameters(httpParameters.filter((_, i) => idx !== i))}
557-
/>
558-
559-
<button ref={submitRef} type="submit" className="hidden" />
560-
</div>
561-
)
562-
}

0 commit comments

Comments
 (0)