From bf1f6576421e768f5c4506c5f0610ec2b3e05ae4 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Fri, 6 Oct 2023 16:34:10 +0200 Subject: [PATCH] :fire: Remove streamer Pages API endpoint --- .../integrations/openai/api/listModels.ts | 2 +- .../zemanticAi/api/listProjects.ts | 2 +- .../credentials/api/createCredentials.ts | 2 +- .../src/features/whatsapp/getPhoneNumber.ts | 2 +- .../features/whatsapp/getSystemTokenInfo.ts | 2 +- apps/builder/src/lib/googleSheets.ts | 3 +- apps/builder/src/pages/api/credentials.ts | 2 +- .../api/credentials/google-sheets/callback.ts | 3 +- .../[typebotId]/whatsapp/media/[mediaId].ts | 2 +- apps/viewer/next-env.d.ts | 1 + .../api/integrations/openai/streamer/route.ts | 27 ++-- apps/viewer/src/lib/google-sheets.ts | 3 +- .../pages/api/integrations/openai/streamer.ts | 115 ------------------ .../stripe/createPaymentIntent.ts | 2 +- .../[typebotId]/integrations/email.tsx | 3 +- apps/viewer/src/test/utils/databaseActions.ts | 2 +- apps/viewer/tsconfig.json | 19 ++- .../computePaymentInputRuntimeOptions.ts | 2 +- .../helpers/getAuthenticatedGoogleDoc.ts | 3 +- .../openai/createChatCompletionOpenAI.ts | 6 +- .../openai/getChatCompletionStream.ts | 4 +- .../sendEmail/executeSendEmailBlock.tsx | 2 +- .../zemanticAi/executeZemanticAiBlock.ts | 2 +- .../bot-engine/whatsapp/resumeWhatsAppFlow.ts | 2 +- packages/lib/api/encryption.ts | 76 ------------ packages/lib/api/encryption/decrypt.ts | 10 ++ .../decryptV1.ts} | 0 packages/lib/api/encryption/decryptV2.ts | 35 ++++++ packages/lib/api/encryption/encrypt.ts | 39 ++++++ packages/lib/api/index.ts | 1 - packages/lib/playwright/databaseSetup.ts | 2 +- 31 files changed, 145 insertions(+), 231 deletions(-) rename apps/viewer/src/{pages => app}/api/integrations/openai/streamer/route.ts (87%) delete mode 100644 apps/viewer/src/pages/api/integrations/openai/streamer.ts delete mode 100644 packages/lib/api/encryption.ts create mode 100644 packages/lib/api/encryption/decrypt.ts rename packages/lib/api/{encryptionV1.ts => encryption/decryptV1.ts} (100%) create mode 100644 packages/lib/api/encryption/decryptV2.ts create mode 100644 packages/lib/api/encryption/encrypt.ts diff --git a/apps/builder/src/features/blocks/integrations/openai/api/listModels.ts b/apps/builder/src/features/blocks/integrations/openai/api/listModels.ts index b6b6d6c491d..46be0629ac1 100644 --- a/apps/builder/src/features/blocks/integrations/openai/api/listModels.ts +++ b/apps/builder/src/features/blocks/integrations/openai/api/listModels.ts @@ -3,7 +3,7 @@ import { authenticatedProcedure } from '@/helpers/server/trpc' import { TRPCError } from '@trpc/server' import { z } from 'zod' import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden' -import { decrypt } from '@typebot.io/lib/api' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { OpenAICredentials, defaultBaseUrl, diff --git a/apps/builder/src/features/blocks/integrations/zemanticAi/api/listProjects.ts b/apps/builder/src/features/blocks/integrations/zemanticAi/api/listProjects.ts index 2852acdeb2c..eaf5ec18931 100644 --- a/apps/builder/src/features/blocks/integrations/zemanticAi/api/listProjects.ts +++ b/apps/builder/src/features/blocks/integrations/zemanticAi/api/listProjects.ts @@ -3,7 +3,7 @@ import { authenticatedProcedure } from '@/helpers/server/trpc' import { TRPCError } from '@trpc/server' import { z } from 'zod' import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden' -import { decrypt } from '@typebot.io/lib/api' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { ZemanticAiCredentials } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi' import got from 'got' diff --git a/apps/builder/src/features/credentials/api/createCredentials.ts b/apps/builder/src/features/credentials/api/createCredentials.ts index c0da7f0f165..3a7571e7e9d 100644 --- a/apps/builder/src/features/credentials/api/createCredentials.ts +++ b/apps/builder/src/features/credentials/api/createCredentials.ts @@ -5,7 +5,7 @@ import { stripeCredentialsSchema } from '@typebot.io/schemas/features/blocks/inp import { googleSheetsCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/googleSheets/schemas' import { openAICredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/openai' import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail' -import { encrypt } from '@typebot.io/lib/api/encryption' +import { encrypt } from '@typebot.io/lib/api/encryption/encrypt' import { z } from 'zod' import { whatsAppCredentialsSchema } from '@typebot.io/schemas/features/whatsapp' import { Credentials, zemanticAiCredentialsSchema } from '@typebot.io/schemas' diff --git a/apps/builder/src/features/whatsapp/getPhoneNumber.ts b/apps/builder/src/features/whatsapp/getPhoneNumber.ts index 56c1c291a8b..57d02e52985 100644 --- a/apps/builder/src/features/whatsapp/getPhoneNumber.ts +++ b/apps/builder/src/features/whatsapp/getPhoneNumber.ts @@ -2,7 +2,7 @@ import { authenticatedProcedure } from '@/helpers/server/trpc' import { z } from 'zod' import got from 'got' import prisma from '@typebot.io/lib/prisma' -import { decrypt } from '@typebot.io/lib/api' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { TRPCError } from '@trpc/server' import { WhatsAppCredentials } from '@typebot.io/schemas/features/whatsapp' diff --git a/apps/builder/src/features/whatsapp/getSystemTokenInfo.ts b/apps/builder/src/features/whatsapp/getSystemTokenInfo.ts index b6bccc19cb8..6c08f107f06 100644 --- a/apps/builder/src/features/whatsapp/getSystemTokenInfo.ts +++ b/apps/builder/src/features/whatsapp/getSystemTokenInfo.ts @@ -4,7 +4,7 @@ import got from 'got' import { TRPCError } from '@trpc/server' import { WhatsAppCredentials } from '@typebot.io/schemas/features/whatsapp' import prisma from '@typebot.io/lib/prisma' -import { decrypt } from '@typebot.io/lib/api/encryption' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' const inputSchema = z.object({ token: z.string().optional(), diff --git a/apps/builder/src/lib/googleSheets.ts b/apps/builder/src/lib/googleSheets.ts index 694c0d6f1ed..9840924a86c 100644 --- a/apps/builder/src/lib/googleSheets.ts +++ b/apps/builder/src/lib/googleSheets.ts @@ -2,9 +2,10 @@ import { Credentials as CredentialsFromDb } from '@typebot.io/prisma' import { OAuth2Client, Credentials } from 'google-auth-library' import { GoogleSheetsCredentials } from '@typebot.io/schemas' import { isDefined } from '@typebot.io/lib' -import { decrypt, encrypt } from '@typebot.io/lib/api' import { env } from '@typebot.io/env' import prisma from '@typebot.io/lib/prisma' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' +import { encrypt } from '@typebot.io/lib/api/encryption/encrypt' export const oauth2Client = new OAuth2Client( env.GOOGLE_CLIENT_ID, diff --git a/apps/builder/src/pages/api/credentials.ts b/apps/builder/src/pages/api/credentials.ts index 48ecd6b3003..7b660e63423 100644 --- a/apps/builder/src/pages/api/credentials.ts +++ b/apps/builder/src/pages/api/credentials.ts @@ -7,8 +7,8 @@ import { forbidden, methodNotAllowed, notAuthenticated, - encrypt, } from '@typebot.io/lib/api' +import { encrypt } from '@typebot.io/lib/api/encryption/encrypt' const handler = async (req: NextApiRequest, res: NextApiResponse) => { const user = await getAuthenticatedUser(req, res) diff --git a/apps/builder/src/pages/api/credentials/google-sheets/callback.ts b/apps/builder/src/pages/api/credentials/google-sheets/callback.ts index 40e9a571635..733ac7dc6d7 100644 --- a/apps/builder/src/pages/api/credentials/google-sheets/callback.ts +++ b/apps/builder/src/pages/api/credentials/google-sheets/callback.ts @@ -3,10 +3,11 @@ import { Prisma } from '@typebot.io/prisma' import prisma from '@typebot.io/lib/prisma' import { googleSheetsScopes } from './consent-url' import { stringify } from 'querystring' -import { badRequest, encrypt, notAuthenticated } from '@typebot.io/lib/api' +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' const handler = async (req: NextApiRequest, res: NextApiResponse) => { const user = await getAuthenticatedUser(req, res) diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/whatsapp/media/[mediaId].ts b/apps/builder/src/pages/api/typebots/[typebotId]/whatsapp/media/[mediaId].ts index d81c0e30e49..1dc6394cc58 100644 --- a/apps/builder/src/pages/api/typebots/[typebotId]/whatsapp/media/[mediaId].ts +++ b/apps/builder/src/pages/api/typebots/[typebotId]/whatsapp/media/[mediaId].ts @@ -2,7 +2,6 @@ import prisma from '@typebot.io/lib/prisma' import { NextApiRequest, NextApiResponse } from 'next' import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser' import { - decrypt, methodNotAllowed, notAuthenticated, notFound, @@ -10,6 +9,7 @@ import { import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden' import { WhatsAppCredentials } from '@typebot.io/schemas/features/whatsapp' import got from 'got' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'GET') { diff --git a/apps/viewer/next-env.d.ts b/apps/viewer/next-env.d.ts index 4f11a03dc6c..fd36f9494e2 100644 --- a/apps/viewer/next-env.d.ts +++ b/apps/viewer/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/viewer/src/pages/api/integrations/openai/streamer/route.ts b/apps/viewer/src/app/api/integrations/openai/streamer/route.ts similarity index 87% rename from apps/viewer/src/pages/api/integrations/openai/streamer/route.ts rename to apps/viewer/src/app/api/integrations/openai/streamer/route.ts index e610c0e378f..e0feb3e1c81 100644 --- a/apps/viewer/src/pages/api/integrations/openai/streamer/route.ts +++ b/apps/viewer/src/app/api/integrations/openai/streamer/route.ts @@ -7,24 +7,25 @@ import OpenAI from 'openai' import { NextResponse } from 'next/dist/server/web/spec-extension/response' export const runtime = 'edge' -export const regions = ['lhr1'] +export const preferredRegion = 'lhr1' export const dynamic = 'force-dynamic' const responseHeaders = { 'Access-Control-Allow-Origin': '*', } -const handler = async (req: Request) => { - if (req.method === 'OPTIONS') { - return new Response('ok', { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST', - 'Access-Control-Expose-Headers': 'Content-Length, X-JSON', - 'Access-Control-Allow-Headers': '*', - }, - }) - } +export async function OPTIONS() { + return new Response('ok', { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST', + 'Access-Control-Expose-Headers': 'Content-Length, X-JSON', + 'Access-Control-Allow-Headers': '*', + }, + }) +} + +export async function POST(req: Request) { const { sessionId, messages } = (await req.json()) as { sessionId: string messages: OpenAI.Chat.ChatCompletionMessage[] @@ -110,5 +111,3 @@ const handler = async (req: Request) => { } } } - -export default handler diff --git a/apps/viewer/src/lib/google-sheets.ts b/apps/viewer/src/lib/google-sheets.ts index 3dd807650db..1337f58bc2f 100644 --- a/apps/viewer/src/lib/google-sheets.ts +++ b/apps/viewer/src/lib/google-sheets.ts @@ -2,9 +2,10 @@ import { Credentials as CredentialsFromDb } from '@typebot.io/prisma' import { OAuth2Client, Credentials } from 'google-auth-library' import { GoogleSheetsCredentials } from '@typebot.io/schemas' import { isDefined } from '@typebot.io/lib' -import { decrypt, encrypt } from '@typebot.io/lib/api' import { env } from '@typebot.io/env' import prisma from '@typebot.io/lib/prisma' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' +import { encrypt } from '@typebot.io/lib/api/encryption/encrypt' export const getAuthenticatedGoogleClient = async ( credentialsId: string diff --git a/apps/viewer/src/pages/api/integrations/openai/streamer.ts b/apps/viewer/src/pages/api/integrations/openai/streamer.ts deleted file mode 100644 index aabef1d88d1..00000000000 --- a/apps/viewer/src/pages/api/integrations/openai/streamer.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { connect } from '@planetscale/database' -import { env } from '@typebot.io/env' -import { IntegrationBlockType, SessionState } from '@typebot.io/schemas' -import { StreamingTextResponse } from 'ai' -import { getChatCompletionStream } from '@typebot.io/bot-engine/blocks/integrations/openai/getChatCompletionStream' -import OpenAI from 'openai' -import { NextResponse } from 'next/dist/server/web/spec-extension/response' - -export const config = { - runtime: 'edge', - regions: ['lhr1'], -} - -const responseHeaders = { - 'Access-Control-Allow-Origin': '*', -} - -const handler = async (req: Request) => { - if (req.method === 'OPTIONS') { - return new Response('ok', { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST', - 'Access-Control-Expose-Headers': 'Content-Length, X-JSON', - 'Access-Control-Allow-Headers': '*', - }, - }) - } - const { sessionId, messages } = (await req.json()) as { - sessionId: string - messages: OpenAI.Chat.ChatCompletionMessage[] - } - - if (!sessionId) - return NextResponse.json( - { message: 'No session ID provided' }, - { status: 400, headers: responseHeaders } - ) - - if (!messages) - return NextResponse.json( - { message: 'No messages provided' }, - { status: 400, headers: responseHeaders } - ) - - const conn = connect({ url: env.DATABASE_URL }) - - const chatSession = await conn.execute( - 'select state from ChatSession where id=?', - [sessionId] - ) - - const state = (chatSession.rows.at(0) as { state: SessionState } | undefined) - ?.state - - if (!state) - return NextResponse.json( - { message: 'No state found' }, - { status: 400, headers: responseHeaders } - ) - - const group = state.typebotsQueue[0].typebot.groups.find( - (group) => group.id === state.currentBlock?.groupId - ) - const blockIndex = - group?.blocks.findIndex( - (block) => block.id === state.currentBlock?.blockId - ) ?? -1 - - const block = blockIndex >= 0 ? group?.blocks[blockIndex ?? 0] : null - - if (!block || !group) - return NextResponse.json( - { message: 'Current block not found' }, - { status: 400, headers: responseHeaders } - ) - - if ( - block.type !== IntegrationBlockType.OPEN_AI || - block.options.task !== 'Create chat completion' - ) - return NextResponse.json( - { message: 'Current block is not an OpenAI block' }, - { status: 400, headers: responseHeaders } - ) - - try { - const stream = await getChatCompletionStream(conn)( - state, - block.options, - messages - ) - if (!stream) - return NextResponse.json( - { message: 'Could not create stream' }, - { status: 400, headers: responseHeaders } - ) - - return new StreamingTextResponse(stream, { - headers: responseHeaders, - }) - } catch (error) { - if (error instanceof OpenAI.APIError) { - const { name, status, message } = error - return NextResponse.json( - { name, status, message }, - { status, headers: responseHeaders } - ) - } else { - throw error - } - } -} - -export default handler diff --git a/apps/viewer/src/pages/api/integrations/stripe/createPaymentIntent.ts b/apps/viewer/src/pages/api/integrations/stripe/createPaymentIntent.ts index 615549754ce..a6f99839f75 100644 --- a/apps/viewer/src/pages/api/integrations/stripe/createPaymentIntent.ts +++ b/apps/viewer/src/pages/api/integrations/stripe/createPaymentIntent.ts @@ -1,11 +1,11 @@ import { NextApiRequest, NextApiResponse } from 'next' import { badRequest, - decrypt, forbidden, initMiddleware, methodNotAllowed, } from '@typebot.io/lib/api' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import Stripe from 'stripe' import Cors from 'cors' diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/integrations/email.tsx b/apps/viewer/src/pages/api/typebots/[typebotId]/integrations/email.tsx index 0db54d9534f..94566643eb0 100644 --- a/apps/viewer/src/pages/api/typebots/[typebotId]/integrations/email.tsx +++ b/apps/viewer/src/pages/api/typebots/[typebotId]/integrations/email.tsx @@ -8,7 +8,8 @@ import { NextApiRequest, NextApiResponse } from 'next' import { createTransport, getTestMessageUrl } from 'nodemailer' import { isDefined, isEmpty, isNotDefined, omit } from '@typebot.io/lib' import { parseAnswers } from '@typebot.io/lib/results' -import { methodNotAllowed, initMiddleware, decrypt } from '@typebot.io/lib/api' +import { methodNotAllowed, initMiddleware } from '@typebot.io/lib/api' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import Cors from 'cors' import Mail from 'nodemailer/lib/mailer' diff --git a/apps/viewer/src/test/utils/databaseActions.ts b/apps/viewer/src/test/utils/databaseActions.ts index e3edbaa1ae4..bec240ff930 100644 --- a/apps/viewer/src/test/utils/databaseActions.ts +++ b/apps/viewer/src/test/utils/databaseActions.ts @@ -1,6 +1,6 @@ import { PrismaClient } from '@typebot.io/prisma' import { SmtpCredentials } from '@typebot.io/schemas' -import { encrypt } from '@typebot.io/lib/api' +import { encrypt } from '@typebot.io/lib/api/encryption/encrypt' import { proWorkspaceId } from '@typebot.io/lib/playwright/databaseSetup' const prisma = new PrismaClient() diff --git a/apps/viewer/tsconfig.json b/apps/viewer/tsconfig.json index 84e4ae1b487..843b0df34c6 100644 --- a/apps/viewer/tsconfig.json +++ b/apps/viewer/tsconfig.json @@ -3,8 +3,21 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@/*": ["src/*"] - } + "@/*": [ + "src/*" + ] + }, + "plugins": [ + { + "name": "next" + } + ], + "strictNullChecks": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ] } diff --git a/packages/bot-engine/blocks/inputs/payment/computePaymentInputRuntimeOptions.ts b/packages/bot-engine/blocks/inputs/payment/computePaymentInputRuntimeOptions.ts index d9033fc19f2..3db57e071aa 100644 --- a/packages/bot-engine/blocks/inputs/payment/computePaymentInputRuntimeOptions.ts +++ b/packages/bot-engine/blocks/inputs/payment/computePaymentInputRuntimeOptions.ts @@ -6,7 +6,7 @@ import { StripeCredentials, } from '@typebot.io/schemas' import Stripe from 'stripe' -import { decrypt } from '@typebot.io/lib/api/encryption' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { parseVariables } from '../../../variables/parseVariables' import prisma from '@typebot.io/lib/prisma' diff --git a/packages/bot-engine/blocks/integrations/googleSheets/helpers/getAuthenticatedGoogleDoc.ts b/packages/bot-engine/blocks/integrations/googleSheets/helpers/getAuthenticatedGoogleDoc.ts index 5602831685d..ed30455368b 100644 --- a/packages/bot-engine/blocks/integrations/googleSheets/helpers/getAuthenticatedGoogleDoc.ts +++ b/packages/bot-engine/blocks/integrations/googleSheets/helpers/getAuthenticatedGoogleDoc.ts @@ -1,6 +1,7 @@ import { TRPCError } from '@trpc/server' import { env } from '@typebot.io/env' -import { decrypt, encrypt } from '@typebot.io/lib/api/encryption' +import { encrypt } from '@typebot.io/lib/api/encryption/encrypt' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { isDefined } from '@typebot.io/lib/utils' import { GoogleSheetsCredentials } from '@typebot.io/schemas/features/blocks/integrations/googleSheets/schemas' import { Credentials as CredentialsFromDb } from '@typebot.io/prisma' diff --git a/packages/bot-engine/blocks/integrations/openai/createChatCompletionOpenAI.ts b/packages/bot-engine/blocks/integrations/openai/createChatCompletionOpenAI.ts index b01d0be2194..10a0913e5de 100644 --- a/packages/bot-engine/blocks/integrations/openai/createChatCompletionOpenAI.ts +++ b/packages/bot-engine/blocks/integrations/openai/createChatCompletionOpenAI.ts @@ -1,6 +1,7 @@ import { Block, BubbleBlockType, + Credentials, SessionState, TypebotInSession, } from '@typebot.io/schemas' @@ -10,7 +11,7 @@ import { chatCompletionMessageRoles, } from '@typebot.io/schemas/features/blocks/integrations/openai' import { byId, isEmpty } from '@typebot.io/lib' -import { decrypt, isCredentialsV2 } from '@typebot.io/lib/api/encryption' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { resumeChatCompletion } from './resumeChatCompletion' import { parseChatCompletionMessages } from './parseChatCompletionMessages' import { executeChatCompletionOpenAIRequest } from './executeChatCompletionOpenAIRequest' @@ -167,3 +168,6 @@ const getNextBlock = ) : connectedGroup?.blocks.at(0) } + +const isCredentialsV2 = (credentials: Pick) => + credentials.iv.length === 24 diff --git a/packages/bot-engine/blocks/integrations/openai/getChatCompletionStream.ts b/packages/bot-engine/blocks/integrations/openai/getChatCompletionStream.ts index cec36cb1156..0e1aafec815 100644 --- a/packages/bot-engine/blocks/integrations/openai/getChatCompletionStream.ts +++ b/packages/bot-engine/blocks/integrations/openai/getChatCompletionStream.ts @@ -1,5 +1,5 @@ import { Connection } from '@planetscale/database' -import { decrypt } from '@typebot.io/lib/api/encryption' +import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2' import { isNotEmpty } from '@typebot.io/lib/utils' import { ChatCompletionOpenAIOptions, @@ -27,7 +27,7 @@ export const getChatCompletionStream = console.error('Could not find credentials in database') return } - const { apiKey } = (await decrypt( + const { apiKey } = (await decryptV2( credentials.data, credentials.iv )) as OpenAICredentials['data'] diff --git a/packages/bot-engine/blocks/integrations/sendEmail/executeSendEmailBlock.tsx b/packages/bot-engine/blocks/integrations/sendEmail/executeSendEmailBlock.tsx index b37b9dd7446..ec1dc1e8fab 100644 --- a/packages/bot-engine/blocks/integrations/sendEmail/executeSendEmailBlock.tsx +++ b/packages/bot-engine/blocks/integrations/sendEmail/executeSendEmailBlock.tsx @@ -13,7 +13,7 @@ import { createTransport } from 'nodemailer' import Mail from 'nodemailer/lib/mailer' import { byId, isDefined, isEmpty, isNotDefined, omit } from '@typebot.io/lib' import { getDefinedVariables, parseAnswers } from '@typebot.io/lib/results' -import { decrypt } from '@typebot.io/lib/api' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { defaultFrom, defaultTransportOptions } from './constants' import { findUniqueVariableValue } from '../../../variables/findUniqueVariableValue' import { env } from '@typebot.io/env' diff --git a/packages/bot-engine/blocks/integrations/zemanticAi/executeZemanticAiBlock.ts b/packages/bot-engine/blocks/integrations/zemanticAi/executeZemanticAiBlock.ts index 95f4881aedb..efefc0f7960 100644 --- a/packages/bot-engine/blocks/integrations/zemanticAi/executeZemanticAiBlock.ts +++ b/packages/bot-engine/blocks/integrations/zemanticAi/executeZemanticAiBlock.ts @@ -5,7 +5,7 @@ import { ZemanticAiResponse, } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi' import got from 'got' -import { decrypt } from '@typebot.io/lib/api/encryption' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { byId, isDefined, isEmpty } from '@typebot.io/lib' import { getDefinedVariables, parseAnswers } from '@typebot.io/lib/results' import prisma from '@typebot.io/lib/prisma' diff --git a/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts b/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts index 06d9a582408..6bbfdb02643 100644 --- a/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts +++ b/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts @@ -8,7 +8,7 @@ import { sendChatReplyToWhatsApp } from './sendChatReplyToWhatsApp' import { startWhatsAppSession } from './startWhatsAppSession' import { getSession } from '../queries/getSession' import { continueBotFlow } from '../continueBotFlow' -import { decrypt } from '@typebot.io/lib/api' +import { decrypt } from '@typebot.io/lib/api/encryption/decrypt' import { saveStateToDatabase } from '../saveStateToDatabase' import prisma from '@typebot.io/lib/prisma' import { isDefined } from '@typebot.io/lib/utils' diff --git a/packages/lib/api/encryption.ts b/packages/lib/api/encryption.ts deleted file mode 100644 index 0d5af3fcc6d..00000000000 --- a/packages/lib/api/encryption.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Credentials } from '@typebot.io/schemas/features/credentials' -import { decryptV1 } from './encryptionV1' -import { env } from '@typebot.io/env' - -const algorithm = 'AES-GCM' -const secretKey = env.ENCRYPTION_SECRET - -export const encrypt = async ( - data: object -): Promise<{ encryptedData: string; iv: string }> => { - if (!secretKey) throw new Error('ENCRYPTION_SECRET is not in environment') - const iv = crypto.getRandomValues(new Uint8Array(12)) - const encodedData = new TextEncoder().encode(JSON.stringify(data)) - - const key = await crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(secretKey), - algorithm, - false, - ['encrypt'] - ) - - const encryptedBuffer = await crypto.subtle.encrypt( - { name: algorithm, iv }, - key, - encodedData - ) - - const encryptedData = btoa( - String.fromCharCode.apply(null, Array.from(new Uint8Array(encryptedBuffer))) - ) - - const ivHex = Array.from(iv) - .map((byte) => byte.toString(16).padStart(2, '0')) - .join('') - - return { - encryptedData, - iv: ivHex, - } -} - -export const decrypt = async ( - encryptedData: string, - ivHex: string -): Promise => { - if (ivHex.length !== 24) return decryptV1(encryptedData, ivHex) - if (!secretKey) throw new Error('ENCRYPTION_SECRET is not in environment') - const iv = new Uint8Array( - ivHex.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) ?? [] - ) - - const key = await crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(secretKey), - algorithm, - false, - ['decrypt'] - ) - - const encryptedBuffer = new Uint8Array( - Array.from(atob(encryptedData)).map((char) => char.charCodeAt(0)) - ) - - const decryptedBuffer = await crypto.subtle.decrypt( - { name: algorithm, iv }, - key, - encryptedBuffer - ) - - const decryptedData = new TextDecoder().decode(decryptedBuffer) - return JSON.parse(decryptedData) -} - -export const isCredentialsV2 = (credentials: Pick) => - credentials.iv.length === 24 diff --git a/packages/lib/api/encryption/decrypt.ts b/packages/lib/api/encryption/decrypt.ts new file mode 100644 index 00000000000..0a0871cab62 --- /dev/null +++ b/packages/lib/api/encryption/decrypt.ts @@ -0,0 +1,10 @@ +import { decryptV1 } from './decryptV1' +import { decryptV2 } from './decryptV2' + +export const decrypt = async ( + encryptedData: string, + ivHex: string +): Promise => { + if (ivHex.length !== 24) return decryptV1(encryptedData, ivHex) + return decryptV2(encryptedData, ivHex) +} diff --git a/packages/lib/api/encryptionV1.ts b/packages/lib/api/encryption/decryptV1.ts similarity index 100% rename from packages/lib/api/encryptionV1.ts rename to packages/lib/api/encryption/decryptV1.ts diff --git a/packages/lib/api/encryption/decryptV2.ts b/packages/lib/api/encryption/decryptV2.ts new file mode 100644 index 00000000000..b2c368eaf7a --- /dev/null +++ b/packages/lib/api/encryption/decryptV2.ts @@ -0,0 +1,35 @@ +import { env } from '@typebot.io/env' + +const algorithm = 'AES-GCM' +const secretKey = env.ENCRYPTION_SECRET + +export const decryptV2 = async ( + encryptedData: string, + ivHex: string +): Promise => { + if (!secretKey) throw new Error('ENCRYPTION_SECRET is not in environment') + const iv = new Uint8Array( + ivHex.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) ?? [] + ) + + const key = await crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(secretKey), + algorithm, + false, + ['decrypt'] + ) + + const encryptedBuffer = new Uint8Array( + Array.from(atob(encryptedData)).map((char) => char.charCodeAt(0)) + ) + + const decryptedBuffer = await crypto.subtle.decrypt( + { name: algorithm, iv }, + key, + encryptedBuffer + ) + + const decryptedData = new TextDecoder().decode(decryptedBuffer) + return JSON.parse(decryptedData) +} diff --git a/packages/lib/api/encryption/encrypt.ts b/packages/lib/api/encryption/encrypt.ts new file mode 100644 index 00000000000..60f15943509 --- /dev/null +++ b/packages/lib/api/encryption/encrypt.ts @@ -0,0 +1,39 @@ +import { env } from '@typebot.io/env' + +const algorithm = 'AES-GCM' +const secretKey = env.ENCRYPTION_SECRET + +export const encrypt = async ( + data: object +): Promise<{ encryptedData: string; iv: string }> => { + if (!secretKey) throw new Error('ENCRYPTION_SECRET is not in environment') + const iv = crypto.getRandomValues(new Uint8Array(12)) + const encodedData = new TextEncoder().encode(JSON.stringify(data)) + + const key = await crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(secretKey), + algorithm, + false, + ['encrypt'] + ) + + const encryptedBuffer = await crypto.subtle.encrypt( + { name: algorithm, iv }, + key, + encodedData + ) + + const encryptedData = btoa( + String.fromCharCode.apply(null, Array.from(new Uint8Array(encryptedBuffer))) + ) + + const ivHex = Array.from(iv) + .map((byte) => byte.toString(16).padStart(2, '0')) + .join('') + + return { + encryptedData, + iv: ivHex, + } +} diff --git a/packages/lib/api/index.ts b/packages/lib/api/index.ts index 2230c46edb7..9c56149efa5 100644 --- a/packages/lib/api/index.ts +++ b/packages/lib/api/index.ts @@ -1,2 +1 @@ export * from './utils' -export * from './encryption' diff --git a/packages/lib/playwright/databaseSetup.ts b/packages/lib/playwright/databaseSetup.ts index dcecc5cc85b..03f7826b9c4 100644 --- a/packages/lib/playwright/databaseSetup.ts +++ b/packages/lib/playwright/databaseSetup.ts @@ -4,7 +4,7 @@ import { PrismaClient, WorkspaceRole, } from '@typebot.io/prisma' -import { encrypt } from '../api' +import { encrypt } from '../api/encryption/encrypt' const prisma = new PrismaClient()