🔒 Improve workspace API role filtering

This commit is contained in:
Baptiste Arnaud
2023-08-17 15:11:50 +02:00
parent 8810aa8ddb
commit 906845bd76
19 changed files with 288 additions and 138 deletions

View File

@@ -1,15 +1,16 @@
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'],
@@ -17,8 +18,7 @@ export const getLinkedTypebots = authenticatedProcedure
})
.input(
z.object({
workspaceId: z.string(),
typebotIds: z.string().describe('Comma separated list of typebot ids'),
typebotId: z.string(),
})
)
.output(
@@ -33,20 +33,10 @@ 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,
@@ -54,18 +44,69 @@ export const getLinkedTypebots = authenticatedProcedure
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,
}
})

View File

@@ -4,6 +4,7 @@ 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
@@ -11,13 +12,22 @@ type Props = {
}
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 && (