2
0

feat(editor): 🔒️ Add verification on backend for file input deployment

This commit is contained in:
Baptiste Arnaud
2022-06-13 08:21:48 +02:00
parent 910b871556
commit 14afd2249e
7 changed files with 112 additions and 17 deletions

View File

@ -34,6 +34,7 @@ export const PublishButton = () => {
publishedTypebot,
restorePublishedTypebot,
typebot,
isSavingLoading,
} = useTypebot()
const hasInputFile = typebot?.groups
@ -73,7 +74,7 @@ export const PublishButton = () => {
>
<Button
colorScheme="blue"
isLoading={isPublishing}
isLoading={isPublishing || isSavingLoading}
isDisabled={isPublished}
onClick={handlePublishClick}
borderRightRadius={publishedTypebot && !isPublished ? 0 : undefined}

View File

@ -187,10 +187,12 @@ export const TypebotContext = ({
}
const savePublishedTypebot = async (newPublishedTypebot: PublicTypebot) => {
if (!localTypebot) return
setIsPublishing(true)
const { error } = await updatePublishedTypebot(
newPublishedTypebot.id,
newPublishedTypebot
newPublishedTypebot,
localTypebot.workspaceId
)
setIsPublishing(false)
if (error)
@ -303,10 +305,13 @@ export const TypebotContext = ({
})
} else {
setIsPublishing(true)
const { data, error } = await createPublishedTypebot({
const { data, error } = await createPublishedTypebot(
{
...parseTypebotToPublicTypebot(newLocalTypebot),
id: publishedTypebotId,
})
},
localTypebot.workspaceId
)
setIsPublishing(false)
if (error)
return showToast({ title: error.name, description: error.message })

View File

@ -1,16 +1,29 @@
import { withSentry } from '@sentry/nextjs'
import prisma from 'libs/prisma'
import { InputBlockType, PublicTypebot } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { canPublishFileInput } from 'services/api/dbRules'
import { getAuthenticatedUser } from 'services/api/utils'
import { methodNotAllowed, notAuthenticated } from 'utils'
import { badRequest, methodNotAllowed, notAuthenticated } from 'utils'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const user = await getAuthenticatedUser(req)
if (!user) return notAuthenticated(res)
try {
if (req.method === 'POST') {
const data =
const workspaceId = req.query.workspaceId as string | undefined
if (!workspaceId) return badRequest(res, 'workspaceId is required')
const data = (
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
) as PublicTypebot
const typebotContainsFileInput = data.groups
.flatMap((g) => g.blocks)
.some((b) => b.type === InputBlockType.FILE)
if (
typebotContainsFileInput &&
!(await canPublishFileInput({ userId: user.id, workspaceId, res }))
)
return
const typebot = await prisma.publicTypebot.create({
data: { ...data },
})

View File

@ -1,16 +1,31 @@
import { withSentry } from '@sentry/nextjs'
import prisma from 'libs/prisma'
import { InputBlockType, PublicTypebot } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { canPublishFileInput } from 'services/api/dbRules'
import { getAuthenticatedUser } from 'services/api/utils'
import { methodNotAllowed, notAuthenticated } from 'utils'
import { badRequest, methodNotAllowed, notAuthenticated } from 'utils'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const user = await getAuthenticatedUser(req)
if (!user) return notAuthenticated(res)
const id = req.query.id.toString()
const id = req.query.id as string
const workspaceId = req.query.workspaceId as string | undefined
if (req.method === 'PUT') {
const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
const data = (
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
) as PublicTypebot
if (!workspaceId) return badRequest(res, 'workspaceId is required')
const typebotContainsFileInput = data.groups
.flatMap((g) => g.blocks)
.some((b) => b.type === InputBlockType.FILE)
if (
typebotContainsFileInput &&
!(await canPublishFileInput({ userId: user.id, workspaceId, res }))
)
return
const typebots = await prisma.publicTypebot.update({
where: { id },
data,

View File

@ -1,6 +1,7 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
freeWorkspaceId,
parseDefaultGroupWithBlock,
} from '../../services/database'
import { defaultFileInputOptions, InputBlockType } from 'models'
@ -8,6 +9,8 @@ import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import path from 'path'
test.describe.configure({ mode: 'parallel' })
test('options should work', async ({ page }) => {
const typebotId = cuid()
await createTypebots([
@ -49,3 +52,31 @@ test('options should work', async ({ page }) => {
typebotViewer(page).locator(`text="3 files uploaded"`)
).toBeVisible()
})
test.describe('Free workspace', () => {
test.use({
storageState: path.join(__dirname, '../../freeUser.json'),
})
test("shouldn't be able to publish typebot", async ({ page }) => {
const typebotId = cuid()
await createTypebots([
{
id: typebotId,
...parseDefaultGroupWithBlock({
type: InputBlockType.FILE,
options: defaultFileInputOptions,
}),
workspaceId: freeWorkspaceId,
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text="Collect file"')
await page.click('text="Allow multiple files?"')
await page.click('text="Publish"')
await expect(
page.locator(
'text="You need to upgrade your plan in order to use file input blocks"'
)
).toBeVisible()
})
})

View File

@ -1,5 +1,7 @@
import { CollaborationType, Prisma, User, WorkspaceRole } from 'db'
import { isNotEmpty } from 'utils'
import { CollaborationType, Plan, Prisma, User, WorkspaceRole } from 'db'
import prisma from 'libs/prisma'
import { NextApiResponse } from 'next'
import { forbidden, isNotEmpty } from 'utils'
const parseWhereFilter = (
typebotIds: string[] | string,
@ -51,3 +53,27 @@ export const canEditGuests = (user: User, typebotId: string) => ({
},
},
})
export const canPublishFileInput = async ({
userId,
workspaceId,
res,
}: {
userId: string
workspaceId: string
res: NextApiResponse
}) => {
const workspace = await prisma.workspace.findFirst({
where: { id: workspaceId, members: { some: { userId } } },
select: { plan: true },
})
if (!workspace) {
forbidden(res, 'workspace not found')
return false
}
if (workspace?.plan === Plan.FREE) {
forbidden(res, 'You need to upgrade your plan to use this feature')
return false
}
return true
}

View File

@ -37,19 +37,23 @@ export const parsePublicTypebotToTypebot = (
workspaceId: existingTypebot.workspaceId,
})
export const createPublishedTypebot = async (typebot: PublicTypebot) =>
export const createPublishedTypebot = async (
typebot: PublicTypebot,
workspaceId: string
) =>
sendRequest<PublicTypebot>({
url: `/api/publicTypebots`,
url: `/api/publicTypebots?workspaceId=${workspaceId}`,
method: 'POST',
body: typebot,
})
export const updatePublishedTypebot = async (
id: string,
typebot: Omit<PublicTypebot, 'id'>
typebot: Omit<PublicTypebot, 'id'>,
workspaceId: string
) =>
sendRequest({
url: `/api/publicTypebots/${id}`,
url: `/api/publicTypebots/${id}?workspaceId=${workspaceId}`,
method: 'PUT',
body: typebot,
})