✨ Introducing Radar, fraud detection
This commit is contained in:
@ -101,6 +101,7 @@
|
||||
"@typebot.io/prisma": "workspace:*",
|
||||
"@typebot.io/schemas": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@typebot.io/radar": "workspace:*",
|
||||
"@types/canvas-confetti": "1.6.0",
|
||||
"@types/jsonwebtoken": "9.0.2",
|
||||
"@types/micro-cors": "0.1.3",
|
||||
|
@ -41,6 +41,7 @@ type UpdateTypebotPayload = Partial<
|
||||
| 'resultsTablePreferences'
|
||||
| 'isClosed'
|
||||
| 'whatsAppCredentialsId'
|
||||
| 'riskLevel'
|
||||
>
|
||||
>
|
||||
|
||||
|
@ -26,6 +26,7 @@ export const convertPublicTypebotToTypebot = (
|
||||
resultsTablePreferences: existingTypebot.resultsTablePreferences,
|
||||
selectedThemeTemplateId: existingTypebot.selectedThemeTemplateId,
|
||||
whatsAppCredentialsId: existingTypebot.whatsAppCredentialsId,
|
||||
riskLevel: existingTypebot.riskLevel,
|
||||
events: typebot.events,
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import { migrateTypebot } from '@typebot.io/lib/migrations/migrateTypebot'
|
||||
const omittedProps = {
|
||||
id: true,
|
||||
whatsAppCredentialsId: true,
|
||||
riskLevel: true,
|
||||
isClosed: true,
|
||||
isArchived: true,
|
||||
createdAt: true,
|
||||
@ -64,6 +65,7 @@ const migrateImportingTypebot = (
|
||||
whatsAppCredentialsId: null,
|
||||
publicId: null,
|
||||
folderId: null,
|
||||
riskLevel: null,
|
||||
} satisfies Typebot
|
||||
return migrateTypebot(fullTypebot)
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden'
|
||||
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { computeRiskLevel } from '@typebot.io/radar'
|
||||
import { env } from '@typebot.io/env'
|
||||
|
||||
export const publishTypebot = authenticatedProcedure
|
||||
.meta({
|
||||
@ -78,6 +80,52 @@ export const publishTypebot = authenticatedProcedure
|
||||
})
|
||||
}
|
||||
|
||||
if (existingTypebot.riskLevel && existingTypebot.riskLevel > 80)
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message:
|
||||
'Radar detected a potential malicious typebot. This bot is being manually reviewed by Fraud Prevention team.',
|
||||
})
|
||||
|
||||
const riskLevel = computeRiskLevel({
|
||||
name: existingTypebot.name,
|
||||
groups: parseGroups(existingTypebot.groups, {
|
||||
typebotVersion: existingTypebot.version,
|
||||
}),
|
||||
})
|
||||
|
||||
if (riskLevel > 0) {
|
||||
if (env.MESSAGE_WEBHOOK_URL)
|
||||
await fetch(env.MESSAGE_WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
body: `🚨 *Radar detected a potential malicious typebot* 🚨\n\n*Typebot:* ${existingTypebot.name}\n*Risk level:* ${riskLevel}/100\n*Typebot ID:* ${existingTypebot.id}\n*Workspace ID:* ${existingTypebot.workspaceId}\n*User ID:* ${user.id}`,
|
||||
}).catch((err) => {
|
||||
console.error('Failed to send message', err)
|
||||
})
|
||||
|
||||
await prisma.typebot.updateMany({
|
||||
where: {
|
||||
id: existingTypebot.id,
|
||||
},
|
||||
data: {
|
||||
riskLevel,
|
||||
},
|
||||
})
|
||||
if (riskLevel > 80) {
|
||||
if (existingTypebot.publishedTypebot)
|
||||
await prisma.publicTypebot.deleteMany({
|
||||
where: {
|
||||
id: existingTypebot.publishedTypebot.id,
|
||||
},
|
||||
})
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message:
|
||||
'Radar detected a potential malicious typebot. This bot is being manually reviewed by Fraud Prevention team.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (existingTypebot.publishedTypebot)
|
||||
await prisma.publicTypebot.updateMany({
|
||||
where: {
|
||||
|
@ -35,6 +35,7 @@ const typebotUpdateSchemaPick = {
|
||||
customDomain: true,
|
||||
isClosed: true,
|
||||
whatsAppCredentialsId: true,
|
||||
riskLevel: true,
|
||||
events: true,
|
||||
} as const
|
||||
|
||||
|
@ -4,10 +4,10 @@ import prisma from '@typebot.io/lib/prisma'
|
||||
import { googleSheetsScopes } from './consent-url'
|
||||
import { stringify } from 'querystring'
|
||||
import { badRequest, notAuthenticated } from '@typebot.io/lib/api'
|
||||
import { oauth2Client } from '@/lib/googleSheets'
|
||||
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
||||
import { env } from '@typebot.io/env'
|
||||
import { encrypt } from '@typebot.io/lib/api/encryption/encrypt'
|
||||
import { OAuth2Client } from 'google-auth-library'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await getAuthenticatedUser(req, res)
|
||||
@ -22,6 +22,11 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (!workspaceId) return badRequest(res)
|
||||
if (!code)
|
||||
return res.status(400).send({ message: "Bad request, couldn't get code" })
|
||||
const oauth2Client = new OAuth2Client(
|
||||
env.GOOGLE_CLIENT_ID,
|
||||
env.GOOGLE_CLIENT_SECRET,
|
||||
`${env.NEXTAUTH_URL}/api/credentials/google-sheets/callback`
|
||||
)
|
||||
const { tokens } = await oauth2Client.getToken(code)
|
||||
if (!tokens?.access_token) {
|
||||
console.error('Error getting oAuth tokens:')
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { oauth2Client } from '@/lib/googleSheets'
|
||||
import { env } from '@typebot.io/env'
|
||||
import { OAuth2Client } from 'google-auth-library'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
export const googleSheetsScopes = [
|
||||
@ -9,6 +10,11 @@ export const googleSheetsScopes = [
|
||||
|
||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === 'GET') {
|
||||
const oauth2Client = new OAuth2Client(
|
||||
env.GOOGLE_CLIENT_ID,
|
||||
env.GOOGLE_CLIENT_SECRET,
|
||||
`${env.NEXTAUTH_URL}/api/credentials/google-sheets/callback`
|
||||
)
|
||||
const url = oauth2Client.generateAuthUrl({
|
||||
access_type: 'offline',
|
||||
scope: googleSheetsScopes,
|
||||
|
@ -15003,6 +15003,10 @@
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"riskLevel": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -15026,7 +15030,8 @@
|
||||
"resultsTablePreferences",
|
||||
"isArchived",
|
||||
"isClosed",
|
||||
"whatsAppCredentialsId"
|
||||
"whatsAppCredentialsId",
|
||||
"riskLevel"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@ -18922,6 +18927,10 @@
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"riskLevel": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"events": {
|
||||
"enum": [
|
||||
"null"
|
||||
@ -22591,6 +22600,10 @@
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"riskLevel": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"events": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
@ -26396,6 +26409,10 @@
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"riskLevel": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -26419,7 +26436,8 @@
|
||||
"resultsTablePreferences",
|
||||
"isArchived",
|
||||
"isClosed",
|
||||
"whatsAppCredentialsId"
|
||||
"whatsAppCredentialsId",
|
||||
"riskLevel"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@ -30276,6 +30294,10 @@
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"riskLevel": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -30298,7 +30320,8 @@
|
||||
"resultsTablePreferences",
|
||||
"isArchived",
|
||||
"isClosed",
|
||||
"whatsAppCredentialsId"
|
||||
"whatsAppCredentialsId",
|
||||
"riskLevel"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
@ -34024,6 +34047,10 @@
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"riskLevel": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -34047,7 +34074,8 @@
|
||||
"resultsTablePreferences",
|
||||
"isArchived",
|
||||
"isClosed",
|
||||
"whatsAppCredentialsId"
|
||||
"whatsAppCredentialsId",
|
||||
"riskLevel"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@ -52980,6 +53008,10 @@
|
||||
"whatsAppCredentialsId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"riskLevel": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -53003,7 +53035,8 @@
|
||||
"resultsTablePreferences",
|
||||
"isArchived",
|
||||
"isClosed",
|
||||
"whatsAppCredentialsId"
|
||||
"whatsAppCredentialsId",
|
||||
"riskLevel"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
Reference in New Issue
Block a user