Skip to content

Commit

Permalink
🔒 Improve workspace API role filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Aug 17, 2023
1 parent 8810aa8 commit 906845b
Show file tree
Hide file tree
Showing 19 changed files with 288 additions and 138 deletions.
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import prisma from '@/lib/prisma'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
import { Typebot, typebotSchema } from '@typebot.io/schemas'
import { LogicBlockType, typebotSchema } from '@typebot.io/schemas'
import { z } from 'zod'
import { getUserRoleInWorkspace } from '@/features/workspace/helpers/getUserRoleInWorkspace'
import { isReadTypebotForbidden } from '@/features/typebot/helpers/isReadTypebotForbidden'
import { isDefined } from '@typebot.io/lib'

export const getLinkedTypebots = authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/linkedTypebots',
path: '/typebots/{typebotId}/linkedTypebots',
protect: true,
summary: 'Get linked typebots',
tags: ['Typebot'],
},
})
.input(
z.object({
workspaceId: z.string(),
typebotIds: z.string().describe('Comma separated list of typebot ids'),
typebotId: z.string(),
})
)
.output(
Expand All @@ -33,39 +33,80 @@ export const getLinkedTypebots = authenticatedProcedure
),
})
)
.query(async ({ input: { workspaceId, typebotIds }, ctx: { user } }) => {
const typebotIdsArray = typebotIds.split(',')
const workspace = await prisma.workspace.findUnique({
where: { id: workspaceId },
select: { members: true },
})
const userRole = getUserRoleInWorkspace(user.id, workspace?.members)
if (userRole === undefined)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
const typebots = (await prisma.typebot.findMany({
.query(async ({ input: { typebotId }, ctx: { user } }) => {
const typebot = await prisma.typebot.findFirst({
where: {
isArchived: { not: true },
id: { in: typebotIdsArray },
workspaceId,
id: typebotId,
},
select: {
id: true,
groups: true,
variables: true,
name: true,
createdAt: true,
workspaceId: true,
collaborators: {
select: {
type: true,
userId: true,
},
},
},
})) as Pick<Typebot, 'id' | 'groups' | 'variables' | 'name' | 'createdAt'>[]

// To avoid the Out of sort memory error, we sort the typebots manually
const sortedTypebots = typebots.sort((a, b) => {
return b.createdAt.getTime() - a.createdAt.getTime()
})

if (!typebots)
throw new TRPCError({ code: 'NOT_FOUND', message: 'No typebots found' })
if (!typebot || (await isReadTypebotForbidden(typebot, user)))
throw new TRPCError({ code: 'NOT_FOUND', message: 'No typebot found' })

const linkedTypebotIds =
typebotSchema.shape.groups
.parse(typebot.groups)
.flatMap((group) => group.blocks)
.reduce<string[]>(
(typebotIds, block) =>
block.type === LogicBlockType.TYPEBOT_LINK &&
isDefined(block.options.typebotId) &&
!typebotIds.includes(block.options.typebotId)
? [...typebotIds, block.options.typebotId]
: typebotIds,
[]
) ?? []

if (!linkedTypebotIds.length) return { typebots: [] }

const typebots = (
await prisma.typebot.findMany({
where: {
isArchived: { not: true },
id: { in: linkedTypebotIds },
},
select: {
id: true,
groups: true,
variables: true,
name: true,
createdAt: true,
workspaceId: true,
collaborators: {
select: {
type: true,
userId: true,
},
},
},
})
)
.filter(async (typebot) => !(await isReadTypebotForbidden(typebot, user)))
// To avoid the out of sort memory error, we sort the typebots manually
.sort((a, b) => {
return b.createdAt.getTime() - a.createdAt.getTime()
})
.map((typebot) => ({
...typebot,
groups: typebotSchema.shape.groups.parse(typebot.groups),
variables: typebotSchema.shape.variables.parse(typebot.variables),
}))

return {
typebots: sortedTypebots,
typebots,
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,30 @@ import { TypebotLinkOptions } from '@typebot.io/schemas'
import { byId } from '@typebot.io/lib'
import { GroupsDropdown } from './GroupsDropdown'
import { TypebotsDropdown } from './TypebotsDropdown'
import { useEffect, useState } from 'react'

type Props = {
options: TypebotLinkOptions
onOptionsChange: (options: TypebotLinkOptions) => void
}

export const TypebotLinkForm = ({ options, onOptionsChange }: Props) => {
const { linkedTypebots, typebot } = useTypebot()
const { linkedTypebots, typebot, save } = useTypebot()
const [linkedTypebotId, setLinkedTypebotId] = useState(options.typebotId)

const handleTypebotIdChange = async (
typebotId: string | 'current' | undefined
) => onOptionsChange({ ...options, typebotId })

const handleTypebotIdChange = (typebotId: string | 'current' | undefined) =>
onOptionsChange({ ...options, typebotId })
const handleGroupIdChange = (groupId: string | undefined) =>
onOptionsChange({ ...options, groupId })

useEffect(() => {
if (linkedTypebotId === options.typebotId) return
setLinkedTypebotId(options.typebotId)
save().then()
}, [linkedTypebotId, options.typebotId, save])

return (
<Stack>
{typebot && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { openAICredentialsSchema } from '@typebot.io/schemas/features/blocks/int
import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
import { encrypt } from '@typebot.io/lib/api/encryption'
import { z } from 'zod'
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden copy'

const inputShape = {
data: true,
Expand Down Expand Up @@ -44,11 +45,10 @@ export const createCredentials = authenticatedProcedure
const workspace = await prisma.workspace.findFirst({
where: {
id: credentials.workspaceId,
members: { some: { userId: user.id } },
},
select: { id: true },
select: { id: true, members: true },
})
if (!workspace)
if (!workspace || (await isWriteWorkspaceForbidden(workspace, user)))
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })

const { encryptedData, iv } = await encrypt(credentials.data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import prisma from '@/lib/prisma'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
import { z } from 'zod'
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden copy'

export const deleteCredentials = authenticatedProcedure
.meta({
Expand Down Expand Up @@ -29,11 +30,10 @@ export const deleteCredentials = authenticatedProcedure
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
members: { some: { userId: user.id } },
},
select: { id: true },
select: { id: true, members: true },
})
if (!workspace)
if (!workspace || (await isWriteWorkspaceForbidden(workspace, user)))
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Workspace not found',
Expand Down
27 changes: 15 additions & 12 deletions apps/builder/src/features/credentials/api/listCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { googleSheetsCredentialsSchema } from '@typebot.io/schemas/features/bloc
import { openAICredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/openai'
import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
import { z } from 'zod'
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'

export const listCredentials = authenticatedProcedure
.meta({
Expand Down Expand Up @@ -35,21 +36,23 @@ export const listCredentials = authenticatedProcedure
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
members: { some: { userId: user.id } },
},
select: { id: true },
})
if (!workspace)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
const credentials = await prisma.credentials.findMany({
where: {
type,
workspaceId,
},
select: {
id: true,
name: true,
members: true,
credentials: {
where: {
type,
},
select: {
id: true,
name: true,
},
},
},
})
return { credentials }
if (!workspace || (await isReadWorkspaceFobidden(workspace, user)))
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })

return { credentials: workspace.credentials }
})
33 changes: 17 additions & 16 deletions apps/builder/src/features/editor/providers/TypebotProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,27 +131,28 @@ export const TypebotProvider = ({
{ redo, undo, flush, canRedo, canUndo, set: setLocalTypebot },
] = useUndo<Typebot>(undefined)

const linkedTypebotIds =
localTypebot?.groups
.flatMap((b) => b.blocks)
.reduce<string[]>(
(typebotIds, block) =>
block.type === LogicBlockType.TYPEBOT_LINK &&
isDefined(block.options.typebotId) &&
!typebotIds.includes(block.options.typebotId)
? [...typebotIds, block.options.typebotId]
: typebotIds,
[]
) ?? []
const linkedTypebotIds = useMemo(
() =>
typebot?.groups
.flatMap((group) => group.blocks)
.reduce<string[]>(
(typebotIds, block) =>
block.type === LogicBlockType.TYPEBOT_LINK &&
isDefined(block.options.typebotId) &&
!typebotIds.includes(block.options.typebotId)
? [...typebotIds, block.options.typebotId]
: typebotIds,
[]
) ?? [],
[typebot?.groups]
)

const { data: linkedTypebotsData } = trpc.getLinkedTypebots.useQuery(
{
workspaceId: localTypebot?.workspaceId as string,
typebotIds: linkedTypebotIds.join(','),
typebotId: typebot?.id as string,
},
{
enabled:
isDefined(localTypebot?.workspaceId) && linkedTypebotIds.length > 0,
enabled: isDefined(typebot?.id) && linkedTypebotIds.length > 0,
onError: (error) =>
showToast({
title: 'Error while fetching linkedTypebots',
Expand Down
17 changes: 16 additions & 1 deletion apps/builder/src/features/typebot/api/createTypebot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
sanitizeSettings,
} from '../helpers/sanitizers'
import { createId } from '@paralleldrive/cuid2'
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'

export const createTypebot = authenticatedProcedure
.meta({
Expand Down Expand Up @@ -106,7 +107,21 @@ export const createTypebot = authenticatedProcedure
},
})

return { typebot: typebotSchema.parse(newTypebot) }
const parsedNewTypebot = typebotSchema.parse(newTypebot)

await sendTelemetryEvents([
{
name: 'Typebot created',
workspaceId: parsedNewTypebot.workspaceId,
typebotId: parsedNewTypebot.id,
userId: user.id,
data: {
name: newTypebot.name,
},
},
])

return { typebot: parsedNewTypebot }
})

const defaultGroups = () => {
Expand Down
42 changes: 28 additions & 14 deletions apps/builder/src/features/typebot/api/publishTypebot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TRPCError } from '@trpc/server'
import { typebotSchema } from '@typebot.io/schemas'
import { z } from 'zod'
import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden'
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'

export const publishTypebot = authenticatedProcedure
.meta({
Expand Down Expand Up @@ -41,7 +42,7 @@ export const publishTypebot = authenticatedProcedure
)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })

if (existingTypebot.publishedTypebot) {
if (existingTypebot.publishedTypebot)
await prisma.publicTypebot.updateMany({
where: {
id: existingTypebot.publishedTypebot.id,
Expand All @@ -59,22 +60,35 @@ export const publishTypebot = authenticatedProcedure
theme: typebotSchema.shape.theme.parse(existingTypebot.theme),
},
})
return { message: 'success' }
}
else
await prisma.publicTypebot.createMany({
data: {
version: existingTypebot.version,
typebotId: existingTypebot.id,
edges: typebotSchema.shape.edges.parse(existingTypebot.edges),
groups: typebotSchema.shape.groups.parse(existingTypebot.groups),
settings: typebotSchema.shape.settings.parse(
existingTypebot.settings
),
variables: typebotSchema.shape.variables.parse(
existingTypebot.variables
),
theme: typebotSchema.shape.theme.parse(existingTypebot.theme),
},
})

await prisma.publicTypebot.createMany({
data: {
version: existingTypebot.version,
await sendTelemetryEvents([
{
name: 'Typebot published',
workspaceId: existingTypebot.workspaceId,
typebotId: existingTypebot.id,
edges: typebotSchema.shape.edges.parse(existingTypebot.edges),
groups: typebotSchema.shape.groups.parse(existingTypebot.groups),
settings: typebotSchema.shape.settings.parse(existingTypebot.settings),
variables: typebotSchema.shape.variables.parse(
existingTypebot.variables
),
theme: typebotSchema.shape.theme.parse(existingTypebot.theme),
userId: user.id,
data: {
name: existingTypebot.name,
isFirstPublish: existingTypebot.publishedTypebot ? undefined : true,
},
},
})
])

return { message: 'success' }
})
Loading

4 comments on commit 906845b

@vercel
Copy link

@vercel vercel bot commented on 906845b Aug 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

kopibayane.com
panther.cr7.ai
panther.cr8.ai
pay.sifuim.com
penguin.cr8.ai
segredomeu.com
talk.gocare.io
test.bot.gives
ticketfute.com
unicorn.cr8.ai
whats-app.chat
app.blogely.com
apr.nigerias.io
zap.techadviser.in
ai.digitaldaftar.in
app.danielnalex.com
ask.realversity.org
bot.boston-voip.com
bot.cabinpromos.com
bot.carnaval.studio
bot.digitalbled.com
bot.dsignagency.com
bot.enthrallart.com
bot.eventhub.com.au
bot.gravityatoms.in
bot.jepierre.com.br
bot.leadgenpod.site
bot.ltmidias.com.br
bot.viralsangat.com
bot.winglabs.com.br
capitaldigital.live
carsalesenquiry.com
casahackeada.online
chat.marius.digital
chat.sr7digital.com
chatbot.matthesv.de
chatbot.repplai.com
chatwebandreia.site
co.onewebcenter.com
demo.botscientis.us
demo.wemakebots.xyz
feiralimpanomes.com
go.onewebcenter.com
hrbot.robomotion.io
inearephones.cr8.ai
kbsub.wpwakanda.com
limitenahora.com.br
live.botscientis.us
mentoria.omelhor.vc
messengerbet.online
noticiariododia.com
nutrisamirbayde.com
online.onlinmey.com
order.maitempah.com
profileadscloud.com
query.forgetsql.com
quest.wpwakanda.com
se.onewebcenter.com
secretespiao.online
start.belenmotz.com
support.wawplus.com
survey1.digienge.io
viewer-v2-typebot-io.vercel.app
mdb.assessoria.jbatista.progenbr.com
mdb.assessoria.mauricio.progenbr.com
mdb.evento.autocadastro.progenbr.com
form.shopmercedesbenzsouthorlando.com
mdb.evento.equipeinterna.progenbr.com
bot.studiotecnicoimmobiliaremerelli.it
mdb.assessoria.boaventura.progenbr.com
mdb.assessoria.jtrebesqui.progenbr.com
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
gabinete.baleia.formulario.progenbr.com
mdb.assessoria.carreirinha.progenbr.com
chrome-os-inquiry-system.itschromeos.com
mdb.assessoria.paulomarques.progenbr.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com
mdb.assessoria.qrcode.ademir.progenbr.com
mdb.assessoria.qrcode.arthur.progenbr.com
mdb.assessoria.qrcode.danilo.progenbr.com
mdb.assessoria.qrcode.marcao.progenbr.com
mdb.assessoria.qrcode.marcio.progenbr.com
mdb.assessoria.qrcode.aloisio.progenbr.com
mdb.assessoria.qrcode.girotto.progenbr.com
mdb.assessoria.qrcode.marinho.progenbr.com
mdb.assessoria.qrcode.rodrigo.progenbr.com
mdb.assessoria.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.desideri.progenbr.com
mdb.assessoria.qrcode.fernanda.progenbr.com
mdb.assessoria.qrcode.jbatista.progenbr.com
mdb.assessoria.qrcode.mauricio.progenbr.com
mdb.assessoria.fernanda.regional.progenbr.com
mdb.assessoria.qrcode.boaventura.progenbr.com
mdb.assessoria.qrcode.jtrebesqui.progenbr.com
mdb.assessoria.qrcode.carreirinha.progenbr.com
mdb.assessoria.qrcode.paulomarques.progenbr.com
mdb.assessoria.qrcode.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.fernanda.regional.progenbr.com

@vercel
Copy link

@vercel vercel bot commented on 906845b Aug 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-typebot-io.vercel.app
app.typebot.io
builder-v2-git-main-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 906845b Aug 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 906845b Aug 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./apps/docs

docs-typebot-io.vercel.app
docs-git-main-typebot-io.vercel.app
docs.typebot.io

Please sign in to comment.