🔒 Improve workspace API role filtering
This commit is contained in:
@ -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,
|
||||
}
|
||||
})
|
||||
|
@ -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 && (
|
||||
|
@ -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,
|
||||
@ -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)
|
||||
|
@ -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({
|
||||
@ -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',
|
||||
|
@ -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({
|
||||
@ -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 }
|
||||
})
|
||||
|
@ -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',
|
||||
|
@ -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({
|
||||
@ -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 = () => {
|
||||
|
@ -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({
|
||||
@ -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,
|
||||
@ -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' }
|
||||
})
|
||||
|
@ -1,6 +1,8 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { z } from 'zod'
|
||||
import { isAdminWriteWorkspaceForbidden } from '../helpers/isAdminWriteWorkspaceForbidden'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
|
||||
export const deleteWorkspace = authenticatedProcedure
|
||||
.meta({
|
||||
@ -23,6 +25,14 @@ export const deleteWorkspace = authenticatedProcedure
|
||||
})
|
||||
)
|
||||
.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({
|
||||
where: { members: { some: { userId: user.id } }, id: workspaceId },
|
||||
})
|
||||
|
@ -1,8 +1,9 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
|
||||
import { workspaceSchema } from '@typebot.io/schemas'
|
||||
import { z } from 'zod'
|
||||
import { isReadWorkspaceFobidden } from '../helpers/isReadWorkspaceFobidden'
|
||||
|
||||
export const getWorkspace = authenticatedProcedure
|
||||
.meta({
|
||||
@ -25,11 +26,12 @@ export const getWorkspace = authenticatedProcedure
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||
const workspace = (await prisma.workspace.findFirst({
|
||||
where: { members: { some: { userId: user.id } }, id: workspaceId },
|
||||
})) as Workspace | null
|
||||
const workspace = await prisma.workspace.findFirst({
|
||||
where: { id: workspaceId },
|
||||
include: { members: true },
|
||||
})
|
||||
|
||||
if (!workspace)
|
||||
if (!workspace || (await isReadWorkspaceFobidden(workspace, user)))
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||
|
||||
return {
|
||||
|
@ -1,11 +1,9 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import {
|
||||
WorkspaceInvitation,
|
||||
workspaceInvitationSchema,
|
||||
} from '@typebot.io/schemas'
|
||||
import { workspaceInvitationSchema } from '@typebot.io/schemas'
|
||||
import { z } from 'zod'
|
||||
import { isReadWorkspaceFobidden } from '../helpers/isReadWorkspaceFobidden'
|
||||
|
||||
export const listInvitationsInWorkspace = authenticatedProcedure
|
||||
.meta({
|
||||
@ -28,19 +26,13 @@ export const listInvitationsInWorkspace = authenticatedProcedure
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||
const invitations = (await prisma.workspaceInvitation.findMany({
|
||||
where: {
|
||||
workspaceId,
|
||||
workspace: { members: { some: { userId: user.id } } },
|
||||
},
|
||||
select: { createdAt: true, email: true, type: true },
|
||||
})) as WorkspaceInvitation[]
|
||||
const workspace = await prisma.workspace.findFirst({
|
||||
where: { id: workspaceId },
|
||||
include: { members: true, invitations: true },
|
||||
})
|
||||
|
||||
if (!invitations)
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'No invitations found',
|
||||
})
|
||||
if (!workspace || (await isReadWorkspaceFobidden(workspace, user)))
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||
|
||||
return { invitations }
|
||||
return { invitations: workspace.invitations }
|
||||
})
|
||||
|
@ -1,8 +1,9 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { WorkspaceMember, workspaceMemberSchema } from '@typebot.io/schemas'
|
||||
import { workspaceMemberSchema } from '@typebot.io/schemas'
|
||||
import { z } from 'zod'
|
||||
import { isReadWorkspaceFobidden } from '../helpers/isReadWorkspaceFobidden'
|
||||
|
||||
export const listMembersInWorkspace = authenticatedProcedure
|
||||
.meta({
|
||||
@ -25,13 +26,25 @@ export const listMembersInWorkspace = authenticatedProcedure
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||
const members = (await prisma.memberInWorkspace.findMany({
|
||||
where: { userId: user.id, workspaceId },
|
||||
include: { user: { select: { name: true, email: true, image: true } } },
|
||||
})) as WorkspaceMember[]
|
||||
const workspace = await prisma.workspace.findFirst({
|
||||
where: { id: workspaceId },
|
||||
include: {
|
||||
members: {
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (!members)
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No members found' })
|
||||
if (!workspace || (await isReadWorkspaceFobidden(workspace, user)))
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||
|
||||
return { members }
|
||||
return {
|
||||
members: workspace.members.map((member) => ({
|
||||
role: member.role,
|
||||
user: member.user,
|
||||
workspaceId,
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
|
||||
import { workspaceSchema } from '@typebot.io/schemas'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const listWorkspaces = authenticatedProcedure
|
||||
@ -23,10 +23,10 @@ export const listWorkspaces = authenticatedProcedure
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx: { user } }) => {
|
||||
const workspaces = (await prisma.workspace.findMany({
|
||||
const workspaces = await prisma.workspace.findMany({
|
||||
where: { members: { some: { userId: user.id } } },
|
||||
select: { name: true, id: true, icon: true, plan: true },
|
||||
})) as Pick<Workspace, 'id' | 'name' | 'icon' | 'plan'>[]
|
||||
})
|
||||
|
||||
if (!workspaces)
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No workspaces found' })
|
||||
|
@ -1,8 +1,9 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
|
||||
import { workspaceSchema } from '@typebot.io/schemas'
|
||||
import { z } from 'zod'
|
||||
import { isAdminWriteWorkspaceForbidden } from '../helpers/isAdminWriteWorkspaceForbidden'
|
||||
|
||||
export const updateWorkspace = authenticatedProcedure
|
||||
.meta({
|
||||
@ -32,13 +33,20 @@ export const updateWorkspace = authenticatedProcedure
|
||||
data: updates,
|
||||
})
|
||||
|
||||
const workspace = (await prisma.workspace.findFirst({
|
||||
const workspace = await prisma.workspace.findFirst({
|
||||
where: { members: { some: { userId: user.id } }, id: workspaceId },
|
||||
})) as Workspace | null
|
||||
include: { members: true },
|
||||
})
|
||||
|
||||
if (!workspace)
|
||||
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 {
|
||||
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",
|
||||
"husky": "^8.0.3",
|
||||
"prettier": "2.8.8",
|
||||
"turbo": "1.10.7"
|
||||
"turbo": "1.10.12"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"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
|
||||
version: 2.8.8
|
||||
turbo:
|
||||
specifier: 1.10.7
|
||||
version: 1.10.7
|
||||
specifier: 1.10.12
|
||||
version: 1.10.12
|
||||
|
||||
apps/builder:
|
||||
dependencies:
|
||||
@ -23117,65 +23117,65 @@ packages:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/turbo-darwin-64@1.10.7:
|
||||
resolution: {integrity: sha512-N2MNuhwrl6g7vGuz4y3fFG2aR1oCs0UZ5HKl8KSTn/VC2y2YIuLGedQ3OVbo0TfEvygAlF3QGAAKKtOCmGPNKA==}
|
||||
/turbo-darwin-64@1.10.12:
|
||||
resolution: {integrity: sha512-vmDfGVPl5/aFenAbOj3eOx3ePNcWVUyZwYr7taRl0ZBbmv2TzjRiFotO4vrKCiTVnbqjQqAFQWY2ugbqCI1kOQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-darwin-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-WbJkvjU+6qkngp7K4EsswOriO3xrNQag7YEGRtfLoDdMTk4O4QTeU6sfg2dKfDsBpTidTvEDwgIYJhYVGzrz9Q==}
|
||||
/turbo-darwin-arm64@1.10.12:
|
||||
resolution: {integrity: sha512-3JliEESLNX2s7g54SOBqqkqJ7UhcOGkS0ywMr5SNuvF6kWVTbuUq7uBU/sVbGq8RwvK1ONlhPvJne5MUqBCTCQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-64@1.10.7:
|
||||
resolution: {integrity: sha512-x1CF2CDP1pDz/J8/B2T0hnmmOQI2+y11JGIzNP0KtwxDM7rmeg3DDTtDM/9PwGqfPotN9iVGgMiMvBuMFbsLhg==}
|
||||
/turbo-linux-64@1.10.12:
|
||||
resolution: {integrity: sha512-siYhgeX0DidIfHSgCR95b8xPee9enKSOjCzx7EjTLmPqPaCiVebRYvbOIYdQWRqiaKh9yfhUtFmtMOMScUf1gg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-JtnBmaBSYbs7peJPkXzXxsRGSGBmBEIb6/kC8RRmyvPAMyqF8wIex0pttsI+9plghREiGPtRWv/lfQEPRlXnNQ==}
|
||||
/turbo-linux-arm64@1.10.12:
|
||||
resolution: {integrity: sha512-K/ZhvD9l4SslclaMkTiIrnfcACgos79YcAo4kwc8bnMQaKuUeRpM15sxLpZp3xDjDg8EY93vsKyjaOhdFG2UbA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-64@1.10.7:
|
||||
resolution: {integrity: sha512-7A/4CByoHdolWS8dg3DPm99owfu1aY/W0V0+KxFd0o2JQMTQtoBgIMSvZesXaWM57z3OLsietFivDLQPuzE75w==}
|
||||
/turbo-windows-64@1.10.12:
|
||||
resolution: {integrity: sha512-7FSgSwvktWDNOqV65l9AbZwcoueAILeE4L7JvjauNASAjjbuzXGCEq5uN8AQU3U5BOFj4TdXrVmO2dX+lLu8Zg==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-D36K/3b6+hqm9IBAymnuVgyePktwQ+F0lSXr2B9JfAdFPBktSqGmp50JNC7pahxhnuCLj0Vdpe9RqfnJw5zATA==}
|
||||
/turbo-windows-arm64@1.10.12:
|
||||
resolution: {integrity: sha512-gCNXF52dwom1HLY9ry/cneBPOKTBHhzpqhMylcyvJP0vp9zeMQQkt6yjYv+6QdnmELC92CtKNp2FsNZo+z0pyw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo@1.10.7:
|
||||
resolution: {integrity: sha512-xm0MPM28TWx1e6TNC3wokfE5eaDqlfi0G24kmeHupDUZt5Wd0OzHFENEHMPqEaNKJ0I+AMObL6nbSZonZBV2HA==}
|
||||
/turbo@1.10.12:
|
||||
resolution: {integrity: sha512-WM3+jTfQWnB9W208pmP4oeehZcC6JQNlydb/ZHMRrhmQa+htGhWLCzd6Q9rLe0MwZLPpSPFV2/bN5egCLyoKjQ==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
optionalDependencies:
|
||||
turbo-darwin-64: 1.10.7
|
||||
turbo-darwin-arm64: 1.10.7
|
||||
turbo-linux-64: 1.10.7
|
||||
turbo-linux-arm64: 1.10.7
|
||||
turbo-windows-64: 1.10.7
|
||||
turbo-windows-arm64: 1.10.7
|
||||
turbo-darwin-64: 1.10.12
|
||||
turbo-darwin-arm64: 1.10.12
|
||||
turbo-linux-64: 1.10.12
|
||||
turbo-linux-arm64: 1.10.12
|
||||
turbo-windows-64: 1.10.12
|
||||
turbo-windows-arm64: 1.10.12
|
||||
dev: true
|
||||
|
||||
/type-check@0.3.2:
|
||||
|
Reference in New Issue
Block a user