mirror of
https://github.com/supabase/supabase.git
synced 2026-05-08 01:40:13 -04:00
fd17b246e1
## What kind of change does this PR introduce? Bug fix. ## What is the current behavior? Webhook endpoint validation is inconsistent across Studio forms. The webhook sheet accepts incomplete hostnames like `https://webhook`, event type validation is not surfaced clearly, and HTTP endpoint validation differs between webhooks, log drains, cron jobs, and database hooks. ## What is the new behavior? - Tightens webhook endpoint URL validation and rejects incomplete hostnames while still allowing localhost and IP-based endpoints. - Surfaces the Event types validation through the standard form error styling and highlights the existing accordion item border when invalid. - **Extracts a shared HTTP endpoint URL validator and reuses it in webhooks, log drains, cron jobs, and database hooks.** - Adds focused regression tests for the webhook sheet and the shared/consumer validation paths. | After | | --- | | <img width="1728" height="997" alt="Webhooks Settings AWS Healthy Toolshed Supabase-CB0D999C-D0BF-47AA-A10F-342A2E328DF9" src="https://github.com/user-attachments/assets/bcbe4876-f9a7-497a-b288-460087a65546" /> | ## To test Form behaviour (in particular URL validation) on: - Webhook endpoint - Log drains - Cron jobs - Database hooks
60 lines
1.4 KiB
TypeScript
60 lines
1.4 KiB
TypeScript
import { z } from 'zod'
|
|
|
|
const HTTP_URL_PROTOCOL_REGEX = /^https?:\/\//
|
|
const IPV4_SEGMENT = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)'
|
|
const IPV4_REGEX = new RegExp(`^(?:${IPV4_SEGMENT}\\.){3}${IPV4_SEGMENT}$`)
|
|
const BRACKETED_IPV6_REGEX = /^\[[0-9a-f:.]+\]$/i
|
|
|
|
export const hasHttpUrlProtocol = (value: string) => HTTP_URL_PROTOCOL_REGEX.test(value)
|
|
|
|
export const isValidHttpEndpointUrl = (value: string) => {
|
|
try {
|
|
const url = new URL(value)
|
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false
|
|
|
|
const { hostname } = url
|
|
return (
|
|
hostname === 'localhost' ||
|
|
hostname.includes('.') ||
|
|
IPV4_REGEX.test(hostname) ||
|
|
BRACKETED_IPV6_REGEX.test(hostname)
|
|
)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
type HttpEndpointUrlSchemaOptions = {
|
|
requiredMessage: string
|
|
invalidMessage: string
|
|
prefixMessage: string
|
|
}
|
|
|
|
export const httpEndpointUrlSchema = ({
|
|
requiredMessage,
|
|
invalidMessage,
|
|
prefixMessage,
|
|
}: HttpEndpointUrlSchemaOptions) =>
|
|
z
|
|
.string()
|
|
.trim()
|
|
.min(1, requiredMessage)
|
|
.superRefine((value, ctx) => {
|
|
if (!value) return
|
|
|
|
if (!hasHttpUrlProtocol(value)) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: prefixMessage,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (!isValidHttpEndpointUrl(value)) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: invalidMessage,
|
|
})
|
|
}
|
|
})
|