* Add whitelistWorkflows to user * Do not autolock whitelisted users * Add endpoint to whitelist a user * Get users data - include whitelistWorkflows * Whitelist a user on the user admin page * Fix typo * Add test * Use translations * Add `updateWhitelistWorkflows` to `UserRepository` * Use i18 for strings * Rename to whitelistWorkflows --------- Co-authored-by: Benny Joo <sldisek783@gmail.com>
163 lines
4.4 KiB
TypeScript
163 lines
4.4 KiB
TypeScript
import { AkismetClient } from "akismet-api";
|
|
import type { Comment } from "akismet-api";
|
|
import z from "zod";
|
|
|
|
import { getTemplateBodyForAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
|
|
import compareReminderBodyToTemplate from "@calcom/features/ee/workflows/lib/compareReminderBodyToTemplate";
|
|
import { lockUser, LockReason } from "@calcom/lib/autoLock";
|
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
|
import logger from "@calcom/lib/logger";
|
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
|
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
|
import prisma from "@calcom/prisma";
|
|
import { scheduleWorkflowNotifications } from "@calcom/trpc/server/routers/viewer/workflows/util";
|
|
|
|
export const scanWorkflowBodySchema = z.object({
|
|
userId: z.number(),
|
|
workflowStepIds: z.array(z.number()),
|
|
});
|
|
|
|
const log = logger.getSubLogger({ prefix: ["[tasker] scanWorkflowBody"] });
|
|
|
|
export async function scanWorkflowBody(payload: string) {
|
|
if (!process.env.AKISMET_API_KEY) {
|
|
log.info("AKISMET_API_KEY not set, skipping scan");
|
|
return;
|
|
}
|
|
|
|
const { workflowStepIds, userId } = scanWorkflowBodySchema.parse(JSON.parse(payload));
|
|
|
|
const workflowSteps = await prisma.workflowStep.findMany({
|
|
where: {
|
|
id: {
|
|
in: workflowStepIds,
|
|
},
|
|
},
|
|
include: {
|
|
workflow: {
|
|
select: {
|
|
user: {
|
|
select: {
|
|
locale: true,
|
|
timeFormat: true,
|
|
whitelistWorkflows: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const client = new AkismetClient({ key: process.env.AKISMET_API_KEY, blog: WEBAPP_URL });
|
|
|
|
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 comment: Comment = {
|
|
user_ip: "127.0.0.1",
|
|
content: workflowStep.reminderBody,
|
|
};
|
|
|
|
const isSpam = await client.checkSpam(comment);
|
|
|
|
if (isSpam) {
|
|
if (workflowStep.workflow.user?.whitelistWorkflows) {
|
|
log.warn(
|
|
`For whitelisted user, workflow step ${workflowStep.id} is spam with body ${workflowStep.reminderBody}`
|
|
);
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
} else {
|
|
await prisma.workflowStep.update({
|
|
where: {
|
|
id: workflowStep.id,
|
|
},
|
|
data: {
|
|
verifiedAt: new Date(),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
const workflow = await prisma.workflow.findFirst({
|
|
where: {
|
|
steps: {
|
|
some: {
|
|
id: {
|
|
in: workflowStepIds,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
include: {
|
|
activeOn: true,
|
|
team: true,
|
|
},
|
|
});
|
|
|
|
if (!workflow) {
|
|
log.warn(`Workflow with steps ${workflowStepIds} not found`);
|
|
return;
|
|
}
|
|
|
|
const isOrg = !!workflow?.team?.isOrganization;
|
|
|
|
await scheduleWorkflowNotifications({
|
|
activeOn: workflow.activeOn.map((activeOn) => activeOn.eventTypeId) ?? [],
|
|
isOrg,
|
|
workflowSteps,
|
|
time: workflow.time,
|
|
timeUnit: workflow.timeUnit,
|
|
trigger: workflow.trigger,
|
|
userId,
|
|
teamId: workflow.team?.id || null,
|
|
});
|
|
}
|