2
0

🛂 (fileUpload) Improve file upload size limit enforcement

Closes #799, closes #797
This commit is contained in:
Baptiste Arnaud
2023-09-19 15:42:33 +02:00
parent f626c9867c
commit bb13c2bd61
19 changed files with 143 additions and 239 deletions

View File

@@ -10,7 +10,7 @@ import {
} from '@typebot.io/schemas'
import { byId, isDefined } from '@typebot.io/lib'
import { z } from 'zod'
import { generatePresignedUrl } from '@typebot.io/lib/s3/generatePresignedUrl'
import { generatePresignedPostPolicy } from '@typebot.io/lib/s3/generatePresignedPostPolicy'
import { env } from '@typebot.io/env'
export const getUploadUrl = publicProcedure
@@ -34,6 +34,7 @@ export const getUploadUrl = publicProcedure
.output(
z.object({
presignedUrl: z.string(),
formData: z.record(z.string(), z.any()),
hasReachedStorageLimit: z.boolean(),
})
)
@@ -61,13 +62,15 @@ export const getUploadUrl = publicProcedure
message: 'File upload block not found',
})
const presignedUrl = await generatePresignedUrl({
const presignedPostPolicy = await generatePresignedPostPolicy({
fileType,
filePath,
maxFileSize: env.NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE,
})
return {
presignedUrl,
presignedUrl: `${presignedPostPolicy.postURL}/${presignedPostPolicy.formData.key}`,
formData: presignedPostPolicy.formData,
hasReachedStorageLimit: false,
}
})

View File

@@ -2,8 +2,9 @@ import { publicProcedure } from '@/helpers/server/trpc'
import prisma from '@/lib/prisma'
import { TRPCError } from '@trpc/server'
import { z } from 'zod'
import { generatePresignedUrl } from '@typebot.io/lib/s3/generatePresignedUrl'
import { generatePresignedPostPolicy } from '@typebot.io/lib/s3/generatePresignedPostPolicy'
import { env } from '@typebot.io/env'
import { InputBlockType, publicTypebotSchema } from '@typebot.io/schemas'
export const generateUploadUrl = publicProcedure
.meta({
@@ -28,6 +29,7 @@ export const generateUploadUrl = publicProcedure
.output(
z.object({
presignedUrl: z.string(),
formData: z.record(z.string(), z.any()),
fileUrl: z.string(),
})
)
@@ -44,6 +46,7 @@ export const generateUploadUrl = publicProcedure
typebotId: filePathProps.typebotId,
},
select: {
groups: true,
typebot: {
select: {
workspaceId: true,
@@ -62,15 +65,30 @@ export const generateUploadUrl = publicProcedure
const filePath = `public/workspaces/${workspaceId}/typebots/${filePathProps.typebotId}/results/${filePathProps.resultId}/${filePathProps.fileName}`
const presignedUrl = await generatePresignedUrl({
const fileUploadBlock = publicTypebotSchema._def.schema.shape.groups
.parse(publicTypebot.groups)
.flatMap((group) => group.blocks)
.find((block) => block.id === filePathProps.blockId)
if (fileUploadBlock?.type !== InputBlockType.FILE)
throw new TRPCError({
code: 'BAD_REQUEST',
message: "Can't find file upload block",
})
const presignedPostPolicy = await generatePresignedPostPolicy({
fileType,
filePath,
maxFileSize:
fileUploadBlock.options.sizeLimit ??
env.NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE,
})
return {
presignedUrl,
presignedUrl: presignedPostPolicy.postURL,
formData: presignedPostPolicy.formData,
fileUrl: env.S3_PUBLIC_CUSTOM_DOMAIN
? `${env.S3_PUBLIC_CUSTOM_DOMAIN}/${filePath}`
: presignedUrl.split('?')[0],
: `${presignedPostPolicy.postURL}/${presignedPostPolicy.formData.key}`,
}
})

View File

@@ -93,25 +93,4 @@ test.describe('Storage limit is reached', () => {
fakeStorage: THREE_GIGABYTES,
})
})
test("shouldn't upload anything if limit has been reached", async ({
page,
}) => {
await page.goto(`/${typebotId}-public`)
await page
.locator(`input[type="file"]`)
.setInputFiles([
getTestAsset('typebots/api.json'),
getTestAsset('typebots/fileUpload.json'),
getTestAsset('typebots/hugeGroup.json'),
])
await expect(page.locator(`text="3"`)).toBeVisible()
await page.locator('text="Upload 3 files"').click()
await expect(page.locator(`text="3 files uploaded"`)).toBeVisible()
await page.evaluate(() =>
window.localStorage.setItem('workspaceId', 'starterWorkspace')
)
await page.goto(`${env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
await expect(page.locator('text="150%"')).toBeVisible()
})
})

View File

@@ -1,8 +1,6 @@
import prisma from '@/lib/prisma'
import { isNotDefined } from '@typebot.io/lib'
import { Prisma } from '@typebot.io/prisma'
import { InputBlock, InputBlockType, SessionState } from '@typebot.io/schemas'
import got from 'got'
import { InputBlock, SessionState } from '@typebot.io/schemas'
type Props = {
answer: Omit<Prisma.AnswerUncheckedCreateInput, 'resultId'>
@@ -11,12 +9,9 @@ type Props = {
itemId?: string
state: SessionState
}
export const upsertAnswer = async ({ answer, reply, block, state }: Props) => {
export const upsertAnswer = async ({ answer, block, state }: Props) => {
const resultId = state.typebotsQueue[0].resultId
if (!resultId) return
if (reply.includes('http') && block.type === InputBlockType.FILE) {
answer.storageUsed = await computeStorageUsed(reply)
}
const where = {
resultId,
blockId: block.id,
@@ -33,7 +28,6 @@ export const upsertAnswer = async ({ answer, reply, block, state }: Props) => {
where,
data: {
content: answer.content,
storageUsed: answer.storageUsed,
itemId: answer.itemId,
},
})
@@ -41,18 +35,3 @@ export const upsertAnswer = async ({ answer, reply, block, state }: Props) => {
data: [{ ...answer, resultId }],
})
}
const computeStorageUsed = async (reply: string) => {
let storageUsed = 0
const fileUrls = reply.split(', ')
const hasReachedStorageLimit = fileUrls[0] === null
if (!hasReachedStorageLimit) {
for (const url of fileUrls) {
const { headers } = await got(url)
const size = headers['content-length']
if (isNotDefined(size)) continue
storageUsed += parseInt(size, 10)
}
}
return storageUsed
}