@@ -4,7 +4,7 @@ import { z } from 'zod'
44import { getSession } from '@/lib/auth'
55import { createLogger } from '@/lib/logs/console-logger'
66import { db } from '@/db'
7- import { workflow , workspace } from '@/db/schema'
7+ import { workflow , workspace , workspaceMember } from '@/db/schema'
88
99const logger = createLogger ( 'WorkflowAPI' )
1010
@@ -80,6 +80,26 @@ export async function GET(request: Request) {
8080 )
8181 }
8282
83+ // Verify the user is a member of the workspace
84+ const isMember = await db
85+ . select ( { id : workspaceMember . id } )
86+ . from ( workspaceMember )
87+ . where ( and (
88+ eq ( workspaceMember . workspaceId , workspaceId ) ,
89+ eq ( workspaceMember . userId , userId )
90+ ) )
91+ . then ( ( rows ) => rows . length > 0 )
92+
93+ if ( ! isMember ) {
94+ logger . warn (
95+ `[${ requestId } ] User ${ userId } attempted to access workspace ${ workspaceId } without membership`
96+ )
97+ return NextResponse . json (
98+ { error : 'Access denied to this workspace' , code : 'WORKSPACE_ACCESS_DENIED' } ,
99+ { status : 403 }
100+ )
101+ }
102+
83103 // Migrate any orphaned workflows to this workspace
84104 await migrateOrphanedWorkflows ( userId , workspaceId )
85105 }
@@ -88,11 +108,12 @@ export async function GET(request: Request) {
88108 let workflows
89109
90110 if ( workspaceId ) {
91- // Filter by user ID and workspace ID
111+ // Filter by workspace ID only, not user ID
112+ // This allows sharing workflows across workspace members
92113 workflows = await db
93114 . select ( )
94115 . from ( workflow )
95- . where ( and ( eq ( workflow . userId , userId ) , eq ( workflow . workspaceId , workspaceId ) ) )
116+ . where ( eq ( workflow . workspaceId , workspaceId ) )
96117 } else {
97118 // Filter by user ID only, including workflows without workspace IDs
98119 workflows = await db . select ( ) . from ( workflow ) . where ( eq ( workflow . userId , userId ) )
@@ -186,7 +207,9 @@ export async function POST(req: NextRequest) {
186207 }
187208 }
188209
189- // Validate that the workspace exists if one is specified
210+ // Validate workspace membership and permissions
211+ let userRole : string | null = null ;
212+
190213 if ( workspaceId ) {
191214 const workspaceExists = await db
192215 . select ( { id : workspace . id } )
@@ -206,17 +229,40 @@ export async function POST(req: NextRequest) {
206229 { status : 404 }
207230 )
208231 }
232+
233+ // Verify the user is a member of the workspace
234+ const membership = await db
235+ . select ( { role : workspaceMember . role } )
236+ . from ( workspaceMember )
237+ . where ( and (
238+ eq ( workspaceMember . workspaceId , workspaceId ) ,
239+ eq ( workspaceMember . userId , session . user . id )
240+ ) )
241+ . then ( ( rows ) => rows [ 0 ] )
242+
243+ if ( ! membership ) {
244+ logger . warn (
245+ `[${ requestId } ] User ${ session . user . id } attempted to sync to workspace ${ workspaceId } without membership`
246+ )
247+ return NextResponse . json (
248+ { error : 'Access denied to this workspace' , code : 'WORKSPACE_ACCESS_DENIED' } ,
249+ { status : 403 }
250+ )
251+ }
252+
253+ // Store user's role for permission checks later
254+ userRole = membership . role ;
209255 }
210256
211- // Get all workflows for the user from the database
257+ // Get all workflows for the workspace from the database
212258 // If workspaceId is provided, only get workflows for that workspace
213259 let dbWorkflows
214260
215261 if ( workspaceId ) {
216262 dbWorkflows = await db
217263 . select ( )
218264 . from ( workflow )
219- . where ( and ( eq ( workflow . userId , session . user . id ) , eq ( workflow . workspaceId , workspaceId ) ) )
265+ . where ( eq ( workflow . workspaceId , workspaceId ) )
220266 } else {
221267 dbWorkflows = await db . select ( ) . from ( workflow ) . where ( eq ( workflow . userId , session . user . id ) )
222268 }
@@ -260,6 +306,17 @@ export async function POST(req: NextRequest) {
260306 } )
261307 )
262308 } else {
309+ // Check if user has permission to update this workflow
310+ const canUpdate = dbWorkflow . userId === session . user . id ||
311+ ( workspaceId && ( userRole === 'owner' || userRole === 'admin' || userRole === 'member' ) ) ;
312+
313+ if ( ! canUpdate ) {
314+ logger . warn (
315+ `[${ requestId } ] User ${ session . user . id } attempted to update workflow ${ id } without permission`
316+ )
317+ continue ; // Skip this workflow update and move to the next one
318+ }
319+
263320 // Existing workflow - update if needed
264321 const needsUpdate =
265322 JSON . stringify ( dbWorkflow . state ) !== JSON . stringify ( clientWorkflow . state ) ||
@@ -291,13 +348,24 @@ export async function POST(req: NextRequest) {
291348 }
292349
293350 // Handle deletions - workflows in DB but not in client
294- // Only delete workflows for the current workspace!
351+ // Only delete workflows for the current workspace and only those the user can modify
295352 for ( const dbWorkflow of dbWorkflows ) {
296353 if (
297354 ! processedIds . has ( dbWorkflow . id ) &&
298355 ( ! workspaceId || dbWorkflow . workspaceId === workspaceId )
299356 ) {
300- operations . push ( db . delete ( workflow ) . where ( eq ( workflow . id , dbWorkflow . id ) ) )
357+ // Check if the user has permission to delete this workflow
358+ // Users can delete their own workflows, or any workflow if they're a workspace owner/admin
359+ const canDelete = dbWorkflow . userId === session . user . id ||
360+ ( workspaceId && ( userRole === 'owner' || userRole === 'admin' || userRole === 'member' ) ) ;
361+
362+ if ( canDelete ) {
363+ operations . push ( db . delete ( workflow ) . where ( eq ( workflow . id , dbWorkflow . id ) ) )
364+ } else {
365+ logger . warn (
366+ `[${ requestId } ] User ${ session . user . id } attempted to delete workflow ${ dbWorkflow . id } without permission`
367+ )
368+ }
301369 }
302370 }
303371
0 commit comments