🔒 Improve workspace API role filtering
This commit is contained in:
@ -1,15 +1,16 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { Typebot, typebotSchema } from '@typebot.io/schemas'
|
import { LogicBlockType, typebotSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
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
|
export const getLinkedTypebots = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/linkedTypebots',
|
path: '/typebots/{typebotId}/linkedTypebots',
|
||||||
protect: true,
|
protect: true,
|
||||||
summary: 'Get linked typebots',
|
summary: 'Get linked typebots',
|
||||||
tags: ['Typebot'],
|
tags: ['Typebot'],
|
||||||
@ -17,8 +18,7 @@ export const getLinkedTypebots = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
workspaceId: z.string(),
|
typebotId: z.string(),
|
||||||
typebotIds: z.string().describe('Comma separated list of typebot ids'),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
.output(
|
||||||
@ -33,20 +33,10 @@ export const getLinkedTypebots = authenticatedProcedure
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { workspaceId, typebotIds }, ctx: { user } }) => {
|
.query(async ({ input: { typebotId }, ctx: { user } }) => {
|
||||||
const typebotIdsArray = typebotIds.split(',')
|
const typebot = await prisma.typebot.findFirst({
|
||||||
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({
|
|
||||||
where: {
|
where: {
|
||||||
isArchived: { not: true },
|
id: typebotId,
|
||||||
id: { in: typebotIdsArray },
|
|
||||||
workspaceId,
|
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@ -54,18 +44,69 @@ export const getLinkedTypebots = authenticatedProcedure
|
|||||||
variables: true,
|
variables: true,
|
||||||
name: true,
|
name: true,
|
||||||
createdAt: 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
|
if (!typebot || (await isReadTypebotForbidden(typebot, user)))
|
||||||
const sortedTypebots = typebots.sort((a, b) => {
|
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()
|
return b.createdAt.getTime() - a.createdAt.getTime()
|
||||||
})
|
})
|
||||||
|
.map((typebot) => ({
|
||||||
if (!typebots)
|
...typebot,
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No typebots found' })
|
groups: typebotSchema.shape.groups.parse(typebot.groups),
|
||||||
|
variables: typebotSchema.shape.variables.parse(typebot.variables),
|
||||||
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
typebots: sortedTypebots,
|
typebots,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,7 @@ import { TypebotLinkOptions } from '@typebot.io/schemas'
|
|||||||
import { byId } from '@typebot.io/lib'
|
import { byId } from '@typebot.io/lib'
|
||||||
import { GroupsDropdown } from './GroupsDropdown'
|
import { GroupsDropdown } from './GroupsDropdown'
|
||||||
import { TypebotsDropdown } from './TypebotsDropdown'
|
import { TypebotsDropdown } from './TypebotsDropdown'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options: TypebotLinkOptions
|
options: TypebotLinkOptions
|
||||||
@ -11,13 +12,22 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TypebotLinkForm = ({ options, onOptionsChange }: 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) =>
|
const handleGroupIdChange = (groupId: string | undefined) =>
|
||||||
onOptionsChange({ ...options, groupId })
|
onOptionsChange({ ...options, groupId })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (linkedTypebotId === options.typebotId) return
|
||||||
|
setLinkedTypebotId(options.typebotId)
|
||||||
|
save().then()
|
||||||
|
}, [linkedTypebotId, options.typebotId, save])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
{typebot && (
|
{typebot && (
|
||||||
|
@ -7,6 +7,7 @@ import { openAICredentialsSchema } from '@typebot.io/schemas/features/blocks/int
|
|||||||
import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
|
import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
|
||||||
import { encrypt } from '@typebot.io/lib/api/encryption'
|
import { encrypt } from '@typebot.io/lib/api/encryption'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden copy'
|
||||||
|
|
||||||
const inputShape = {
|
const inputShape = {
|
||||||
data: true,
|
data: true,
|
||||||
@ -44,11 +45,10 @@ export const createCredentials = authenticatedProcedure
|
|||||||
const workspace = await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: credentials.workspaceId,
|
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' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
|
||||||
|
|
||||||
const { encryptedData, iv } = await encrypt(credentials.data)
|
const { encryptedData, iv } = await encrypt(credentials.data)
|
||||||
|
@ -2,6 +2,7 @@ import prisma from '@/lib/prisma'
|
|||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden copy'
|
||||||
|
|
||||||
export const deleteCredentials = authenticatedProcedure
|
export const deleteCredentials = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -29,11 +30,10 @@ export const deleteCredentials = authenticatedProcedure
|
|||||||
const workspace = await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: workspaceId,
|
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({
|
throw new TRPCError({
|
||||||
code: 'NOT_FOUND',
|
code: 'NOT_FOUND',
|
||||||
message: 'Workspace not found',
|
message: 'Workspace not found',
|
||||||
|
@ -6,6 +6,7 @@ import { googleSheetsCredentialsSchema } from '@typebot.io/schemas/features/bloc
|
|||||||
import { openAICredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
import { openAICredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||||
import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
|
import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
|
||||||
|
|
||||||
export const listCredentials = authenticatedProcedure
|
export const listCredentials = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -35,21 +36,23 @@ export const listCredentials = authenticatedProcedure
|
|||||||
const workspace = await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
members: { some: { userId: user.id } },
|
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: {
|
||||||
})
|
id: true,
|
||||||
if (!workspace)
|
members: true,
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
|
credentials: {
|
||||||
const credentials = await prisma.credentials.findMany({
|
|
||||||
where: {
|
where: {
|
||||||
type,
|
type,
|
||||||
workspaceId,
|
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: 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 }
|
||||||
})
|
})
|
||||||
|
@ -131,9 +131,10 @@ export const TypebotProvider = ({
|
|||||||
{ redo, undo, flush, canRedo, canUndo, set: setLocalTypebot },
|
{ redo, undo, flush, canRedo, canUndo, set: setLocalTypebot },
|
||||||
] = useUndo<Typebot>(undefined)
|
] = useUndo<Typebot>(undefined)
|
||||||
|
|
||||||
const linkedTypebotIds =
|
const linkedTypebotIds = useMemo(
|
||||||
localTypebot?.groups
|
() =>
|
||||||
.flatMap((b) => b.blocks)
|
typebot?.groups
|
||||||
|
.flatMap((group) => group.blocks)
|
||||||
.reduce<string[]>(
|
.reduce<string[]>(
|
||||||
(typebotIds, block) =>
|
(typebotIds, block) =>
|
||||||
block.type === LogicBlockType.TYPEBOT_LINK &&
|
block.type === LogicBlockType.TYPEBOT_LINK &&
|
||||||
@ -142,16 +143,16 @@ export const TypebotProvider = ({
|
|||||||
? [...typebotIds, block.options.typebotId]
|
? [...typebotIds, block.options.typebotId]
|
||||||
: typebotIds,
|
: typebotIds,
|
||||||
[]
|
[]
|
||||||
) ?? []
|
) ?? [],
|
||||||
|
[typebot?.groups]
|
||||||
|
)
|
||||||
|
|
||||||
const { data: linkedTypebotsData } = trpc.getLinkedTypebots.useQuery(
|
const { data: linkedTypebotsData } = trpc.getLinkedTypebots.useQuery(
|
||||||
{
|
{
|
||||||
workspaceId: localTypebot?.workspaceId as string,
|
typebotId: typebot?.id as string,
|
||||||
typebotIds: linkedTypebotIds.join(','),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled:
|
enabled: isDefined(typebot?.id) && linkedTypebotIds.length > 0,
|
||||||
isDefined(localTypebot?.workspaceId) && linkedTypebotIds.length > 0,
|
|
||||||
onError: (error) =>
|
onError: (error) =>
|
||||||
showToast({
|
showToast({
|
||||||
title: 'Error while fetching linkedTypebots',
|
title: 'Error while fetching linkedTypebots',
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
sanitizeSettings,
|
sanitizeSettings,
|
||||||
} from '../helpers/sanitizers'
|
} from '../helpers/sanitizers'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
||||||
|
|
||||||
export const createTypebot = authenticatedProcedure
|
export const createTypebot = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -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 = () => {
|
const defaultGroups = () => {
|
||||||
|
@ -4,6 +4,7 @@ import { TRPCError } from '@trpc/server'
|
|||||||
import { typebotSchema } from '@typebot.io/schemas'
|
import { typebotSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden'
|
import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden'
|
||||||
|
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
||||||
|
|
||||||
export const publishTypebot = authenticatedProcedure
|
export const publishTypebot = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -41,7 +42,7 @@ export const publishTypebot = authenticatedProcedure
|
|||||||
)
|
)
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
||||||
|
|
||||||
if (existingTypebot.publishedTypebot) {
|
if (existingTypebot.publishedTypebot)
|
||||||
await prisma.publicTypebot.updateMany({
|
await prisma.publicTypebot.updateMany({
|
||||||
where: {
|
where: {
|
||||||
id: existingTypebot.publishedTypebot.id,
|
id: existingTypebot.publishedTypebot.id,
|
||||||
@ -59,16 +60,16 @@ export const publishTypebot = authenticatedProcedure
|
|||||||
theme: typebotSchema.shape.theme.parse(existingTypebot.theme),
|
theme: typebotSchema.shape.theme.parse(existingTypebot.theme),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return { message: 'success' }
|
else
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.publicTypebot.createMany({
|
await prisma.publicTypebot.createMany({
|
||||||
data: {
|
data: {
|
||||||
version: existingTypebot.version,
|
version: existingTypebot.version,
|
||||||
typebotId: existingTypebot.id,
|
typebotId: existingTypebot.id,
|
||||||
edges: typebotSchema.shape.edges.parse(existingTypebot.edges),
|
edges: typebotSchema.shape.edges.parse(existingTypebot.edges),
|
||||||
groups: typebotSchema.shape.groups.parse(existingTypebot.groups),
|
groups: typebotSchema.shape.groups.parse(existingTypebot.groups),
|
||||||
settings: typebotSchema.shape.settings.parse(existingTypebot.settings),
|
settings: typebotSchema.shape.settings.parse(
|
||||||
|
existingTypebot.settings
|
||||||
|
),
|
||||||
variables: typebotSchema.shape.variables.parse(
|
variables: typebotSchema.shape.variables.parse(
|
||||||
existingTypebot.variables
|
existingTypebot.variables
|
||||||
),
|
),
|
||||||
@ -76,5 +77,18 @@ export const publishTypebot = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await sendTelemetryEvents([
|
||||||
|
{
|
||||||
|
name: 'Typebot published',
|
||||||
|
workspaceId: existingTypebot.workspaceId,
|
||||||
|
typebotId: existingTypebot.id,
|
||||||
|
userId: user.id,
|
||||||
|
data: {
|
||||||
|
name: existingTypebot.name,
|
||||||
|
isFirstPublish: existingTypebot.publishedTypebot ? undefined : true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
return { message: 'success' }
|
return { message: 'success' }
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { isAdminWriteWorkspaceForbidden } from '../helpers/isAdminWriteWorkspaceForbidden'
|
||||||
|
import { TRPCError } from '@trpc/server'
|
||||||
|
|
||||||
export const deleteWorkspace = authenticatedProcedure
|
export const deleteWorkspace = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -23,6 +25,14 @@ export const deleteWorkspace = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { workspaceId }, ctx: { user } }) => {
|
.mutation(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||||
|
const workspace = await prisma.workspace.findFirst({
|
||||||
|
where: { id: workspaceId },
|
||||||
|
include: { members: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!workspace || (await isAdminWriteWorkspaceForbidden(workspace, user)))
|
||||||
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||||
|
|
||||||
await prisma.workspace.deleteMany({
|
await prisma.workspace.deleteMany({
|
||||||
where: { members: { some: { userId: user.id } }, id: workspaceId },
|
where: { members: { some: { userId: user.id } }, id: workspaceId },
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
|
import { workspaceSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { isReadWorkspaceFobidden } from '../helpers/isReadWorkspaceFobidden'
|
||||||
|
|
||||||
export const getWorkspace = authenticatedProcedure
|
export const getWorkspace = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -25,11 +26,12 @@ export const getWorkspace = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||||
const workspace = (await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: { members: { some: { userId: user.id } }, id: workspaceId },
|
where: { id: workspaceId },
|
||||||
})) as Workspace | null
|
include: { members: true },
|
||||||
|
})
|
||||||
|
|
||||||
if (!workspace)
|
if (!workspace || (await isReadWorkspaceFobidden(workspace, user)))
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import {
|
import { workspaceInvitationSchema } from '@typebot.io/schemas'
|
||||||
WorkspaceInvitation,
|
|
||||||
workspaceInvitationSchema,
|
|
||||||
} from '@typebot.io/schemas'
|
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { isReadWorkspaceFobidden } from '../helpers/isReadWorkspaceFobidden'
|
||||||
|
|
||||||
export const listInvitationsInWorkspace = authenticatedProcedure
|
export const listInvitationsInWorkspace = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -28,19 +26,13 @@ export const listInvitationsInWorkspace = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||||
const invitations = (await prisma.workspaceInvitation.findMany({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: {
|
where: { id: workspaceId },
|
||||||
workspaceId,
|
include: { members: true, invitations: true },
|
||||||
workspace: { members: { some: { userId: user.id } } },
|
|
||||||
},
|
|
||||||
select: { createdAt: true, email: true, type: true },
|
|
||||||
})) as WorkspaceInvitation[]
|
|
||||||
|
|
||||||
if (!invitations)
|
|
||||||
throw new TRPCError({
|
|
||||||
code: 'NOT_FOUND',
|
|
||||||
message: 'No invitations found',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return { invitations }
|
if (!workspace || (await isReadWorkspaceFobidden(workspace, user)))
|
||||||
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||||
|
|
||||||
|
return { invitations: workspace.invitations }
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { WorkspaceMember, workspaceMemberSchema } from '@typebot.io/schemas'
|
import { workspaceMemberSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { isReadWorkspaceFobidden } from '../helpers/isReadWorkspaceFobidden'
|
||||||
|
|
||||||
export const listMembersInWorkspace = authenticatedProcedure
|
export const listMembersInWorkspace = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -25,13 +26,25 @@ export const listMembersInWorkspace = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||||
const members = (await prisma.memberInWorkspace.findMany({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: { userId: user.id, workspaceId },
|
where: { id: workspaceId },
|
||||||
include: { user: { select: { name: true, email: true, image: true } } },
|
include: {
|
||||||
})) as WorkspaceMember[]
|
members: {
|
||||||
|
include: {
|
||||||
if (!members)
|
user: true,
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No members found' })
|
},
|
||||||
|
},
|
||||||
return { members }
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!workspace || (await isReadWorkspaceFobidden(workspace, user)))
|
||||||
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||||
|
|
||||||
|
return {
|
||||||
|
members: workspace.members.map((member) => ({
|
||||||
|
role: member.role,
|
||||||
|
user: member.user,
|
||||||
|
workspaceId,
|
||||||
|
})),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
|
import { workspaceSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const listWorkspaces = authenticatedProcedure
|
export const listWorkspaces = authenticatedProcedure
|
||||||
@ -23,10 +23,10 @@ export const listWorkspaces = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ ctx: { user } }) => {
|
.query(async ({ ctx: { user } }) => {
|
||||||
const workspaces = (await prisma.workspace.findMany({
|
const workspaces = await prisma.workspace.findMany({
|
||||||
where: { members: { some: { userId: user.id } } },
|
where: { members: { some: { userId: user.id } } },
|
||||||
select: { name: true, id: true, icon: true, plan: true },
|
select: { name: true, id: true, icon: true, plan: true },
|
||||||
})) as Pick<Workspace, 'id' | 'name' | 'icon' | 'plan'>[]
|
})
|
||||||
|
|
||||||
if (!workspaces)
|
if (!workspaces)
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
|
import { workspaceSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { isAdminWriteWorkspaceForbidden } from '../helpers/isAdminWriteWorkspaceForbidden'
|
||||||
|
|
||||||
export const updateWorkspace = authenticatedProcedure
|
export const updateWorkspace = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -32,13 +33,20 @@ export const updateWorkspace = authenticatedProcedure
|
|||||||
data: updates,
|
data: updates,
|
||||||
})
|
})
|
||||||
|
|
||||||
const workspace = (await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: { members: { some: { userId: user.id } }, id: workspaceId },
|
where: { members: { some: { userId: user.id } }, id: workspaceId },
|
||||||
})) as Workspace | null
|
include: { members: true },
|
||||||
|
})
|
||||||
|
|
||||||
if (!workspace)
|
if (!workspace)
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
|
||||||
|
|
||||||
|
if (await isAdminWriteWorkspaceForbidden(workspace, user))
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'FORBIDDEN',
|
||||||
|
message: 'You are not allowed to update this workspace',
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
workspace,
|
workspace,
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import { MemberInWorkspace, User } from '@typebot.io/prisma'
|
||||||
|
|
||||||
|
export const isAdminWriteWorkspaceForbidden = async (
|
||||||
|
workspace: {
|
||||||
|
members: MemberInWorkspace[]
|
||||||
|
},
|
||||||
|
user: Pick<User, 'email' | 'id'>
|
||||||
|
) => {
|
||||||
|
const userRole = workspace.members.find(
|
||||||
|
(member) => member.userId === user.id
|
||||||
|
)?.role
|
||||||
|
return !userRole || userRole !== 'ADMIN'
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { MemberInWorkspace, User } from '@typebot.io/prisma'
|
||||||
|
|
||||||
|
export const isReadWorkspaceFobidden = async (
|
||||||
|
workspace: {
|
||||||
|
members: MemberInWorkspace[]
|
||||||
|
},
|
||||||
|
user: Pick<User, 'email' | 'id'>
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
process.env.ADMIN_EMAIL === user.email ||
|
||||||
|
workspace.members.find((member) => member.userId === user.id)
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { MemberInWorkspace, User } from '@typebot.io/prisma'
|
||||||
|
|
||||||
|
export const isWriteWorkspaceForbidden = async (
|
||||||
|
workspace: {
|
||||||
|
members: MemberInWorkspace[]
|
||||||
|
},
|
||||||
|
user: Pick<User, 'email' | 'id'>
|
||||||
|
) => {
|
||||||
|
const userRole = workspace.members.find(
|
||||||
|
(member) => member.userId === user.id
|
||||||
|
)?.role
|
||||||
|
return !userRole || userRole === 'GUEST'
|
||||||
|
}
|
@ -23,12 +23,12 @@
|
|||||||
"cz-emoji": "1.3.2-canary.2",
|
"cz-emoji": "1.3.2-canary.2",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"turbo": "1.10.7"
|
"turbo": "1.10.12"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
"path": "node_modules/cz-emoji"
|
"path": "node_modules/cz-emoji"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.6.7"
|
"packageManager": "pnpm@8.6.12"
|
||||||
}
|
}
|
||||||
|
44
pnpm-lock.yaml
generated
44
pnpm-lock.yaml
generated
@ -21,8 +21,8 @@ importers:
|
|||||||
specifier: 2.8.8
|
specifier: 2.8.8
|
||||||
version: 2.8.8
|
version: 2.8.8
|
||||||
turbo:
|
turbo:
|
||||||
specifier: 1.10.7
|
specifier: 1.10.12
|
||||||
version: 1.10.7
|
version: 1.10.12
|
||||||
|
|
||||||
apps/builder:
|
apps/builder:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -23117,65 +23117,65 @@ packages:
|
|||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/turbo-darwin-64@1.10.7:
|
/turbo-darwin-64@1.10.12:
|
||||||
resolution: {integrity: sha512-N2MNuhwrl6g7vGuz4y3fFG2aR1oCs0UZ5HKl8KSTn/VC2y2YIuLGedQ3OVbo0TfEvygAlF3QGAAKKtOCmGPNKA==}
|
resolution: {integrity: sha512-vmDfGVPl5/aFenAbOj3eOx3ePNcWVUyZwYr7taRl0ZBbmv2TzjRiFotO4vrKCiTVnbqjQqAFQWY2ugbqCI1kOQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/turbo-darwin-arm64@1.10.7:
|
/turbo-darwin-arm64@1.10.12:
|
||||||
resolution: {integrity: sha512-WbJkvjU+6qkngp7K4EsswOriO3xrNQag7YEGRtfLoDdMTk4O4QTeU6sfg2dKfDsBpTidTvEDwgIYJhYVGzrz9Q==}
|
resolution: {integrity: sha512-3JliEESLNX2s7g54SOBqqkqJ7UhcOGkS0ywMr5SNuvF6kWVTbuUq7uBU/sVbGq8RwvK1ONlhPvJne5MUqBCTCQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/turbo-linux-64@1.10.7:
|
/turbo-linux-64@1.10.12:
|
||||||
resolution: {integrity: sha512-x1CF2CDP1pDz/J8/B2T0hnmmOQI2+y11JGIzNP0KtwxDM7rmeg3DDTtDM/9PwGqfPotN9iVGgMiMvBuMFbsLhg==}
|
resolution: {integrity: sha512-siYhgeX0DidIfHSgCR95b8xPee9enKSOjCzx7EjTLmPqPaCiVebRYvbOIYdQWRqiaKh9yfhUtFmtMOMScUf1gg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/turbo-linux-arm64@1.10.7:
|
/turbo-linux-arm64@1.10.12:
|
||||||
resolution: {integrity: sha512-JtnBmaBSYbs7peJPkXzXxsRGSGBmBEIb6/kC8RRmyvPAMyqF8wIex0pttsI+9plghREiGPtRWv/lfQEPRlXnNQ==}
|
resolution: {integrity: sha512-K/ZhvD9l4SslclaMkTiIrnfcACgos79YcAo4kwc8bnMQaKuUeRpM15sxLpZp3xDjDg8EY93vsKyjaOhdFG2UbA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/turbo-windows-64@1.10.7:
|
/turbo-windows-64@1.10.12:
|
||||||
resolution: {integrity: sha512-7A/4CByoHdolWS8dg3DPm99owfu1aY/W0V0+KxFd0o2JQMTQtoBgIMSvZesXaWM57z3OLsietFivDLQPuzE75w==}
|
resolution: {integrity: sha512-7FSgSwvktWDNOqV65l9AbZwcoueAILeE4L7JvjauNASAjjbuzXGCEq5uN8AQU3U5BOFj4TdXrVmO2dX+lLu8Zg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/turbo-windows-arm64@1.10.7:
|
/turbo-windows-arm64@1.10.12:
|
||||||
resolution: {integrity: sha512-D36K/3b6+hqm9IBAymnuVgyePktwQ+F0lSXr2B9JfAdFPBktSqGmp50JNC7pahxhnuCLj0Vdpe9RqfnJw5zATA==}
|
resolution: {integrity: sha512-gCNXF52dwom1HLY9ry/cneBPOKTBHhzpqhMylcyvJP0vp9zeMQQkt6yjYv+6QdnmELC92CtKNp2FsNZo+z0pyw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/turbo@1.10.7:
|
/turbo@1.10.12:
|
||||||
resolution: {integrity: sha512-xm0MPM28TWx1e6TNC3wokfE5eaDqlfi0G24kmeHupDUZt5Wd0OzHFENEHMPqEaNKJ0I+AMObL6nbSZonZBV2HA==}
|
resolution: {integrity: sha512-WM3+jTfQWnB9W208pmP4oeehZcC6JQNlydb/ZHMRrhmQa+htGhWLCzd6Q9rLe0MwZLPpSPFV2/bN5egCLyoKjQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
turbo-darwin-64: 1.10.7
|
turbo-darwin-64: 1.10.12
|
||||||
turbo-darwin-arm64: 1.10.7
|
turbo-darwin-arm64: 1.10.12
|
||||||
turbo-linux-64: 1.10.7
|
turbo-linux-64: 1.10.12
|
||||||
turbo-linux-arm64: 1.10.7
|
turbo-linux-arm64: 1.10.12
|
||||||
turbo-windows-64: 1.10.7
|
turbo-windows-64: 1.10.12
|
||||||
turbo-windows-arm64: 1.10.7
|
turbo-windows-arm64: 1.10.12
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/type-check@0.3.2:
|
/type-check@0.3.2:
|
||||||
|
Reference in New Issue
Block a user