feat(editor): 🔒️ Add verification on backend for file input deployment
This commit is contained in:
@ -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}
|
||||
|
@ -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({
|
||||
...parseTypebotToPublicTypebot(newLocalTypebot),
|
||||
id: publishedTypebotId,
|
||||
})
|
||||
const { data, error } = await createPublishedTypebot(
|
||||
{
|
||||
...parseTypebotToPublicTypebot(newLocalTypebot),
|
||||
id: publishedTypebotId,
|
||||
},
|
||||
localTypebot.workspaceId
|
||||
)
|
||||
setIsPublishing(false)
|
||||
if (error)
|
||||
return showToast({ title: error.name, description: error.message })
|
||||
|
@ -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 },
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
|
Reference in New Issue
Block a user