Skip to content

Commit 3a33ec9

Browse files
authored
fix(authentication): added auth checks for various routes, mysql and postgres query validation, csp improvements (#2472)
1 parent 24356d9 commit 3a33ec9

File tree

18 files changed

+737
-382
lines changed

18 files changed

+737
-382
lines changed
Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { runs } from '@trigger.dev/sdk'
22
import { type NextRequest, NextResponse } from 'next/server'
3-
import { authenticateApiKeyFromHeader, updateApiKeyLastUsed } from '@/lib/api-key/service'
4-
import { getSession } from '@/lib/auth'
3+
import { checkHybridAuth } from '@/lib/auth/hybrid'
54
import { generateRequestId } from '@/lib/core/utils/request'
65
import { createLogger } from '@/lib/logs/console/logger'
76
import { createErrorResponse } from '@/app/api/workflows/utils'
@@ -18,38 +17,44 @@ export async function GET(
1817
try {
1918
logger.debug(`[${requestId}] Getting status for task: ${taskId}`)
2019

21-
// Try session auth first (for web UI)
22-
const session = await getSession()
23-
let authenticatedUserId: string | null = session?.user?.id || null
24-
25-
if (!authenticatedUserId) {
26-
const apiKeyHeader = request.headers.get('x-api-key')
27-
if (apiKeyHeader) {
28-
const authResult = await authenticateApiKeyFromHeader(apiKeyHeader)
29-
if (authResult.success && authResult.userId) {
30-
authenticatedUserId = authResult.userId
31-
if (authResult.keyId) {
32-
await updateApiKeyLastUsed(authResult.keyId).catch((error) => {
33-
logger.warn(`[${requestId}] Failed to update API key last used timestamp:`, {
34-
keyId: authResult.keyId,
35-
error,
36-
})
37-
})
38-
}
39-
}
40-
}
20+
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
21+
if (!authResult.success || !authResult.userId) {
22+
logger.warn(`[${requestId}] Unauthorized task status request`)
23+
return createErrorResponse(authResult.error || 'Authentication required', 401)
4124
}
4225

43-
if (!authenticatedUserId) {
44-
return createErrorResponse('Authentication required', 401)
45-
}
26+
const authenticatedUserId = authResult.userId
4627

47-
// Fetch task status from Trigger.dev
4828
const run = await runs.retrieve(taskId)
4929

5030
logger.debug(`[${requestId}] Task ${taskId} status: ${run.status}`)
5131

52-
// Map Trigger.dev status to our format
32+
const payload = run.payload as any
33+
if (payload?.workflowId) {
34+
const { verifyWorkflowAccess } = await import('@/socket-server/middleware/permissions')
35+
const accessCheck = await verifyWorkflowAccess(authenticatedUserId, payload.workflowId)
36+
if (!accessCheck.hasAccess) {
37+
logger.warn(`[${requestId}] User ${authenticatedUserId} denied access to task ${taskId}`, {
38+
workflowId: payload.workflowId,
39+
})
40+
return createErrorResponse('Access denied', 403)
41+
}
42+
logger.debug(`[${requestId}] User ${authenticatedUserId} has access to task ${taskId}`)
43+
} else {
44+
if (payload?.userId && payload.userId !== authenticatedUserId) {
45+
logger.warn(
46+
`[${requestId}] User ${authenticatedUserId} attempted to access task ${taskId} owned by ${payload.userId}`
47+
)
48+
return createErrorResponse('Access denied', 403)
49+
}
50+
if (!payload?.userId) {
51+
logger.warn(
52+
`[${requestId}] Task ${taskId} has no ownership information in payload. Denying access for security.`
53+
)
54+
return createErrorResponse('Access denied', 403)
55+
}
56+
}
57+
5358
const statusMap = {
5459
QUEUED: 'queued',
5560
WAITING_FOR_DEPLOY: 'queued',
@@ -67,7 +72,6 @@ export async function GET(
6772

6873
const mappedStatus = statusMap[run.status as keyof typeof statusMap] || 'unknown'
6974

70-
// Build response based on status
7175
const response: any = {
7276
success: true,
7377
taskId,
@@ -77,21 +81,18 @@ export async function GET(
7781
},
7882
}
7983

80-
// Add completion details if finished
8184
if (mappedStatus === 'completed') {
8285
response.output = run.output // This contains the workflow execution results
8386
response.metadata.completedAt = run.finishedAt
8487
response.metadata.duration = run.durationMs
8588
}
8689

87-
// Add error details if failed
8890
if (mappedStatus === 'failed') {
8991
response.error = run.error
9092
response.metadata.completedAt = run.finishedAt
9193
response.metadata.duration = run.durationMs
9294
}
9395

94-
// Add progress info if still processing
9596
if (mappedStatus === 'processing' || mappedStatus === 'queued') {
9697
response.estimatedDuration = 180000 // 3 minutes max from our config
9798
}
@@ -107,6 +108,3 @@ export async function GET(
107108
return createErrorResponse('Failed to fetch task status', 500)
108109
}
109110
}
110-
111-
// TODO: Implement task cancellation via Trigger.dev API if needed
112-
// export async function DELETE() { ... }
Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,80 @@
11
import { db } from '@sim/db'
2-
import { workflowExecutionLogs, workflowExecutionSnapshots } from '@sim/db/schema'
3-
import { eq } from 'drizzle-orm'
2+
import {
3+
permissions,
4+
workflow,
5+
workflowExecutionLogs,
6+
workflowExecutionSnapshots,
7+
} from '@sim/db/schema'
8+
import { and, eq } from 'drizzle-orm'
49
import { type NextRequest, NextResponse } from 'next/server'
10+
import { checkHybridAuth } from '@/lib/auth/hybrid'
11+
import { generateRequestId } from '@/lib/core/utils/request'
512
import { createLogger } from '@/lib/logs/console/logger'
613

714
const logger = createLogger('LogsByExecutionIdAPI')
815

916
export async function GET(
10-
_request: NextRequest,
17+
request: NextRequest,
1118
{ params }: { params: Promise<{ executionId: string }> }
1219
) {
20+
const requestId = generateRequestId()
21+
1322
try {
1423
const { executionId } = await params
1524

16-
logger.debug(`Fetching execution data for: ${executionId}`)
25+
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
26+
if (!authResult.success || !authResult.userId) {
27+
logger.warn(`[${requestId}] Unauthorized execution data access attempt for: ${executionId}`)
28+
return NextResponse.json(
29+
{ error: authResult.error || 'Authentication required' },
30+
{ status: 401 }
31+
)
32+
}
33+
34+
const authenticatedUserId = authResult.userId
35+
36+
logger.debug(
37+
`[${requestId}] Fetching execution data for: ${executionId} (auth: ${authResult.authType})`
38+
)
1739

18-
// Get the workflow execution log to find the snapshot
1940
const [workflowLog] = await db
20-
.select()
41+
.select({
42+
id: workflowExecutionLogs.id,
43+
workflowId: workflowExecutionLogs.workflowId,
44+
executionId: workflowExecutionLogs.executionId,
45+
stateSnapshotId: workflowExecutionLogs.stateSnapshotId,
46+
trigger: workflowExecutionLogs.trigger,
47+
startedAt: workflowExecutionLogs.startedAt,
48+
endedAt: workflowExecutionLogs.endedAt,
49+
totalDurationMs: workflowExecutionLogs.totalDurationMs,
50+
cost: workflowExecutionLogs.cost,
51+
})
2152
.from(workflowExecutionLogs)
53+
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
54+
.innerJoin(
55+
permissions,
56+
and(
57+
eq(permissions.entityType, 'workspace'),
58+
eq(permissions.entityId, workflow.workspaceId),
59+
eq(permissions.userId, authenticatedUserId)
60+
)
61+
)
2262
.where(eq(workflowExecutionLogs.executionId, executionId))
2363
.limit(1)
2464

2565
if (!workflowLog) {
66+
logger.warn(`[${requestId}] Execution not found or access denied: ${executionId}`)
2667
return NextResponse.json({ error: 'Workflow execution not found' }, { status: 404 })
2768
}
2869

29-
// Get the workflow state snapshot
3070
const [snapshot] = await db
3171
.select()
3272
.from(workflowExecutionSnapshots)
3373
.where(eq(workflowExecutionSnapshots.id, workflowLog.stateSnapshotId))
3474
.limit(1)
3575

3676
if (!snapshot) {
77+
logger.warn(`[${requestId}] Workflow state snapshot not found for execution: ${executionId}`)
3778
return NextResponse.json({ error: 'Workflow state snapshot not found' }, { status: 404 })
3879
}
3980

@@ -50,14 +91,14 @@ export async function GET(
5091
},
5192
}
5293

53-
logger.debug(`Successfully fetched execution data for: ${executionId}`)
94+
logger.debug(`[${requestId}] Successfully fetched execution data for: ${executionId}`)
5495
logger.debug(
55-
`Workflow state contains ${Object.keys((snapshot.stateData as any)?.blocks || {}).length} blocks`
96+
`[${requestId}] Workflow state contains ${Object.keys((snapshot.stateData as any)?.blocks || {}).length} blocks`
5697
)
5798

5899
return NextResponse.json(response)
59100
} catch (error) {
60-
logger.error('Error fetching execution data:', error)
101+
logger.error(`[${requestId}] Error fetching execution data:`, error)
61102
return NextResponse.json({ error: 'Failed to fetch execution data' }, { status: 500 })
62103
}
63104
}

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { memory, workflowBlocks } from '@sim/db/schema'
33
import { and, eq } from 'drizzle-orm'
44
import { type NextRequest, NextResponse } from 'next/server'
55
import { z } from 'zod'
6+
import { checkHybridAuth } from '@/lib/auth/hybrid'
67
import { generateRequestId } from '@/lib/core/utils/request'
78
import { createLogger } from '@/lib/logs/console/logger'
9+
import { getWorkflowAccessContext } from '@/lib/workflows/utils'
810

911
const logger = createLogger('MemoryByIdAPI')
1012

@@ -65,6 +67,65 @@ const memoryPutBodySchema = z.object({
6567
workflowId: z.string().uuid('Invalid workflow ID format'),
6668
})
6769

70+
/**
71+
* Validates authentication and workflow access for memory operations
72+
* @param request - The incoming request
73+
* @param workflowId - The workflow ID to check access for
74+
* @param requestId - Request ID for logging
75+
* @param action - 'read' for GET, 'write' for PUT/DELETE
76+
* @returns Object with userId if successful, or error response if failed
77+
*/
78+
async function validateMemoryAccess(
79+
request: NextRequest,
80+
workflowId: string,
81+
requestId: string,
82+
action: 'read' | 'write'
83+
): Promise<{ userId: string } | { error: NextResponse }> {
84+
const authResult = await checkHybridAuth(request, {
85+
requireWorkflowId: false,
86+
})
87+
if (!authResult.success || !authResult.userId) {
88+
logger.warn(`[${requestId}] Unauthorized memory ${action} attempt`)
89+
return {
90+
error: NextResponse.json(
91+
{ success: false, error: { message: 'Authentication required' } },
92+
{ status: 401 }
93+
),
94+
}
95+
}
96+
97+
const accessContext = await getWorkflowAccessContext(workflowId, authResult.userId)
98+
if (!accessContext) {
99+
logger.warn(`[${requestId}] Workflow ${workflowId} not found`)
100+
return {
101+
error: NextResponse.json(
102+
{ success: false, error: { message: 'Workflow not found' } },
103+
{ status: 404 }
104+
),
105+
}
106+
}
107+
108+
const { isOwner, workspacePermission } = accessContext
109+
const hasAccess =
110+
action === 'read'
111+
? isOwner || workspacePermission !== null
112+
: isOwner || workspacePermission === 'write' || workspacePermission === 'admin'
113+
114+
if (!hasAccess) {
115+
logger.warn(
116+
`[${requestId}] User ${authResult.userId} denied ${action} access to workflow ${workflowId}`
117+
)
118+
return {
119+
error: NextResponse.json(
120+
{ success: false, error: { message: 'Access denied' } },
121+
{ status: 403 }
122+
),
123+
}
124+
}
125+
126+
return { userId: authResult.userId }
127+
}
128+
68129
export const dynamic = 'force-dynamic'
69130
export const runtime = 'nodejs'
70131

@@ -101,6 +162,11 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
101162

102163
const { workflowId: validatedWorkflowId } = validation.data
103164

165+
const accessCheck = await validateMemoryAccess(request, validatedWorkflowId, requestId, 'read')
166+
if ('error' in accessCheck) {
167+
return accessCheck.error
168+
}
169+
104170
const memories = await db
105171
.select()
106172
.from(memory)
@@ -203,6 +269,11 @@ export async function DELETE(
203269

204270
const { workflowId: validatedWorkflowId } = validation.data
205271

272+
const accessCheck = await validateMemoryAccess(request, validatedWorkflowId, requestId, 'write')
273+
if ('error' in accessCheck) {
274+
return accessCheck.error
275+
}
276+
206277
const existingMemory = await db
207278
.select({ id: memory.id })
208279
.from(memory)
@@ -296,6 +367,11 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
296367
)
297368
}
298369

370+
const accessCheck = await validateMemoryAccess(request, validatedWorkflowId, requestId, 'write')
371+
if ('error' in accessCheck) {
372+
return accessCheck.error
373+
}
374+
299375
const existingMemories = await db
300376
.select()
301377
.from(memory)

0 commit comments

Comments
 (0)