11import crypto from 'crypto'
2- import { and , eq } from 'drizzle-orm'
2+ import { eq } from 'drizzle-orm'
33import { type NextRequest , NextResponse } from 'next/server'
44import { z } from 'zod'
55import { getSession } from '@/lib/auth'
66import { createLogger } from '@/lib/logs/console-logger'
7+ import { getUserEntityPermissions } from '@/lib/permissions/utils'
78import { db } from '@/db'
89import { workflow , workflowBlocks , workflowEdges , workflowSubflows } from '@/db/schema'
910import type { LoopConfig , ParallelConfig , WorkflowState } from '@/stores/workflows/workflow/types'
@@ -24,15 +25,13 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
2425 const requestId = crypto . randomUUID ( ) . slice ( 0 , 8 )
2526 const startTime = Date . now ( )
2627
27- try {
28- const session = await getSession ( )
29- if ( ! session ?. user ?. id ) {
30- logger . warn (
31- `[${ requestId } ] Unauthorized workflow duplication attempt for ${ sourceWorkflowId } `
32- )
33- return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
34- }
28+ const session = await getSession ( )
29+ if ( ! session ?. user ?. id ) {
30+ logger . warn ( `[${ requestId } ] Unauthorized workflow duplication attempt for ${ sourceWorkflowId } ` )
31+ return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
32+ }
3533
34+ try {
3635 const body = await req . json ( )
3736 const { name, description, color, workspaceId, folderId } = DuplicateRequestSchema . parse ( body )
3837
@@ -46,19 +45,43 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
4645
4746 // Duplicate workflow and all related data in a transaction
4847 const result = await db . transaction ( async ( tx ) => {
49- // First verify the source workflow exists and user has access
48+ // First verify the source workflow exists
5049 const sourceWorkflow = await tx
5150 . select ( )
5251 . from ( workflow )
53- . where ( and ( eq ( workflow . id , sourceWorkflowId ) , eq ( workflow . userId , session . user . id ) ) )
52+ . where ( eq ( workflow . id , sourceWorkflowId ) )
5453 . limit ( 1 )
5554
5655 if ( sourceWorkflow . length === 0 ) {
57- throw new Error ( 'Source workflow not found or access denied ' )
56+ throw new Error ( 'Source workflow not found' )
5857 }
5958
6059 const source = sourceWorkflow [ 0 ]
6160
61+ // Check if user has permission to access the source workflow
62+ let canAccessSource = false
63+
64+ // Case 1: User owns the workflow
65+ if ( source . userId === session . user . id ) {
66+ canAccessSource = true
67+ }
68+
69+ // Case 2: User has admin or write permission in the source workspace
70+ if ( ! canAccessSource && source . workspaceId ) {
71+ const userPermission = await getUserEntityPermissions (
72+ session . user . id ,
73+ 'workspace' ,
74+ source . workspaceId
75+ )
76+ if ( userPermission === 'admin' || userPermission === 'write' ) {
77+ canAccessSource = true
78+ }
79+ }
80+
81+ if ( ! canAccessSource ) {
82+ throw new Error ( 'Source workflow not found or access denied' )
83+ }
84+
6285 // Create the new workflow first (required for foreign key constraints)
6386 await tx . insert ( workflow ) . values ( {
6487 id : newWorkflowId ,
@@ -346,9 +369,18 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
346369
347370 return NextResponse . json ( result , { status : 201 } )
348371 } catch ( error ) {
349- if ( error instanceof Error && error . message === 'Source workflow not found or access denied' ) {
350- logger . warn ( `[${ requestId } ] Source workflow ${ sourceWorkflowId } not found or access denied` )
351- return NextResponse . json ( { error : 'Source workflow not found' } , { status : 404 } )
372+ if ( error instanceof Error ) {
373+ if ( error . message === 'Source workflow not found' ) {
374+ logger . warn ( `[${ requestId } ] Source workflow ${ sourceWorkflowId } not found` )
375+ return NextResponse . json ( { error : 'Source workflow not found' } , { status : 404 } )
376+ }
377+
378+ if ( error . message === 'Source workflow not found or access denied' ) {
379+ logger . warn (
380+ `[${ requestId } ] User ${ session . user . id } denied access to source workflow ${ sourceWorkflowId } `
381+ )
382+ return NextResponse . json ( { error : 'Access denied' } , { status : 403 } )
383+ }
352384 }
353385
354386 if ( error instanceof z . ZodError ) {
0 commit comments