Skip to content
File

Blob: src/worker/api/public/webhooks/request.ts

typescript62 lines
1import { matchesWebhookRequestContentType, type WebhookProviderCatalogEntry } from "@/lib/webhooks";
2import { assertValidSlug } from "@/worker/validation";
3import { HttpError } from "@/worker/http";
4 
5export interface ParsedWebhookRequestHeaders {
6 deliveryId: string;
7 eventName: string;
8 verificationHeader: string;
9}
10 
11export const requirePublicSlug = (value: string | undefined, fieldName: string): string => {
12 if (!value) {
13 throw new HttpError(404, "webhook_not_found", "Webhook was not found.");
14 }
15 
16 assertValidSlug(value, fieldName);
17 return value;
18};
19 
20export const assertExpectedWebhookContentType = (catalog: WebhookProviderCatalogEntry, request: Request): void => {
21 if (!matchesWebhookRequestContentType(catalog, request.headers.get("content-type"))) {
22 throw new HttpError(415, "unsupported_media_type", `Webhook requests must use ${catalog.expectedContentType}.`);
23 }
24};
25 
26export const requireHeader = (request: Request, headerName: string): string => {
27 const value = request.headers.get(headerName)?.trim();
28 if (!value) {
29 throw new HttpError(401, "invalid_webhook_signature", `Missing required header ${headerName}.`);
30 }
31 
32 return value;
33};
34 
35export const requireAnyHeader = (request: Request, headerNames: readonly string[]): string => {
36 for (const headerName of headerNames) {
37 const value = request.headers.get(headerName)?.trim();
38 if (value) {
39 return value;
40 }
41 }
42 
43 throw new HttpError(401, "invalid_webhook_signature", `Missing required header ${headerNames.join(" or ")}.`);
44};
45 
46export const readWebhookRequestHeaders = (
47 request: Request,
48 catalog: WebhookProviderCatalogEntry,
49): ParsedWebhookRequestHeaders => {
50 const requiredHeaderValues = new Map<string, string>();
51 for (const headerName of catalog.requiredHeaders) {
52 requiredHeaderValues.set(headerName, requireHeader(request, headerName));
53 }
54 
55 return {
56 deliveryId: requireAnyHeader(request, catalog.deliveryIdHeaders),
57 eventName: requiredHeaderValues.get(catalog.eventHeader) ?? requireHeader(request, catalog.eventHeader),
58 verificationHeader:
59 requiredHeaderValues.get(catalog.verificationHeader) ?? requireHeader(request, catalog.verificationHeader),
60 };
61};