diff --git a/src/features/webhook/assembly/api/webhook.controller.ts b/src/features/webhook/assembly/api/webhook.controller.ts index 09c0c19..7c07a16 100644 --- a/src/features/webhook/assembly/api/webhook.controller.ts +++ b/src/features/webhook/assembly/api/webhook.controller.ts @@ -11,9 +11,13 @@ import logger from '@/lib/logger' import { sleep } from '@/utils/sleep' export const handleWebhookEvent = async (req: NextRequest) => { - await sleep(800) // prevent ping-pong case of webhooks + // Read the body before any awaits so the upstream connection isn't torn down + // (sleep + auth + DB lookup) while we still have an unread request stream. + const rawBody = await req.json() const token = req.nextUrl.searchParams.get('token') + await sleep(800) // prevent ping-pong case of webhooks + const user = await User.authenticate(token) const dropboxConnectionService = new DropboxConnectionsService(user) @@ -32,7 +36,7 @@ export const handleWebhookEvent = async (req: NextRequest) => { accountId: connection.accountId, rootNamespaceId: connection.rootNamespaceId, }) - const webhookEvent = await assemblyWebhookService.parseWebhook(req) + const webhookEvent = assemblyWebhookService.parseWebhook(rawBody) logger.info(`Event triggered. ${JSON.stringify(webhookEvent)}`) const eventType = assemblyWebhookService.validateHandleableEvent(webhookEvent) diff --git a/src/features/webhook/assembly/lib/webhook.service.ts b/src/features/webhook/assembly/lib/webhook.service.ts index 0eb4fbc..50008d0 100644 --- a/src/features/webhook/assembly/lib/webhook.service.ts +++ b/src/features/webhook/assembly/lib/webhook.service.ts @@ -1,5 +1,4 @@ import { and, eq } from 'drizzle-orm' -import type { NextRequest } from 'next/server' import z from 'zod' import { ObjectType, type ObjectTypeValue } from '@/db/constants' import { channelSync } from '@/db/schema/channelSync.schema' @@ -23,6 +22,8 @@ import { updateAssemblyFileInDropbox, } from '@/trigger/processFileSync' +const SYNCABLE_OBJECT_TYPES: readonly string[] = [ObjectType.FILE, ObjectType.FOLDER] + export class AssemblyWebhookService extends AuthenticatedDropboxService { readonly mapFilesService: MapFilesService constructor(user: User, connectionToken: DropboxConnectionTokens) { @@ -30,8 +31,8 @@ export class AssemblyWebhookService extends AuthenticatedDropboxService { this.mapFilesService = new MapFilesService(user, connectionToken) } - async parseWebhook(req: NextRequest): Promise { - const webhookEvent = AssemblyWebhookSchema.safeParse(await req.json()) + parseWebhook(body: unknown): AssemblyWebhookEvent { + const webhookEvent = AssemblyWebhookSchema.safeParse(body) logger.info('AssemblyWebhookService#parseWebhook :: Parsed webhook event', webhookEvent) if (!webhookEvent.success) { throw new APIError('Failed to parse webhook event') @@ -63,8 +64,7 @@ export class AssemblyWebhookService extends AuthenticatedDropboxService { DISPATCHABLE_HANDLEABLE_EVENT.FolderCreated, DISPATCHABLE_HANDLEABLE_EVENT.FolderDeleted, DISPATCHABLE_HANDLEABLE_EVENT.FolderUpdated, - ].includes(eventType) || - !(webhookEvent.object !== ObjectType.FILE && webhookEvent.object !== ObjectType.FOLDER) // avoid file with object 'link' + ].includes(eventType) && SYNCABLE_OBJECT_TYPES.includes(webhookEvent.data.object) // avoid non-syncable object types like 'link' // if (isValidWebhook) { // const record = await this.checkNonDuplicateWebhookRecord(webhookEvent) diff --git a/src/features/webhook/assembly/utils/types.ts b/src/features/webhook/assembly/utils/types.ts index c0b858c..3b384dd 100644 --- a/src/features/webhook/assembly/utils/types.ts +++ b/src/features/webhook/assembly/utils/types.ts @@ -13,7 +13,6 @@ export enum DISPATCHABLE_HANDLEABLE_EVENT { export const AssemblyWebhookSchema = z.object({ eventType: z.string(), created: z.string().optional(), - object: z.string().optional(), data: CopilotFileRetrieveSchema, }) diff --git a/src/features/webhook/dropbox/api/webhook.controller.ts b/src/features/webhook/dropbox/api/webhook.controller.ts index ec8cbec..320905d 100644 --- a/src/features/webhook/dropbox/api/webhook.controller.ts +++ b/src/features/webhook/dropbox/api/webhook.controller.ts @@ -25,14 +25,14 @@ export const handleWebhookUrlVerification = (req: NextRequest) => { } export const handleWebhookEvents = async (req: NextRequest) => { - await sleep(800) // prevent ping-pong case of webhooks - const signature = req.headers.get('X-Dropbox-Signature') if (!signature) return NextResponse.json({ error: 'Missing signature' }, { status: status.BAD_REQUEST }) const body = await req.text() + await sleep(800) // prevent ping-pong case of webhooks + const computedSignature = crypto .createHmac('sha256', env.DROPBOX_APP_SECRET) .update(body)