From 36a618610174fd168fb255d9c8ecc0d2cf61a192 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Thu, 21 May 2026 17:01:12 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20WhatsApp=20preview=20webho?= =?UTF-8?q?ok=20authorization=20(#2499)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Validate that WhatsApp preview webhook test sessions belong to the authorized typebot before resuming them. - Require the preview session to still be waiting on the requested webhook block. - Share WhatsApp preview phone normalization between preview creation and test webhook execution. --- .../src/api/handleExecuteTestWebhookWhatsApp.ts | 16 ++++++++++++---- .../src/api/handleStartWhatsAppPreview.ts | 11 ++++------- .../src/normalizeWhatsAppPreviewPhoneNumber.ts | 2 ++ 3 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 packages/whatsapp/src/normalizeWhatsAppPreviewPhoneNumber.ts diff --git a/packages/blocks/webhook/src/api/handleExecuteTestWebhookWhatsApp.ts b/packages/blocks/webhook/src/api/handleExecuteTestWebhookWhatsApp.ts index 3b64155c2..ca10d6412 100644 --- a/packages/blocks/webhook/src/api/handleExecuteTestWebhookWhatsApp.ts +++ b/packages/blocks/webhook/src/api/handleExecuteTestWebhookWhatsApp.ts @@ -8,6 +8,8 @@ import prisma from "@typebot.io/prisma"; import type { Prisma } from "@typebot.io/prisma/types"; import { isTypebotVersionAtLeastV6 } from "@typebot.io/schemas/helpers/isTypebotVersionAtLeastV6"; import { isReadTypebotForbidden } from "@typebot.io/typebot/helpers/isReadTypebotForbidden"; +import { WHATSAPP_PREVIEW_SESSION_ID_PREFIX } from "@typebot.io/whatsapp/constants"; +import { normalizeWhatsAppPreviewPhoneNumber } from "@typebot.io/whatsapp/normalizeWhatsAppPreviewPhoneNumber"; import { resumeWhatsAppFlow } from "@typebot.io/whatsapp/resumeWhatsAppFlow"; import { z } from "zod"; @@ -15,7 +17,7 @@ export const executeTestWebhookWhatsAppInputSchema = z.object({ params: z.object({ typebotId: z.string(), blockId: z.string(), - phone: z.string(), + phone: z.string().transform(normalizeWhatsAppPreviewPhoneNumber), }), body: z.unknown(), }); @@ -84,9 +86,15 @@ export const handleExecuteTestWebhookWhatsApp = async ({ message: "Webhook block not found", }); - const chatSession = await getSession(`wa-preview-${phone}`); + const chatSession = await getSession( + `${WHATSAPP_PREVIEW_SESSION_ID_PREFIX}${phone}`, + ); - if (!chatSession?.state?.whatsApp) + if ( + !chatSession?.state?.whatsApp || + chatSession.state.typebotsQueue[0]?.typebot.id !== typebotId || + chatSession.state.currentBlockId !== blockId + ) throw new ORPCError("BAD_REQUEST", { message: "Expected whatsapp chat session", }); @@ -94,7 +102,7 @@ export const handleExecuteTestWebhookWhatsApp = async ({ await resumeWhatsAppFlow({ receivedMessages: [ { - from: chatSession.id.split("-").at(-1)!, + from: phone, timestamp: new Date().toISOString(), type: "webhook", webhook: { diff --git a/packages/whatsapp/src/api/handleStartWhatsAppPreview.ts b/packages/whatsapp/src/api/handleStartWhatsAppPreview.ts index b4b0e552c..b36e57270 100644 --- a/packages/whatsapp/src/api/handleStartWhatsAppPreview.ts +++ b/packages/whatsapp/src/api/handleStartWhatsAppPreview.ts @@ -13,14 +13,11 @@ import type { User } from "@typebot.io/user/schemas"; import { sendChatReplyToWhatsApp } from "@typebot.io/whatsapp/sendChatReplyToWhatsApp"; import { sendWhatsAppMessage } from "@typebot.io/whatsapp/sendWhatsAppMessage"; import { z } from "zod"; +import { WHATSAPP_PREVIEW_SESSION_ID_PREFIX } from "../constants"; +import { normalizeWhatsAppPreviewPhoneNumber } from "../normalizeWhatsAppPreviewPhoneNumber"; export const startWhatsAppPreviewInputSchema = z.object({ - to: z - .string() - .min(1) - .transform((value) => - value.replace(/\s/g, "").replace(/\+/g, "").replace(/-/g, ""), - ), + to: z.string().min(1).transform(normalizeWhatsAppPreviewPhoneNumber), typebotId: z.string(), startFrom: startFromSchema.optional(), }); @@ -72,7 +69,7 @@ export const handleStartWhatsAppPreview = async ({ ) throw new ORPCError("NOT_FOUND", { message: "Typebot not found" }); - const sessionId = `wa-preview-${to}`; + const sessionId = `${WHATSAPP_PREVIEW_SESSION_ID_PREFIX}${to}`; const existingSession = await prisma.chatSession.findFirst({ where: { diff --git a/packages/whatsapp/src/normalizeWhatsAppPreviewPhoneNumber.ts b/packages/whatsapp/src/normalizeWhatsAppPreviewPhoneNumber.ts new file mode 100644 index 000000000..174e3b2aa --- /dev/null +++ b/packages/whatsapp/src/normalizeWhatsAppPreviewPhoneNumber.ts @@ -0,0 +1,2 @@ +export const normalizeWhatsAppPreviewPhoneNumber = (phoneNumber: string) => + phoneNumber.replace(/[\s+-]/g, "");