Skip to content

Commit 56b90ef

Browse files
committed
fix(uploads): tighten permission checks and fix multipart customKey for mothership/execution
1 parent 3ec86ce commit 56b90ef

5 files changed

Lines changed: 53 additions & 7 deletions

File tree

apps/sim/app/api/files/multipart/route.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,30 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
158158
{ status: 413 }
159159
)
160160
}
161+
} else if (context === 'mothership') {
162+
const { generateWorkspaceFileKey } = await import(
163+
'@/lib/uploads/contexts/workspace/workspace-file-manager'
164+
)
165+
customKey = generateWorkspaceFileKey(workspaceId, fileName)
166+
} else if (context === 'execution') {
167+
const workflowId = (data as { workflowId?: unknown }).workflowId
168+
const executionId = (data as { executionId?: unknown }).executionId
169+
if (typeof workflowId !== 'string' || !workflowId.trim()) {
170+
return NextResponse.json(
171+
{ error: 'workflowId is required for execution uploads' },
172+
{ status: 400 }
173+
)
174+
}
175+
if (typeof executionId !== 'string' || !executionId.trim()) {
176+
return NextResponse.json(
177+
{ error: 'executionId is required for execution uploads' },
178+
{ status: 400 }
179+
)
180+
}
181+
const { generateExecutionFileKey } = await import(
182+
'@/lib/uploads/contexts/execution/utils'
183+
)
184+
customKey = generateExecutionFileKey({ workspaceId, workflowId, executionId }, fileName)
161185
}
162186

163187
let uploadId: string

apps/sim/app/api/files/presigned/route.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
134134
}
135135

136136
const permission = await getUserEntityPermissions(sessionUserId, 'workspace', workspaceId)
137-
if (permission === null) {
137+
if (permission !== 'write' && permission !== 'admin') {
138138
return NextResponse.json(
139-
{ error: 'Insufficient permissions for workspace' },
139+
{ error: 'Write or Admin access required for mothership uploads' },
140140
{ status: 403 }
141141
)
142142
}
@@ -192,9 +192,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
192192
}
193193

194194
const permission = await getUserEntityPermissions(sessionUserId, 'workspace', workspaceId)
195-
if (permission === null) {
195+
if (permission !== 'write' && permission !== 'admin') {
196196
return NextResponse.json(
197-
{ error: 'Insufficient permissions for workspace' },
197+
{ error: 'Write or Admin access required for workspace logo uploads' },
198198
{ status: 403 }
199199
)
200200
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,8 @@ export function useWorkflowExecution() {
513513
file: fileData.file,
514514
workspaceId,
515515
context: 'execution',
516+
workflowId: activeWorkflowId,
517+
executionId,
516518
presignedEndpoint,
517519
})
518520
uploadedFiles.push({

apps/sim/lib/uploads/client/direct-upload.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ interface MultipartUploadOptions {
310310
| 'profile-pictures'
311311
| 'workspace-logos'
312312
| 'execution'
313+
workflowId?: string
314+
executionId?: string
313315
signal?: AbortSignal
314316
onProgress?: (event: UploadProgressEvent) => void
315317
}
@@ -327,7 +329,7 @@ interface PartUrl {
327329
const uploadViaMultipart = async (
328330
opts: MultipartUploadOptions
329331
): Promise<{ key: string; path: string }> => {
330-
const { file, workspaceId, context, signal, onProgress } = opts
332+
const { file, workspaceId, context, workflowId, executionId, signal, onProgress } = opts
331333

332334
// boundary-raw-fetch: multipart upload control plane uses action query strings; client lifecycle (initiate/get-part-urls/complete/abort) is sequenced manually and not modeled by a single contract
333335
const initiateResponse = await fetch('/api/files/multipart?action=initiate', {
@@ -339,6 +341,8 @@ const uploadViaMultipart = async (
339341
fileSize: file.size,
340342
workspaceId,
341343
context,
344+
...(workflowId ? { workflowId } : {}),
345+
...(executionId ? { executionId } : {}),
342346
}),
343347
signal,
344348
})
@@ -534,6 +538,10 @@ export interface RunUploadStrategyOptions {
534538
presignedEndpoint?: string
535539
/** Pre-fetched presigned data (e.g. from a batch endpoint). Skips per-file fetch. */
536540
presignedOverride?: PresignedUploadInfo
541+
/** Required when context is `execution`; forwarded to the multipart route to scope the storage key. */
542+
workflowId?: string
543+
/** Required when context is `execution`; forwarded to the multipart route to scope the storage key. */
544+
executionId?: string
537545
signal?: AbortSignal
538546
onProgress?: (event: UploadProgressEvent) => void
539547
}
@@ -549,8 +557,17 @@ export interface RunUploadStrategyOptions {
549557
export const runUploadStrategy = async (
550558
opts: RunUploadStrategyOptions
551559
): Promise<UploadStrategyResult> => {
552-
const { file, presignedEndpoint, presignedOverride, workspaceId, context, signal, onProgress } =
553-
opts
560+
const {
561+
file,
562+
presignedEndpoint,
563+
presignedOverride,
564+
workspaceId,
565+
context,
566+
workflowId,
567+
executionId,
568+
signal,
569+
onProgress,
570+
} = opts
554571
const contentType = getFileContentType(file)
555572

556573
if (presignedOverride && !presignedOverride.directUploadSupported) {
@@ -562,6 +579,8 @@ export const runUploadStrategy = async (
562579
file,
563580
workspaceId,
564581
context,
582+
workflowId,
583+
executionId,
565584
signal,
566585
onProgress,
567586
})

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)