From 4ff366cd99770b28cc4cbeb854ef4cda47ad84f7 Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Wed, 21 May 2025 14:35:31 +0200 Subject: [PATCH] fix: canceling workflow reminders (#21416) * fix deleting reminders * schedule reminders if iffy is disabled * fix unit test * clean up code --------- Co-authored-by: CarinaWolli --- .../tasker/tasks/scanWorkflowBody.test.ts | 12 +- .../features/tasker/tasks/scanWorkflowBody.ts | 151 +++++++++--------- .../workflows/activateEventType.handler.ts | 5 +- 3 files changed, 89 insertions(+), 79 deletions(-) diff --git a/packages/features/tasker/tasks/scanWorkflowBody.test.ts b/packages/features/tasker/tasks/scanWorkflowBody.test.ts index dda3b03086..a17a95eed6 100644 --- a/packages/features/tasker/tasks/scanWorkflowBody.test.ts +++ b/packages/features/tasker/tasks/scanWorkflowBody.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it, vi, beforeEach } from "vitest"; import { lockUser, LockReason } from "@calcom/lib/autoLock"; import { scheduleWorkflowNotifications } from "@calcom/trpc/server/routers/viewer/workflows/util"; -import { scanWorkflowBody } from "./scanWorkflowBody"; +import { scanWorkflowBody, iffyScanBody } from "./scanWorkflowBody"; vi.mock("@calcom/lib/autoLock", async (importActual) => { const actual = await importActual(); @@ -19,6 +19,14 @@ vi.mock("@calcom/trpc/server/routers/viewer/workflows/util", () => ({ scheduleWorkflowNotifications: vi.fn(), })); +vi.mock("./scanWorkflowBody", async (importActual) => { + const actual = await importActual(); + return { + ...actual, + iffyScanBody: vi.fn(), + }; +}); + const mockWorkflowStep = { id: 1, reminderBody: "Test reminder body", @@ -58,7 +66,7 @@ describe("scanWorkflowBody", () => { await scanWorkflowBody(payload); - expect(prismaMock.workflowStep.findMany).not.toHaveBeenCalled(); + expect(iffyScanBody).not.toHaveBeenCalled(); }); it("should mark workflow step as safe if no reminder body", async () => { diff --git a/packages/features/tasker/tasks/scanWorkflowBody.ts b/packages/features/tasker/tasks/scanWorkflowBody.ts index 694dcde180..f1d06a652b 100644 --- a/packages/features/tasker/tasks/scanWorkflowBody.ts +++ b/packages/features/tasker/tasks/scanWorkflowBody.ts @@ -19,21 +19,6 @@ const log = logger.getSubLogger({ prefix: ["[tasker] scanWorkflowBody"] }); export async function scanWorkflowBody(payload: string) { const { workflowStepIds, userId } = scanWorkflowBodySchema.parse(JSON.parse(payload)); - if (!process.env.IFFY_API_KEY) { - log.info("IFFY_API_KEY not set, skipping scan"); - await prisma.workflowStep.updateMany({ - where: { - id: { - in: workflowStepIds, - }, - }, - data: { - verifiedAt: new Date(), - }, - }); - return; - } - const workflowSteps = await prisma.workflowStep.findMany({ where: { id: { @@ -55,68 +40,84 @@ export async function scanWorkflowBody(payload: string) { }, }); - for (const workflowStep of workflowSteps) { - if (!workflowStep.reminderBody) { - await prisma.workflowStep.update({ - where: { - id: workflowStep.id, - }, - data: { - verifiedAt: new Date(), - }, - }); - continue; - } - - const timeFormat = getTimeFormatStringFromUserTimeFormat(workflowStep.workflow.user?.timeFormat); - - // Determine if body is a template - const defaultTemplate = getTemplateBodyForAction({ - action: workflowStep.action, - locale: workflowStep.workflow.user?.locale ?? "en", - t: await getTranslation(workflowStep.workflow.user?.locale ?? "en", "common"), - template: workflowStep.template, - timeFormat, - }); - - if (!defaultTemplate) { - log.error(`Template not found for action ${workflowStep.action}, template ${workflowStep.template}`); - continue; - } - - if ( - compareReminderBodyToTemplate({ reminderBody: workflowStep.reminderBody, template: defaultTemplate }) - ) { - await prisma.workflowStep.update({ - where: { - id: workflowStep.id, - }, - data: { - verifiedAt: new Date(), - }, - }); - continue; - } - - const isSpam = await iffyScanBody(workflowStep.reminderBody, workflowStep.id); - - if (isSpam) { - if (!workflowStep.workflow.user?.whitelistWorkflows) { - // We won't delete the workflow step incase it is flagged as a false positive - log.warn(`Workflow step ${workflowStep.id} is spam with body ${workflowStep.reminderBody}`); - await lockUser("userId", userId.toString(), LockReason.SPAM_WORKFLOW_BODY); - - // Return early if spam is detected - return; + if (process.env.IFFY_API_KEY) { + for (const workflowStep of workflowSteps) { + if (!workflowStep.reminderBody) { + await prisma.workflowStep.update({ + where: { + id: workflowStep.id, + }, + data: { + verifiedAt: new Date(), + }, + }); + continue; } - log.warn( - `For whitelisted user, workflow step ${workflowStep.id} is spam with body ${workflowStep.reminderBody}` - ); - } - await prisma.workflowStep.update({ + const timeFormat = getTimeFormatStringFromUserTimeFormat(workflowStep.workflow.user?.timeFormat); + + // Determine if body is a template + const defaultTemplate = getTemplateBodyForAction({ + action: workflowStep.action, + locale: workflowStep.workflow.user?.locale ?? "en", + t: await getTranslation(workflowStep.workflow.user?.locale ?? "en", "common"), + template: workflowStep.template, + timeFormat, + }); + + if (!defaultTemplate) { + log.error(`Template not found for action ${workflowStep.action}, template ${workflowStep.template}`); + continue; + } + + if ( + compareReminderBodyToTemplate({ reminderBody: workflowStep.reminderBody, template: defaultTemplate }) + ) { + await prisma.workflowStep.update({ + where: { + id: workflowStep.id, + }, + data: { + verifiedAt: new Date(), + }, + }); + continue; + } + + const isSpam = await iffyScanBody(workflowStep.reminderBody, workflowStep.id); + + if (isSpam) { + if (!workflowStep.workflow.user?.whitelistWorkflows) { + // We won't delete the workflow step incase it is flagged as a false positive + log.warn(`Workflow step ${workflowStep.id} is spam with body ${workflowStep.reminderBody}`); + await lockUser("userId", userId.toString(), LockReason.SPAM_WORKFLOW_BODY); + + // Return early if spam is detected + return; + } + log.warn( + `For whitelisted user, workflow step ${workflowStep.id} is spam with body ${workflowStep.reminderBody}` + ); + } + + await prisma.workflowStep.update({ + where: { + id: workflowStep.id, + }, + data: { + verifiedAt: new Date(), + }, + }); + } + } + + if (!process.env.IFFY_API_KEY) { + log.info("IFFY_API_KEY not set, skipping scan"); + await prisma.workflowStep.updateMany({ where: { - id: workflowStep.id, + id: { + in: workflowStepIds, + }, }, data: { verifiedAt: new Date(), @@ -159,7 +160,7 @@ export async function scanWorkflowBody(payload: string) { }); } -const iffyScanBody = async (body: string, workflowStepId: number) => { +export const iffyScanBody = async (body: string, workflowStepId: number) => { try { const response = await fetch("https://api.iffy.com/api/v1/moderate", { method: "POST", diff --git a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts index bbadb64406..6c73e5c2bd 100644 --- a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts @@ -164,8 +164,9 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType const remindersToDelete = await prisma.workflowReminder.findMany({ where: { booking: { - eventTypeId: eventTypeId, - userId: ctx.user.id, + eventTypeId: { + in: Array.from(activeOnEventTypes.keys()), + }, }, workflowStepId: { in: eventTypeWorkflow.steps.map((step) => {