-
Notifications
You must be signed in to change notification settings - Fork 8.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: Add spam checking for workflow bodies #18822
base: main
Are you sure you want to change the base?
Changes from 5 commits
a5fb61f
8a63afa
2db52be
da358a4
38ca4fc
60c5cdb
5e447a1
7067778
abd1b50
01d2d58
834cb6b
06e49cc
7be1ec9
62d3726
0700a53
b88cd3e
d096470
d9a0313
c314420
60ca133
2e569e3
bdfa6f9
a33e995
2133ea3
60b0879
5d2762f
9f53fd0
49b3396
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { AkismetClient } from "akismet-api"; | ||
import type { Comment } from "akismet-api"; | ||
import z from "zod"; | ||
|
||
import { lockUser } from "@calcom/lib/autoLock"; | ||
import { WEBAPP_URL } from "@calcom/lib/constants"; | ||
import logger from "@calcom/lib/logger"; | ||
import prisma from "@calcom/prisma"; | ||
|
||
export const scanWorkflowBodySchema = z.object({ | ||
userId: z.number(), | ||
workflowStepId: z.number(), | ||
}); | ||
|
||
const log = logger.getSubLogger({ prefix: ["[tasker] scanWorkflowBody"] }); | ||
|
||
export async function scanWorkflowBody(payload: string) { | ||
if (!process.env.AKISMET_API_KEY) { | ||
log.warn("AKISMET_API_KEY not set, skipping scan"); | ||
joeauyeung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
|
||
const { workflowStepId, userId } = scanWorkflowBodySchema.parse(JSON.parse(payload)); | ||
|
||
const workflowStep = await prisma.workflowStep.findUnique({ | ||
where: { | ||
id: workflowStepId, | ||
}, | ||
}); | ||
|
||
if (!workflowStep?.reminderBody) return; | ||
|
||
const client = new AkismetClient({ key: process.env.AKISMET_API_KEY, blog: WEBAPP_URL }); | ||
|
||
const comment: Comment = { | ||
user_ip: "127.0.0.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment needs an IP. From Akismet support we can just use this. IP is used for scanning against know abuse IPs and adding a reputation score. |
||
content: workflowStep.reminderBody, | ||
}; | ||
|
||
const isSpam = await client.checkSpam(comment); | ||
|
||
if (isSpam) { | ||
log.warn(`Workflow step ${workflowStepId} is spam with body ${workflowStep.reminderBody}`); | ||
|
||
await prisma.workflowStep.delete({ | ||
joeauyeung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
where: { | ||
id: workflowStepId, | ||
}, | ||
}); | ||
|
||
await lockUser("userId", userId.toString()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ import { | |
removeSmsReminderFieldForEventTypes, | ||
isStepEdited, | ||
getEmailTemplateText, | ||
scheduleWorkflowBodyScan, | ||
} from "./util"; | ||
|
||
type UpdateOptions = { | ||
|
@@ -398,6 +399,13 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { | |
}, | ||
}); | ||
|
||
await scheduleWorkflowBodyScan({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the update above, I think we need to set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But if we do that, then scheduling the updated reminders will fail too ( |
||
workflowStepId: oldStep.id, | ||
userId: ctx.user.id, | ||
newStepBody: newStep?.reminderBody, | ||
oldStepBody: oldStep?.reminderBody, | ||
}); | ||
|
||
// cancel all notifications of edited step | ||
await WorkflowRepository.deleteAllWorkflowReminders(remindersFromStep); | ||
|
||
|
@@ -466,6 +474,16 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { | |
) | ||
); | ||
|
||
await Promise.all( | ||
createdSteps.map((step) => | ||
scheduleWorkflowBodyScan({ | ||
workflowStepId: step.id, | ||
userId: ctx.user.id, | ||
newStepBody: step.reminderBody, | ||
}) | ||
) | ||
); | ||
|
||
// schedule notification for new step | ||
await scheduleWorkflowNotifications( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, they would fail because tasker needs to run before it's set to true |
||
activeOn, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need the userId to know which user to lock. This comes from the context of the workflow update tRPC endpoint.