Skip to content

Commit 193b95c

Browse files
fix(auth): swap out hybrid auth in relevant callsites (#3160)
* fix(logs): execution files should always use our internal route * correct degree of access control * fix tests * fix tag defs flag * fix type check * fix mcp tools * make webhooks consistent * fix ollama and vllm visibility * remove dup test
1 parent 0ca25bb commit 193b95c

File tree

38 files changed

+196
-193
lines changed

38 files changed

+196
-193
lines changed

apps/sim/app/api/a2a/agents/[agentId]/route.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { eq } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { generateAgentCard, generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
77
import type { AgentCapabilities, AgentSkill } from '@/lib/a2a/types'
8-
import { checkHybridAuth } from '@/lib/auth/hybrid'
8+
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
99
import { getRedisClient } from '@/lib/core/config/redis'
1010
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
1111
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
@@ -40,7 +40,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<Ro
4040
}
4141

4242
if (!agent.agent.isPublished) {
43-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
43+
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
4444
if (!auth.success) {
4545
return NextResponse.json({ error: 'Agent not published' }, { status: 404 })
4646
}
@@ -81,7 +81,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<Ro
8181
const { agentId } = await params
8282

8383
try {
84-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
84+
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
8585
if (!auth.success || !auth.userId) {
8686
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
8787
}
@@ -151,7 +151,7 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise
151151
const { agentId } = await params
152152

153153
try {
154-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
154+
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
155155
if (!auth.success || !auth.userId) {
156156
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
157157
}
@@ -189,7 +189,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<R
189189
const { agentId } = await params
190190

191191
try {
192-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
192+
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
193193
if (!auth.success || !auth.userId) {
194194
logger.warn('A2A agent publish auth failed:', { error: auth.error, hasUserId: !!auth.userId })
195195
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })

apps/sim/app/api/a2a/agents/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { v4 as uuidv4 } from 'uuid'
1313
import { generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
1414
import { A2A_DEFAULT_CAPABILITIES } from '@/lib/a2a/constants'
1515
import { sanitizeAgentName } from '@/lib/a2a/utils'
16-
import { checkHybridAuth } from '@/lib/auth/hybrid'
16+
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
1717
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
1818
import { hasValidStartBlockInState } from '@/lib/workflows/triggers/trigger-utils'
1919
import { getWorkspaceById } from '@/lib/workspaces/permissions/utils'
@@ -27,7 +27,7 @@ export const dynamic = 'force-dynamic'
2727
*/
2828
export async function GET(request: NextRequest) {
2929
try {
30-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
30+
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
3131
if (!auth.success || !auth.userId) {
3232
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
3333
}
@@ -87,7 +87,7 @@ export async function GET(request: NextRequest) {
8787
*/
8888
export async function POST(request: NextRequest) {
8989
try {
90-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
90+
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
9191
if (!auth.success || !auth.userId) {
9292
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
9393
}

apps/sim/app/api/auth/oauth/credentials/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { and, eq } from 'drizzle-orm'
55
import { jwtDecode } from 'jwt-decode'
66
import { type NextRequest, NextResponse } from 'next/server'
77
import { z } from 'zod'
8-
import { checkHybridAuth } from '@/lib/auth/hybrid'
8+
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
99
import { generateRequestId } from '@/lib/core/utils/request'
1010
import { evaluateScopeCoverage, type OAuthProvider, parseProvider } from '@/lib/oauth'
1111
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -81,7 +81,7 @@ export async function GET(request: NextRequest) {
8181
const { provider: providerParam, workflowId, credentialId } = parseResult.data
8282

8383
// Authenticate requester (supports session, API key, internal JWT)
84-
const authResult = await checkHybridAuth(request)
84+
const authResult = await checkSessionOrInternalAuth(request)
8585
if (!authResult.success || !authResult.userId) {
8686
logger.warn(`[${requestId}] Unauthenticated credentials request rejected`)
8787
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })

apps/sim/app/api/auth/oauth/token/route.test.ts

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe('OAuth Token API Routes', () => {
1212
const mockRefreshTokenIfNeeded = vi.fn()
1313
const mockGetOAuthToken = vi.fn()
1414
const mockAuthorizeCredentialUse = vi.fn()
15-
const mockCheckHybridAuth = vi.fn()
15+
const mockCheckSessionOrInternalAuth = vi.fn()
1616

1717
const mockLogger = createMockLogger()
1818

@@ -42,7 +42,7 @@ describe('OAuth Token API Routes', () => {
4242
}))
4343

4444
vi.doMock('@/lib/auth/hybrid', () => ({
45-
checkHybridAuth: mockCheckHybridAuth,
45+
checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth,
4646
}))
4747
})
4848

@@ -235,7 +235,7 @@ describe('OAuth Token API Routes', () => {
235235

236236
describe('credentialAccountUserId + providerId path', () => {
237237
it('should reject unauthenticated requests', async () => {
238-
mockCheckHybridAuth.mockResolvedValueOnce({
238+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
239239
success: false,
240240
error: 'Authentication required',
241241
})
@@ -255,30 +255,8 @@ describe('OAuth Token API Routes', () => {
255255
expect(mockGetOAuthToken).not.toHaveBeenCalled()
256256
})
257257

258-
it('should reject API key authentication', async () => {
259-
mockCheckHybridAuth.mockResolvedValueOnce({
260-
success: true,
261-
authType: 'api_key',
262-
userId: 'test-user-id',
263-
})
264-
265-
const req = createMockRequest('POST', {
266-
credentialAccountUserId: 'test-user-id',
267-
providerId: 'google',
268-
})
269-
270-
const { POST } = await import('@/app/api/auth/oauth/token/route')
271-
272-
const response = await POST(req)
273-
const data = await response.json()
274-
275-
expect(response.status).toBe(401)
276-
expect(data).toHaveProperty('error', 'User not authenticated')
277-
expect(mockGetOAuthToken).not.toHaveBeenCalled()
278-
})
279-
280258
it('should reject internal JWT authentication', async () => {
281-
mockCheckHybridAuth.mockResolvedValueOnce({
259+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
282260
success: true,
283261
authType: 'internal_jwt',
284262
userId: 'test-user-id',
@@ -300,7 +278,7 @@ describe('OAuth Token API Routes', () => {
300278
})
301279

302280
it('should reject requests for other users credentials', async () => {
303-
mockCheckHybridAuth.mockResolvedValueOnce({
281+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
304282
success: true,
305283
authType: 'session',
306284
userId: 'attacker-user-id',
@@ -322,7 +300,7 @@ describe('OAuth Token API Routes', () => {
322300
})
323301

324302
it('should allow session-authenticated users to access their own credentials', async () => {
325-
mockCheckHybridAuth.mockResolvedValueOnce({
303+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
326304
success: true,
327305
authType: 'session',
328306
userId: 'test-user-id',
@@ -345,7 +323,7 @@ describe('OAuth Token API Routes', () => {
345323
})
346324

347325
it('should return 404 when credential not found for user', async () => {
348-
mockCheckHybridAuth.mockResolvedValueOnce({
326+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
349327
success: true,
350328
authType: 'session',
351329
userId: 'test-user-id',
@@ -373,7 +351,7 @@ describe('OAuth Token API Routes', () => {
373351
*/
374352
describe('GET handler', () => {
375353
it('should return access token successfully', async () => {
376-
mockCheckHybridAuth.mockResolvedValueOnce({
354+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
377355
success: true,
378356
authType: 'session',
379357
userId: 'test-user-id',
@@ -402,7 +380,7 @@ describe('OAuth Token API Routes', () => {
402380
expect(response.status).toBe(200)
403381
expect(data).toHaveProperty('accessToken', 'fresh-token')
404382

405-
expect(mockCheckHybridAuth).toHaveBeenCalled()
383+
expect(mockCheckSessionOrInternalAuth).toHaveBeenCalled()
406384
expect(mockGetCredential).toHaveBeenCalledWith(mockRequestId, 'credential-id', 'test-user-id')
407385
expect(mockRefreshTokenIfNeeded).toHaveBeenCalled()
408386
})
@@ -421,7 +399,7 @@ describe('OAuth Token API Routes', () => {
421399
})
422400

423401
it('should handle authentication failure', async () => {
424-
mockCheckHybridAuth.mockResolvedValueOnce({
402+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
425403
success: false,
426404
error: 'Authentication required',
427405
})
@@ -440,7 +418,7 @@ describe('OAuth Token API Routes', () => {
440418
})
441419

442420
it('should handle credential not found', async () => {
443-
mockCheckHybridAuth.mockResolvedValueOnce({
421+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
444422
success: true,
445423
authType: 'session',
446424
userId: 'test-user-id',
@@ -461,7 +439,7 @@ describe('OAuth Token API Routes', () => {
461439
})
462440

463441
it('should handle missing access token', async () => {
464-
mockCheckHybridAuth.mockResolvedValueOnce({
442+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
465443
success: true,
466444
authType: 'session',
467445
userId: 'test-user-id',
@@ -487,7 +465,7 @@ describe('OAuth Token API Routes', () => {
487465
})
488466

489467
it('should handle token refresh failure', async () => {
490-
mockCheckHybridAuth.mockResolvedValueOnce({
468+
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
491469
success: true,
492470
authType: 'session',
493471
userId: 'test-user-id',

apps/sim/app/api/auth/oauth/token/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { z } from 'zod'
44
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
5-
import { checkHybridAuth } from '@/lib/auth/hybrid'
5+
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
66
import { generateRequestId } from '@/lib/core/utils/request'
77
import { getCredential, getOAuthToken, refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
88

@@ -71,7 +71,7 @@ export async function POST(request: NextRequest) {
7171
providerId,
7272
})
7373

74-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
74+
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
7575
if (!auth.success || auth.authType !== 'session' || !auth.userId) {
7676
logger.warn(`[${requestId}] Unauthorized request for credentialAccountUserId path`, {
7777
success: auth.success,
@@ -187,7 +187,7 @@ export async function GET(request: NextRequest) {
187187
const { credentialId } = parseResult.data
188188

189189
// For GET requests, we only support session-based authentication
190-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
190+
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
191191
if (!auth.success || auth.authType !== 'session' || !auth.userId) {
192192
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
193193
}

apps/sim/app/api/files/delete/route.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ function setupFileApiMocks(
2929
}
3030

3131
vi.doMock('@/lib/auth/hybrid', () => ({
32-
checkHybridAuth: vi.fn().mockResolvedValue({
32+
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({
3333
success: authenticated,
3434
userId: authenticated ? 'test-user-id' : undefined,
3535
error: authenticated ? undefined : 'Unauthorized',

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createLogger } from '@sim/logger'
22
import type { NextRequest } from 'next/server'
33
import { NextResponse } from 'next/server'
4-
import { checkHybridAuth } from '@/lib/auth/hybrid'
4+
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
55
import type { StorageContext } from '@/lib/uploads/config'
66
import { deleteFile, hasCloudStorage } from '@/lib/uploads/core/storage-service'
77
import { extractStorageKey, inferContextFromKey } from '@/lib/uploads/utils/file-utils'
@@ -24,7 +24,7 @@ const logger = createLogger('FilesDeleteAPI')
2424
*/
2525
export async function POST(request: NextRequest) {
2626
try {
27-
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
27+
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
2828

2929
if (!authResult.success || !authResult.userId) {
3030
logger.warn('Unauthorized file delete request', {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
3-
import { checkHybridAuth } from '@/lib/auth/hybrid'
3+
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
44
import type { StorageContext } from '@/lib/uploads/config'
55
import { hasCloudStorage } from '@/lib/uploads/core/storage-service'
66
import { verifyFileAccess } from '@/app/api/files/authorization'
@@ -12,7 +12,7 @@ export const dynamic = 'force-dynamic'
1212

1313
export async function POST(request: NextRequest) {
1414
try {
15-
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
15+
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
1616

1717
if (!authResult.success || !authResult.userId) {
1818
logger.warn('Unauthorized download URL request', {

apps/sim/app/api/files/parse/route.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function setupFileApiMocks(
3535
}
3636

3737
vi.doMock('@/lib/auth/hybrid', () => ({
38-
checkHybridAuth: vi.fn().mockResolvedValue({
38+
checkInternalAuth: vi.fn().mockResolvedValue({
3939
success: authenticated,
4040
userId: authenticated ? 'test-user-id' : undefined,
4141
error: authenticated ? undefined : 'Unauthorized',

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import path from 'path'
55
import { createLogger } from '@sim/logger'
66
import binaryExtensionsList from 'binary-extensions'
77
import { type NextRequest, NextResponse } from 'next/server'
8-
import { checkHybridAuth } from '@/lib/auth/hybrid'
8+
import { checkInternalAuth } from '@/lib/auth/hybrid'
99
import {
1010
secureFetchWithPinnedIP,
1111
validateUrlWithDNS,
@@ -66,7 +66,7 @@ export async function POST(request: NextRequest) {
6666
const startTime = Date.now()
6767

6868
try {
69-
const authResult = await checkHybridAuth(request, { requireWorkflowId: true })
69+
const authResult = await checkInternalAuth(request, { requireWorkflowId: true })
7070

7171
if (!authResult.success) {
7272
logger.warn('Unauthorized file parse request', {

0 commit comments

Comments
 (0)