From 0673dbe2d6e7ac2e10f3b11499b190ac8c238974 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 12 Mar 2026 16:37:35 -0700 Subject: [PATCH 1/3] fix(executor): skip Response block formatting for internal JWT callers The workflow executor tool received `{error: true}` despite successful child workflow execution when the child had a Response block. This happened because `createHttpResponseFromBlock()` hijacked the response with raw user-defined data, and the executor's `transformResponse` expected the standard `{success, executionId, output, metadata}` wrapper. Fix: skip Response block formatting when `authType === INTERNAL_JWT` since Response blocks are designed for external API consumers, not internal workflow-to-workflow calls. Also extract `AuthType` constants from magic strings across all auth type comparisons in the codebase. Co-Authored-By: Claude Opus 4.6 --- apps/sim/app/api/a2a/serve/[agentId]/route.ts | 6 +- apps/sim/app/api/auth/oauth/token/route.ts | 6 +- .../knowledge/[id]/tag-definitions/route.ts | 6 +- .../sim/app/api/mcp/serve/[serverId]/route.ts | 6 +- .../[executionId]/[contextId]/route.ts | 3 +- .../app/api/users/me/usage-limits/route.ts | 4 +- .../app/api/workflows/[id]/execute/route.ts | 16 ++-- apps/sim/app/api/workflows/[id]/route.ts | 4 +- apps/sim/lib/auth/credential-access.ts | 8 +- apps/sim/lib/auth/hybrid.ts | 20 ++-- apps/sim/tools/workflow/executor.test.ts | 95 +++++++++++++++++++ 11 files changed, 140 insertions(+), 34 deletions(-) diff --git a/apps/sim/app/api/a2a/serve/[agentId]/route.ts b/apps/sim/app/api/a2a/serve/[agentId]/route.ts index 20cb4879e39..5ac5ac4ae30 100644 --- a/apps/sim/app/api/a2a/serve/[agentId]/route.ts +++ b/apps/sim/app/api/a2a/serve/[agentId]/route.ts @@ -13,7 +13,7 @@ import { isTerminalState, parseWorkflowSSEChunk, } from '@/lib/a2a/utils' -import { type AuthResult, checkHybridAuth } from '@/lib/auth/hybrid' +import { type AuthResult, AuthType, checkHybridAuth } from '@/lib/auth/hybrid' import { acquireLock, getRedisClient, releaseLock } from '@/lib/core/config/redis' import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server' import { SSE_HEADERS } from '@/lib/core/utils/sse' @@ -242,9 +242,9 @@ export async function POST(request: NextRequest, { params }: { params: Promise { const { selectedOutputs, @@ -407,7 +410,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: // Public API callers always execute the deployed state, never the draft. const shouldUseDraftState = isPublicApiAccess ? false - : (useDraftState ?? auth.authType === 'session') + : (useDraftState ?? auth.authType === AuthType.SESSION) const streamHeader = req.headers.get('X-Stream-Response') === 'true' const enableSSE = streamHeader || streamParam === true const executionModeHeader = req.headers.get('X-Execution-Mode') @@ -440,7 +443,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: // Client-side sessions and personal API keys bill/permission-check the // authenticated user, not the workspace billed account. const useAuthenticatedUserAsActor = - isClientSession || (auth.authType === 'api_key' && auth.apiKeyType === 'personal') + isClientSession || (auth.authType === AuthType.API_KEY && auth.apiKeyType === 'personal') // Authorization fetches the full workflow record and checks workspace permissions. // Run it first so we can pass the record to preprocessing (eliminates a duplicate DB query). @@ -670,8 +673,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: const resultWithBase64 = { ...result, output: outputWithBase64 } - const hasResponseBlock = workflowHasResponseBlock(resultWithBase64) - if (hasResponseBlock) { + if (auth.authType !== AuthType.INTERNAL_JWT && workflowHasResponseBlock(resultWithBase64)) { return createHttpResponseFromBlock(resultWithBase64) } diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts index 140cc8ef53f..19d89e8eeb7 100644 --- a/apps/sim/app/api/workflows/[id]/route.ts +++ b/apps/sim/app/api/workflows/[id]/route.ts @@ -5,7 +5,7 @@ import { and, eq, isNull, ne } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' -import { checkHybridAuth, checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { AuthType, checkHybridAuth, checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { env } from '@/lib/core/config/env' import { PlatformEvents } from '@/lib/core/telemetry' import { generateRequestId } from '@/lib/core/utils/request' @@ -39,7 +39,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } - const isInternalCall = auth.authType === 'internal_jwt' + const isInternalCall = auth.authType === AuthType.INTERNAL_JWT const userId = auth.userId || null let workflowData = await getWorkflowById(workflowId) diff --git a/apps/sim/lib/auth/credential-access.ts b/apps/sim/lib/auth/credential-access.ts index b4ecc184760..5b4ba2edd1c 100644 --- a/apps/sim/lib/auth/credential-access.ts +++ b/apps/sim/lib/auth/credential-access.ts @@ -2,13 +2,13 @@ import { db } from '@sim/db' import { account, credential, credentialMember, workflow as workflowTable } from '@sim/db/schema' import { and, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { AuthType, checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' export interface CredentialAccessResult { ok: boolean error?: string - authType?: 'session' | 'internal_jwt' + authType?: typeof AuthType.SESSION | typeof AuthType.INTERNAL_JWT requesterUserId?: string credentialOwnerUserId?: string workspaceId?: string @@ -39,7 +39,7 @@ export async function authorizeCredentialUse( return { ok: false, error: auth.error || 'Authentication required' } } - const actingUserId = auth.authType === 'internal_jwt' ? callerUserId : auth.userId + const actingUserId = auth.authType === AuthType.INTERNAL_JWT ? callerUserId : auth.userId const [workflowContext] = workflowId ? await db @@ -217,7 +217,7 @@ export async function authorizeCredentialUse( return { ok: false, error: 'Credential not found' } } - if (auth.authType === 'internal_jwt') { + if (auth.authType === AuthType.INTERNAL_JWT) { return { ok: false, error: 'workflowId is required' } } diff --git a/apps/sim/lib/auth/hybrid.ts b/apps/sim/lib/auth/hybrid.ts index 0cc00e72e95..c3c03acb872 100644 --- a/apps/sim/lib/auth/hybrid.ts +++ b/apps/sim/lib/auth/hybrid.ts @@ -6,12 +6,20 @@ import { verifyInternalToken } from '@/lib/auth/internal' const logger = createLogger('HybridAuth') +export const AuthType = { + SESSION: 'session', + API_KEY: 'api_key', + INTERNAL_JWT: 'internal_jwt', +} as const + +export type AuthTypeValue = (typeof AuthType)[keyof typeof AuthType] + export interface AuthResult { success: boolean userId?: string userName?: string | null userEmail?: string | null - authType?: 'session' | 'api_key' | 'internal_jwt' + authType?: AuthTypeValue apiKeyType?: 'personal' | 'workspace' error?: string } @@ -46,14 +54,14 @@ async function resolveUserFromJwt( } if (userId) { - return { success: true, userId, authType: 'internal_jwt' } + return { success: true, userId, authType: AuthType.INTERNAL_JWT } } if (options.requireWorkflowId !== false) { return { success: false, error: 'userId required for internal JWT calls' } } - return { success: true, authType: 'internal_jwt' } + return { success: true, authType: AuthType.INTERNAL_JWT } } /** @@ -146,7 +154,7 @@ export async function checkSessionOrInternalAuth( userId: session.user.id, userName: session.user.name, userEmail: session.user.email, - authType: 'session', + authType: AuthType.SESSION, } } @@ -195,7 +203,7 @@ export async function checkHybridAuth( userId: session.user.id, userName: session.user.name, userEmail: session.user.email, - authType: 'session', + authType: AuthType.SESSION, } } @@ -208,7 +216,7 @@ export async function checkHybridAuth( return { success: true, userId: result.userId!, - authType: 'api_key', + authType: AuthType.API_KEY, apiKeyType: result.keyType, } } diff --git a/apps/sim/tools/workflow/executor.test.ts b/apps/sim/tools/workflow/executor.test.ts index 14b42619049..e49020ef0cc 100644 --- a/apps/sim/tools/workflow/executor.test.ts +++ b/apps/sim/tools/workflow/executor.test.ts @@ -242,6 +242,101 @@ describe('workflowExecutorTool', () => { }) }) + describe('transformResponse', () => { + const transformResponse = workflowExecutorTool.transformResponse! + + function mockResponse(body: any, status = 200): Response { + return { + ok: status >= 200 && status < 300, + status, + json: async () => body, + } as unknown as Response + } + + it.concurrent('should parse standard format response', async () => { + const body = { + success: true, + executionId: '550e8400-e29b-41d4-a716-446655440000', + output: { result: 'hello' }, + metadata: { duration: 500 }, + } + + const result = await transformResponse(mockResponse(body)) + + expect(result.success).toBe(true) + expect(result.output).toEqual({ result: 'hello' }) + expect(result.duration).toBe(500) + expect(result.error).toBeUndefined() + }) + + it.concurrent('should parse standard format failure', async () => { + const body = { + success: false, + executionId: '550e8400-e29b-41d4-a716-446655440000', + output: {}, + error: 'Something went wrong', + } + + const result = await transformResponse(mockResponse(body)) + + expect(result.success).toBe(false) + expect(result.error).toBe('Something went wrong') + }) + + it.concurrent('should default success to false when missing', async () => { + const body = { output: { data: 'test' } } + + const result = await transformResponse(mockResponse(body)) + + expect(result.success).toBe(false) + expect(result.output).toEqual({ data: 'test' }) + }) + + it.concurrent('should default output to empty object when missing', async () => { + const body = { success: true } + + const result = await transformResponse(mockResponse(body)) + + expect(result.success).toBe(true) + expect(result.output).toEqual({}) + expect(result.result).toEqual({}) + }) + + it.concurrent('should extract metadata duration', async () => { + const body = { + success: true, + output: {}, + metadata: { duration: 1234 }, + } + + const result = await transformResponse(mockResponse(body)) + + expect(result.duration).toBe(1234) + }) + + it.concurrent('should default duration to 0 when metadata is missing', async () => { + const body = { success: true, output: {} } + + const result = await transformResponse(mockResponse(body)) + + expect(result.duration).toBe(0) + }) + + it.concurrent('should extract workflowId and workflowName', async () => { + const body = { + success: true, + output: {}, + workflowId: 'wf-123', + workflowName: 'My Workflow', + } + + const result = await transformResponse(mockResponse(body)) + + expect(result.childWorkflowId).toBe('wf-123') + expect(result.childWorkflowName).toBe('My Workflow') + }) + }) + describe('tool metadata', () => { it.concurrent('should have correct id', () => { expect(workflowExecutorTool.id).toBe('workflow_executor') From 6305395b69bc51c033548b1ea7c2c880ed97e92c Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 12 Mar 2026 16:45:27 -0700 Subject: [PATCH 2/3] test(executor): add route-level tests for Response block auth gating Verify that internal JWT callers receive standard format while external callers (API key, session) get Response block formatting. Tests the server-side condition directly using workflowHasResponseBlock and createHttpResponseFromBlock with AuthType constants. Co-Authored-By: Claude Opus 4.6 --- .../[id]/execute/response-block.test.ts | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 apps/sim/app/api/workflows/[id]/execute/response-block.test.ts diff --git a/apps/sim/app/api/workflows/[id]/execute/response-block.test.ts b/apps/sim/app/api/workflows/[id]/execute/response-block.test.ts new file mode 100644 index 00000000000..16808cca6f9 --- /dev/null +++ b/apps/sim/app/api/workflows/[id]/execute/response-block.test.ts @@ -0,0 +1,115 @@ +/** + * Tests that internal JWT callers receive the standard response format + * even when the child workflow has a Response block. + * + * @vitest-environment node + */ + +import { beforeEach, describe, expect, it } from 'vitest' +import { AuthType } from '@/lib/auth/hybrid' +import type { ExecutionResult } from '@/lib/workflows/types' +import { createHttpResponseFromBlock, workflowHasResponseBlock } from '@/lib/workflows/utils' + +function buildExecutionResult(overrides: Partial = {}): ExecutionResult { + return { + success: true, + output: { data: { issues: [] }, status: 200, headers: {} }, + logs: [ + { + blockId: 'response-1', + blockType: 'response', + blockName: 'Response', + success: true, + output: { data: { issues: [] }, status: 200, headers: {} }, + startedAt: '2026-01-01T00:00:00Z', + endedAt: '2026-01-01T00:00:01Z', + }, + ], + metadata: { + duration: 500, + startTime: '2026-01-01T00:00:00Z', + endTime: '2026-01-01T00:00:01Z', + }, + ...overrides, + } +} + +describe('Response block gating by auth type', () => { + let resultWithResponseBlock: ExecutionResult + + beforeEach(() => { + resultWithResponseBlock = buildExecutionResult() + }) + + it('should detect a Response block in execution result', () => { + expect(workflowHasResponseBlock(resultWithResponseBlock)).toBe(true) + }) + + it('should not detect a Response block when none exists', () => { + const resultWithoutResponseBlock = buildExecutionResult({ + output: { result: 'hello' }, + logs: [ + { + blockId: 'agent-1', + blockType: 'agent', + blockName: 'Agent', + success: true, + output: { result: 'hello' }, + startedAt: '2026-01-01T00:00:00Z', + endedAt: '2026-01-01T00:00:01Z', + }, + ], + }) + expect(workflowHasResponseBlock(resultWithoutResponseBlock)).toBe(false) + }) + + it('should skip Response block formatting for internal JWT callers', () => { + const authType = AuthType.INTERNAL_JWT + const hasResponseBlock = workflowHasResponseBlock(resultWithResponseBlock) + + expect(hasResponseBlock).toBe(true) + + // This mirrors the route.ts condition: + // if (auth.authType !== AuthType.INTERNAL_JWT && workflowHasResponseBlock(...)) + const shouldFormatAsResponseBlock = authType !== AuthType.INTERNAL_JWT && hasResponseBlock + expect(shouldFormatAsResponseBlock).toBe(false) + }) + + it('should apply Response block formatting for API key callers', () => { + const authType = AuthType.API_KEY + const hasResponseBlock = workflowHasResponseBlock(resultWithResponseBlock) + + const shouldFormatAsResponseBlock = authType !== AuthType.INTERNAL_JWT && hasResponseBlock + expect(shouldFormatAsResponseBlock).toBe(true) + + const response = createHttpResponseFromBlock(resultWithResponseBlock) + expect(response.status).toBe(200) + }) + + it('should apply Response block formatting for session callers', () => { + const authType = AuthType.SESSION + const hasResponseBlock = workflowHasResponseBlock(resultWithResponseBlock) + + const shouldFormatAsResponseBlock = authType !== AuthType.INTERNAL_JWT && hasResponseBlock + expect(shouldFormatAsResponseBlock).toBe(true) + }) + + it('should return raw user data via createHttpResponseFromBlock', async () => { + const response = createHttpResponseFromBlock(resultWithResponseBlock) + const body = await response.json() + + // Response block returns the user-defined data directly (no success/executionId wrapper) + expect(body).toEqual({ issues: [] }) + expect(body.success).toBeUndefined() + expect(body.executionId).toBeUndefined() + }) + + it('should respect custom status codes from Response block', () => { + const result = buildExecutionResult({ + output: { data: { error: 'Not found' }, status: 404, headers: {} }, + }) + + const response = createHttpResponseFromBlock(result) + expect(response.status).toBe(404) + }) +}) From abf0881f1de2897d8d1dff0982a9870aae58a8a6 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 12 Mar 2026 16:51:45 -0700 Subject: [PATCH 3/3] fix(testing): add AuthType to all hybrid auth test mocks Route code now imports AuthType from @/lib/auth/hybrid, so test mocks must export it too. Added AuthTypeMock to @sim/testing and included it in all 15 test files that mock the hybrid auth module. Co-Authored-By: Claude Opus 4.6 --- .../app/api/auth/oauth/credentials/route.test.ts | 1 + apps/sim/app/api/auth/oauth/token/route.test.ts | 1 + apps/sim/app/api/files/delete/route.test.ts | 1 + apps/sim/app/api/files/parse/route.test.ts | 1 + .../sim/app/api/files/serve/[...path]/route.test.ts | 1 + apps/sim/app/api/files/upload/route.test.ts | 1 + apps/sim/app/api/function/execute/route.test.ts | 1 + apps/sim/app/api/knowledge/search/route.test.ts | 1 + apps/sim/app/api/mcp/serve/[serverId]/route.test.ts | 1 + apps/sim/app/api/tools/custom/route.test.ts | 1 + .../api/workflows/[id]/chat/status/route.test.ts | 1 + .../api/workflows/[id]/form/status/route.test.ts | 1 + apps/sim/app/api/workflows/[id]/route.test.ts | 1 + .../app/api/workflows/[id]/variables/route.test.ts | 1 + apps/sim/app/api/workflows/route.test.ts | 1 + packages/testing/src/index.ts | 1 + packages/testing/src/mocks/hybrid-auth.mock.ts | 13 ++++++++++++- packages/testing/src/mocks/index.ts | 2 +- 18 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/api/auth/oauth/credentials/route.test.ts b/apps/sim/app/api/auth/oauth/credentials/route.test.ts index 9170ec8b974..bb303924f01 100644 --- a/apps/sim/app/api/auth/oauth/credentials/route.test.ts +++ b/apps/sim/app/api/auth/oauth/credentials/route.test.ts @@ -24,6 +24,7 @@ const { mockCheckSessionOrInternalAuth, mockLogger } = vi.hoisted(() => { }) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, })) diff --git a/apps/sim/app/api/auth/oauth/token/route.test.ts b/apps/sim/app/api/auth/oauth/token/route.test.ts index 7054576c3c5..4f3d06d3a5a 100644 --- a/apps/sim/app/api/auth/oauth/token/route.test.ts +++ b/apps/sim/app/api/auth/oauth/token/route.test.ts @@ -51,6 +51,7 @@ vi.mock('@/lib/auth/credential-access', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkHybridAuth: vi.fn(), checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, checkInternalAuth: vi.fn(), diff --git a/apps/sim/app/api/files/delete/route.test.ts b/apps/sim/app/api/files/delete/route.test.ts index e63955015dc..60149483ded 100644 --- a/apps/sim/app/api/files/delete/route.test.ts +++ b/apps/sim/app/api/files/delete/route.test.ts @@ -91,6 +91,7 @@ vi.mock('@/lib/auth', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkHybridAuth: mocks.mockCheckHybridAuth, checkSessionOrInternalAuth: mocks.mockCheckSessionOrInternalAuth, checkInternalAuth: mocks.mockCheckInternalAuth, diff --git a/apps/sim/app/api/files/parse/route.test.ts b/apps/sim/app/api/files/parse/route.test.ts index 51aeecf3ee9..02baaffb6c0 100644 --- a/apps/sim/app/api/files/parse/route.test.ts +++ b/apps/sim/app/api/files/parse/route.test.ts @@ -106,6 +106,7 @@ vi.mock('@/lib/auth', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkInternalAuth: mockCheckInternalAuth, checkHybridAuth: mockCheckHybridAuth, checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, diff --git a/apps/sim/app/api/files/serve/[...path]/route.test.ts b/apps/sim/app/api/files/serve/[...path]/route.test.ts index 390348c4c01..bc5b35647c0 100644 --- a/apps/sim/app/api/files/serve/[...path]/route.test.ts +++ b/apps/sim/app/api/files/serve/[...path]/route.test.ts @@ -49,6 +49,7 @@ vi.mock('fs/promises', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, })) diff --git a/apps/sim/app/api/files/upload/route.test.ts b/apps/sim/app/api/files/upload/route.test.ts index a2361fd506a..75c06b429f9 100644 --- a/apps/sim/app/api/files/upload/route.test.ts +++ b/apps/sim/app/api/files/upload/route.test.ts @@ -100,6 +100,7 @@ vi.mock('@/lib/auth', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkHybridAuth: mocks.mockCheckHybridAuth, checkSessionOrInternalAuth: mocks.mockCheckSessionOrInternalAuth, checkInternalAuth: mocks.mockCheckInternalAuth, diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts index 70a56b06b19..b57c8fdb77e 100644 --- a/apps/sim/app/api/function/execute/route.test.ts +++ b/apps/sim/app/api/function/execute/route.test.ts @@ -18,6 +18,7 @@ vi.mock('@/lib/execution/isolated-vm', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkInternalAuth: mockCheckInternalAuth, })) diff --git a/apps/sim/app/api/knowledge/search/route.test.ts b/apps/sim/app/api/knowledge/search/route.test.ts index d257bb71655..d736edc44e9 100644 --- a/apps/sim/app/api/knowledge/search/route.test.ts +++ b/apps/sim/app/api/knowledge/search/route.test.ts @@ -68,6 +68,7 @@ vi.mock('@sim/db', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, })) diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts index 97f887e9559..77dd1adebf6 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts @@ -59,6 +59,7 @@ vi.mock('@sim/db/schema', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkHybridAuth: mockCheckHybridAuth, checkSessionOrInternalAuth: vi.fn(), checkInternalAuth: vi.fn(), diff --git a/apps/sim/app/api/tools/custom/route.test.ts b/apps/sim/app/api/tools/custom/route.test.ts index 7e6e7e6da23..3781fc1c169 100644 --- a/apps/sim/app/api/tools/custom/route.test.ts +++ b/apps/sim/app/api/tools/custom/route.test.ts @@ -182,6 +182,7 @@ vi.mock('@/lib/auth', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkSessionOrInternalAuth: (...args: unknown[]) => mockCheckSessionOrInternalAuth(...args), })) diff --git a/apps/sim/app/api/workflows/[id]/chat/status/route.test.ts b/apps/sim/app/api/workflows/[id]/chat/status/route.test.ts index 3456e372e8f..3be0cba6e2e 100644 --- a/apps/sim/app/api/workflows/[id]/chat/status/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/chat/status/route.test.ts @@ -49,6 +49,7 @@ vi.mock('@sim/db/schema', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, })) diff --git a/apps/sim/app/api/workflows/[id]/form/status/route.test.ts b/apps/sim/app/api/workflows/[id]/form/status/route.test.ts index 8099d2d844e..4e16e491fd0 100644 --- a/apps/sim/app/api/workflows/[id]/form/status/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/form/status/route.test.ts @@ -44,6 +44,7 @@ vi.mock('@sim/db/schema', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, })) diff --git a/apps/sim/app/api/workflows/[id]/route.test.ts b/apps/sim/app/api/workflows/[id]/route.test.ts index fba05c92c9c..d886e27d466 100644 --- a/apps/sim/app/api/workflows/[id]/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/route.test.ts @@ -43,6 +43,7 @@ vi.mock('@/lib/auth', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkHybridAuth: (...args: unknown[]) => mockCheckHybridAuth(...args), checkSessionOrInternalAuth: (...args: unknown[]) => mockCheckSessionOrInternalAuth(...args), })) diff --git a/apps/sim/app/api/workflows/[id]/variables/route.test.ts b/apps/sim/app/api/workflows/[id]/variables/route.test.ts index 68c863502a3..99a07d6f12b 100644 --- a/apps/sim/app/api/workflows/[id]/variables/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/variables/route.test.ts @@ -18,6 +18,7 @@ const { mockCheckSessionOrInternalAuth, mockAuthorizeWorkflowByWorkspacePermissi vi.mock('@/lib/audit/log', () => auditMock) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, })) diff --git a/apps/sim/app/api/workflows/route.test.ts b/apps/sim/app/api/workflows/route.test.ts index e1b83bdb0eb..bff62acfc83 100644 --- a/apps/sim/app/api/workflows/route.test.ts +++ b/apps/sim/app/api/workflows/route.test.ts @@ -64,6 +64,7 @@ vi.mock('@/lib/audit/log', () => ({ })) vi.mock('@/lib/auth/hybrid', () => ({ + AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, checkHybridAuth: vi.fn(), checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, checkInternalAuth: vi.fn(), diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index 48881879ac8..ce84686a152 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -45,6 +45,7 @@ export * from './assertions' export * from './builders' export * from './factories' export { + AuthTypeMock, auditMock, clearRedisMocks, createEnvMock, diff --git a/packages/testing/src/mocks/hybrid-auth.mock.ts b/packages/testing/src/mocks/hybrid-auth.mock.ts index 7ddb2ecb1eb..add5babbcc3 100644 --- a/packages/testing/src/mocks/hybrid-auth.mock.ts +++ b/packages/testing/src/mocks/hybrid-auth.mock.ts @@ -6,12 +6,22 @@ import { vi } from 'vitest' import type { MockUser } from './auth.mock' import { defaultMockUser } from './auth.mock' +/** + * Auth type constants matching @/lib/auth/hybrid AuthType. + * Include this in vi.mock() factories so route code can reference AuthType.*. + */ +export const AuthTypeMock = { + SESSION: 'session', + API_KEY: 'api_key', + INTERNAL_JWT: 'internal_jwt', +} as const + interface HybridAuthResponse { success: boolean userId?: string userName?: string | null userEmail?: string | null - authType?: 'session' | 'api_key' | 'internal_jwt' + authType?: (typeof AuthTypeMock)[keyof typeof AuthTypeMock] error?: string } @@ -46,6 +56,7 @@ export function mockHybridAuth(user: MockUser = defaultMockUser): MockHybridAuth const mockCheckInternalAuth = vi.fn<() => Promise>() vi.doMock('@/lib/auth/hybrid', () => ({ + AuthType: AuthTypeMock, checkHybridAuth: mockCheckHybridAuth, checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, checkInternalAuth: mockCheckInternalAuth, diff --git a/packages/testing/src/mocks/index.ts b/packages/testing/src/mocks/index.ts index f2c3fc6730b..69a2e5984d5 100644 --- a/packages/testing/src/mocks/index.ts +++ b/packages/testing/src/mocks/index.ts @@ -64,7 +64,7 @@ export { setupGlobalFetchMock, } from './fetch.mock' // Hybrid auth mocks -export { type MockHybridAuthResult, mockHybridAuth } from './hybrid-auth.mock' +export { AuthTypeMock, type MockHybridAuthResult, mockHybridAuth } from './hybrid-auth.mock' // Logger mocks export { clearLoggerMocks, createMockLogger, getLoggerCalls, loggerMock } from './logger.mock' // Redis mocks