Skip to content

Commit 4a9d7c8

Browse files
fix(workflows): require user-backed workflow reads
1 parent 017ca88 commit 4a9d7c8

File tree

2 files changed

+45
-25
lines changed

2 files changed

+45
-25
lines changed

apps/sim/app/api/workflows/[id]/route.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,31 @@ describe('Workflow By ID API Route', () => {
174174
expect(mockAuthorizeWorkflowByWorkspacePermission).not.toHaveBeenCalled()
175175
})
176176

177+
it('should return 401 for verified internal jwt without userId', async () => {
178+
mockCheckHybridAuth.mockResolvedValue({
179+
success: true,
180+
authType: 'internal_jwt',
181+
})
182+
mockGetWorkflowById.mockResolvedValue({
183+
id: 'workflow-123',
184+
userId: 'other-user',
185+
name: 'Internal Workflow',
186+
workspaceId: 'workspace-456',
187+
})
188+
189+
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123', {
190+
headers: { authorization: 'Bearer internal-token' },
191+
})
192+
const params = Promise.resolve({ id: 'workflow-123' })
193+
194+
const response = await GET(req, { params })
195+
196+
expect(response.status).toBe(401)
197+
const data = await response.json()
198+
expect(data.error).toBe('Unauthorized')
199+
expect(mockAuthorizeWorkflowByWorkspacePermission).not.toHaveBeenCalled()
200+
})
201+
177202
it('should allow access when user has admin workspace permission', async () => {
178203
const mockWorkflow = {
179204
id: 'workflow-123',

apps/sim/app/api/workflows/[id]/route.ts

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server'
66
import { z } from 'zod'
77
import { getAuditActorMetadata } from '@/lib/audit/actor-metadata'
88
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
9-
import { AuthType, checkHybridAuth } from '@/lib/auth/hybrid'
9+
import { checkHybridAuth } from '@/lib/auth/hybrid'
1010
import { generateRequestId } from '@/lib/core/utils/request'
1111
import { archiveWorkflow } from '@/lib/workflows/lifecycle'
1212
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
@@ -40,7 +40,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
4040
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
4141
}
4242

43-
const isInternalCall = auth.authType === AuthType.INTERNAL_JWT
4443
const userId = auth.userId || null
4544

4645
let workflowData = await getWorkflowById(workflowId)
@@ -54,32 +53,28 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
5453
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
5554
}
5655

57-
if (isInternalCall && !userId) {
58-
// Internal system calls (e.g. workflow-in-workflow executor) may not carry a userId.
59-
// These are already authenticated via internal JWT; allow read access.
60-
logger.info(`[${requestId}] Internal API call for workflow ${workflowId}`)
61-
} else if (!userId) {
56+
if (!userId) {
6257
logger.warn(`[${requestId}] Unauthorized access attempt for workflow ${workflowId}`)
6358
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
64-
} else {
65-
const authorization = await authorizeWorkflowByWorkspacePermission({
66-
workflowId,
67-
userId,
68-
action: 'read',
69-
})
70-
if (!authorization.workflow) {
71-
logger.warn(`[${requestId}] Workflow ${workflowId} not found`)
72-
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
73-
}
59+
}
7460

75-
workflowData = authorization.workflow
76-
if (!authorization.allowed) {
77-
logger.warn(`[${requestId}] User ${userId} denied access to workflow ${workflowId}`)
78-
return NextResponse.json(
79-
{ error: authorization.message || 'Access denied' },
80-
{ status: authorization.status }
81-
)
82-
}
61+
const authorization = await authorizeWorkflowByWorkspacePermission({
62+
workflowId,
63+
userId,
64+
action: 'read',
65+
})
66+
if (!authorization.workflow) {
67+
logger.warn(`[${requestId}] Workflow ${workflowId} not found`)
68+
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
69+
}
70+
71+
workflowData = authorization.workflow
72+
if (!authorization.allowed) {
73+
logger.warn(`[${requestId}] User ${userId} denied access to workflow ${workflowId}`)
74+
return NextResponse.json(
75+
{ error: authorization.message || 'Access denied' },
76+
{ status: authorization.status }
77+
)
8378
}
8479

8580
const normalizedData = await loadWorkflowFromNormalizedTables(workflowId)

0 commit comments

Comments
 (0)