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

@@ -23,6 +23,7 @@ export const uploadFiles = async ({
i += 1
const { data } = await sendRequest<{
presignedUrl: string
formData: Record<string, string>
hasReachedStorageLimit: boolean
}>(
`${basePath}/storage/upload-url?filePath=${encodeURIComponent(
@@ -35,9 +36,14 @@ export const uploadFiles = async ({
const url = data.presignedUrl
if (data.hasReachedStorageLimit) urls.push(null)
else {
const upload = await fetch(url, {
method: 'PUT',
body: file,
const formData = new FormData()
Object.entries(data.formData).forEach(([key, value]) => {
formData.append(key, value)
})
formData.append('file', file)
const upload = await fetch(data.presignedUrl, {
method: 'POST',
body: formData,
})
if (!upload.ok) continue

View File

@@ -7,6 +7,7 @@ import { Button } from '@/components/Button'
import { Spinner } from '@/components/Spinner'
import { uploadFiles } from '../helpers/uploadFiles'
import { guessApiHost } from '@/utils/guessApiHost'
import { getRuntimeVariable } from '@typebot.io/env/getRuntimeVariable'
type Props = {
context: BotContext
@@ -25,15 +26,14 @@ export const FileUploadForm = (props: Props) => {
const onNewFiles = (files: FileList) => {
setErrorMessage(undefined)
const newFiles = Array.from(files)
const sizeLimit =
props.block.options.sizeLimit ??
getRuntimeVariable('NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE')
if (
newFiles.some(
(file) =>
file.size > (props.block.options.sizeLimit ?? 10) * 1024 * 1024
)
sizeLimit &&
newFiles.some((file) => file.size > sizeLimit * 1024 * 1024)
)
return setErrorMessage(
`A file is larger than ${props.block.options.sizeLimit ?? 10}MB`
)
return setErrorMessage(`A file is larger than ${sizeLimit}MB`)
if (!props.block.options.isMultipleAllowed && files)
return startSingleFileUpload(newFiles[0])
setSelectedFiles([...selectedFiles(), ...newFiles])

View File

@@ -28,6 +28,7 @@ export const uploadFiles = async ({
i += 1
const { data } = await sendRequest<{
presignedUrl: string
formData: Record<string, string>
fileUrl: string
}>({
method: 'POST',
@@ -40,9 +41,14 @@ export const uploadFiles = async ({
if (!data?.presignedUrl) continue
else {
const formData = new FormData()
Object.entries(data.formData).forEach(([key, value]) => {
formData.append(key, value)
})
formData.append('file', file)
const upload = await fetch(data.presignedUrl, {
method: 'PUT',
body: file,
method: 'POST',
body: formData,
})
if (!upload.ok) continue

4
packages/env/env.ts vendored
View File

@@ -35,6 +35,7 @@ const baseEnv = {
.transform((string) => string.split(',')),
NEXT_PUBLIC_VIEWER_INTERNAL_URL: z.string().url().optional(),
NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID: z.string().min(1).optional(),
NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE: z.coerce.number().optional(),
},
runtimeEnv: {
NEXT_PUBLIC_E2E_TEST: getRuntimeVariable('NEXT_PUBLIC_E2E_TEST'),
@@ -45,6 +46,9 @@ const baseEnv = {
NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID: getRuntimeVariable(
'NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID'
),
NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE: getRuntimeVariable(
'NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE'
),
},
}
const githubEnv = {

View File

@@ -0,0 +1,40 @@
import { env } from '@typebot.io/env'
import { Client, PostPolicyResult } from 'minio'
type Props = {
filePath: string
fileType?: string
maxFileSize?: number
}
const tenMinutes = 10 * 60
export const generatePresignedPostPolicy = async ({
filePath,
fileType,
maxFileSize,
}: Props): Promise<PostPolicyResult> => {
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
throw new Error(
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
)
const minioClient = new Client({
endPoint: env.S3_ENDPOINT,
port: env.S3_PORT,
useSSL: env.S3_SSL,
accessKey: env.S3_ACCESS_KEY,
secretKey: env.S3_SECRET_KEY,
region: env.S3_REGION,
})
const postPolicy = minioClient.newPostPolicy()
if (maxFileSize)
postPolicy.setContentLengthRange(0, maxFileSize * 1024 * 1024)
postPolicy.setKey(filePath)
postPolicy.setBucket(env.S3_BUCKET)
postPolicy.setExpires(new Date(Date.now() + tenMinutes))
if (fileType) postPolicy.setContentType(fileType)
return minioClient.presignedPostPolicy(postPolicy)
}

View File

@@ -1,32 +0,0 @@
import { env } from '@typebot.io/env'
import { Client } from 'minio'
type GeneratePresignedUrlProps = {
filePath: string
fileType?: string
}
const tenMinutes = 10 * 60
export const generatePresignedUrl = async ({
filePath,
fileType,
}: GeneratePresignedUrlProps): Promise<string> => {
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
throw new Error(
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
)
const minioClient = new Client({
endPoint: env.S3_ENDPOINT,
port: env.S3_PORT,
useSSL: env.S3_SSL,
accessKey: env.S3_ACCESS_KEY,
secretKey: env.S3_SECRET_KEY,
region: env.S3_REGION,
})
return minioClient.presignedUrl('PUT', env.S3_BUCKET, filePath, tenMinutes, {
'Content-Type': fileType,
})
}

View File

@@ -12,7 +12,7 @@ export const fileInputOptionsSchema = optionBaseSchema.merge(
clear: z.string().optional(),
skip: z.string().optional(),
}),
sizeLimit: z.number().optional(),
sizeLimit: z.number().optional().describe('Deprecated'),
})
)