⚡ (openai) Stream chat completion to avoid serverless timeout (#526)
Closes #520
This commit is contained in:
@ -1,36 +1,75 @@
|
||||
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto'
|
||||
import { Credentials } from '@typebot.io/schemas/features/credentials'
|
||||
import { decryptV1 } from './encryptionV1'
|
||||
|
||||
const algorithm = 'aes-256-gcm'
|
||||
const algorithm = 'AES-GCM'
|
||||
const secretKey = process.env.ENCRYPTION_SECRET
|
||||
|
||||
export const encrypt = (
|
||||
export const encrypt = async (
|
||||
data: object
|
||||
): { encryptedData: string; iv: string } => {
|
||||
if (!secretKey) throw new Error(`ENCRYPTION_SECRET is not in environment`)
|
||||
const iv = randomBytes(16)
|
||||
const cipher = createCipheriv(algorithm, secretKey, iv)
|
||||
const dataString = JSON.stringify(data)
|
||||
const encryptedData =
|
||||
cipher.update(dataString, 'utf8', 'hex') + cipher.final('hex')
|
||||
const tag = cipher.getAuthTag()
|
||||
): 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: iv.toString('hex') + '.' + tag.toString('hex'),
|
||||
iv: ivHex,
|
||||
}
|
||||
}
|
||||
|
||||
export const decrypt = (encryptedData: string, auth: string): object => {
|
||||
if (!secretKey) throw new Error(`ENCRYPTION_SECRET is not in environment`)
|
||||
const [iv, tag] = auth.split('.')
|
||||
const decipher = createDecipheriv(
|
||||
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,
|
||||
secretKey,
|
||||
Buffer.from(iv, 'hex')
|
||||
false,
|
||||
['decrypt']
|
||||
)
|
||||
decipher.setAuthTag(Buffer.from(tag, 'hex'))
|
||||
return JSON.parse(
|
||||
(
|
||||
decipher.update(Buffer.from(encryptedData, 'hex')) + decipher.final('hex')
|
||||
).toString()
|
||||
|
||||
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
|
||||
|
20
packages/lib/api/encryptionV1.ts
Normal file
20
packages/lib/api/encryptionV1.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { createDecipheriv } from 'crypto'
|
||||
|
||||
const algorithm = 'aes-256-gcm'
|
||||
const secretKey = process.env.ENCRYPTION_SECRET
|
||||
|
||||
export const decryptV1 = (encryptedData: string, auth: string): object => {
|
||||
if (!secretKey) throw new Error(`ENCRYPTION_SECRET is not in environment`)
|
||||
const [iv, tag] = auth.split('.')
|
||||
const decipher = createDecipheriv(
|
||||
algorithm,
|
||||
secretKey,
|
||||
Buffer.from(iv, 'hex')
|
||||
)
|
||||
decipher.setAuthTag(Buffer.from(tag, 'hex'))
|
||||
return JSON.parse(
|
||||
(
|
||||
decipher.update(Buffer.from(encryptedData, 'hex')) + decipher.final('hex')
|
||||
).toString()
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user