diff --git a/apps/sim/app/api/workflows/[id]/execute/route.test.ts b/apps/sim/app/api/workflows/[id]/execute/route.test.ts index 50886847851..a6ce1ca061d 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.test.ts @@ -2241,13 +2241,16 @@ describe('workflow execute async route', () => { }) describe('credential actor is derived from the authenticated caller', () => { - it.each([ - ['session without isClientSession', EXECUTION_CALLERS[0], true], - ['personal API key', EXECUTION_CALLERS[1], true], - ['workspace API key', EXECUTION_CALLERS[2], false], - ['public API', EXECUTION_CALLERS[3], false], - ['internal JWT', EXECUTION_CALLERS[4], false], - ] as const)('%s: authenticated user is actor = %s', async (_name, caller, expected) => { + const ACTS_AS_AUTHENTICATED_USER: Record = { + session: true, + 'personal API key': true, + 'workspace API key': false, + 'public API': false, + 'internal JWT': false, + } + + it.each(EXECUTION_CALLERS)('$caseName', async (caller) => { + const expected = ACTS_AS_AUTHENTICATED_USER[caller.caseName] configureExecutionCaller(caller) const response = await POST( @@ -2259,31 +2262,21 @@ describe('workflow execute async route', () => { expect(mockPreprocessExecution).toHaveBeenCalledWith( expect.objectContaining({ useAuthenticatedUserAsActor: expected }) ) - const executionCall = mockExecuteWorkflowCore.mock.calls[0][0] - const snapshot = - typeof executionCall.snapshot === 'string' - ? JSON.parse(executionCall.snapshot) - : executionCall.snapshot - expect(snapshot.metadata.enforceCredentialAccess).toBe(expected) - }) - - it('authorizes a session run that defaults to draft state at write', async () => { - const response = await POST( - createMockRequest('POST', { input: {} }, { 'Content-Type': 'application/json' }), - createRouteContext({ id: 'workflow-1' }) - ) - - expect(response.status).toBe(200) - expect(mockAuthorizeWorkflowByWorkspacePermission).toHaveBeenCalledWith( - expect.objectContaining({ action: 'write' }) - ) + expect( + mockExecuteWorkflowCore.mock.calls[0][0].snapshot.metadata.enforceCredentialAccess + ).toBe(expected) }) + }) - it('authorizes a session run of the deployed state at read', async () => { + describe('execution access level', () => { + it.each([ + [{}, 'write'], + [{ useDraftState: false }, 'read'], + ] as const)('session run with %o authorizes at %s', async (bodyExtra, action) => { const response = await POST( createMockRequest( 'POST', - { input: {}, useDraftState: false }, + { input: {}, ...bodyExtra }, { 'Content-Type': 'application/json' } ), createRouteContext({ id: 'workflow-1' }) @@ -2291,7 +2284,7 @@ describe('workflow execute async route', () => { expect(response.status).toBe(200) expect(mockAuthorizeWorkflowByWorkspacePermission).toHaveBeenCalledWith( - expect.objectContaining({ action: 'read' }) + expect.objectContaining({ action }) ) }) }) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index a2c5f27608a..94d7625a68f 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -987,11 +987,9 @@ async function handleExecutePost( } /** - * Sessions and personal keys preserve the authenticated human as actor. - * Preprocessing resolves the workspace payer independently. This follows - * the auth type, never the body's `isClientSession`: a session runs the - * draft it can edit, so letting it opt out would run that draft with the - * billing account's credentials. + * Sessions and personal keys act as the authenticated human. Keyed on auth + * type, not the body's `isClientSession`, so a session cannot opt out and + * run its editable draft on the billing account's credentials. */ const useAuthenticatedUserAsActor = auth.authType === AuthType.SESSION ||