2
0

🔥 Remove streamer Pages API endpoint

This commit is contained in:
Baptiste Arnaud
2023-10-06 16:34:10 +02:00
parent b232a9495e
commit bf1f657642
31 changed files with 145 additions and 231 deletions

View File

@ -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,

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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(),

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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') {

View File

@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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()

View File

@ -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"
]
}

View File

@ -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'

View File

@ -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'

View File

@ -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'>) =>
credentials.iv.length === 24

View File

@ -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']

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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<object> => {
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'>) =>
credentials.iv.length === 24

View File

@ -0,0 +1,10 @@
import { decryptV1 } from './decryptV1'
import { decryptV2 } from './decryptV2'
export const decrypt = async (
encryptedData: string,
ivHex: string
): Promise<object> => {
if (ivHex.length !== 24) return decryptV1(encryptedData, ivHex)
return decryptV2(encryptedData, ivHex)
}

View File

@ -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<object> => {
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)
}

View File

@ -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,
}
}

View File

@ -1,2 +1 @@
export * from './utils'
export * from './encryption'

View File

@ -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()