Skip to content

Commit 0981aaa

Browse files
fix(workflows): streamline middleware api key auth
1 parent b381908 commit 0981aaa

File tree

2 files changed

+73
-37
lines changed

2 files changed

+73
-37
lines changed

apps/sim/app/api/workflows/middleware.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,29 @@ describe('validateWorkflowAccess', () => {
147147
expect(mockAuthorizeWorkflowByWorkspacePermission).not.toHaveBeenCalled()
148148
})
149149

150+
it('returns 403 when active workflow record lacks workspaceId and skips api key auth', async () => {
151+
const request = new NextRequest(`http://localhost:3000/api/workflows/${WORKFLOW_ID}/status`, {
152+
headers: { 'x-api-key': 'personal-key' },
153+
})
154+
mockGetActiveWorkflowRecord.mockResolvedValue(createWorkflow({ workspaceId: null }))
155+
156+
const result = await validateWorkflowAccess(request, WORKFLOW_ID, {
157+
requireDeployment: false,
158+
action: 'read',
159+
})
160+
161+
expect(result).toEqual({
162+
error: {
163+
message:
164+
'This workflow is not attached to a workspace. Personal workflows are deprecated and cannot be accessed.',
165+
status: 403,
166+
},
167+
})
168+
expect(mockAuthenticateApiKeyFromHeader).not.toHaveBeenCalled()
169+
expect(mockUpdateApiKeyLastUsed).not.toHaveBeenCalled()
170+
expect(mockAuthorizeWorkflowByWorkspacePermission).not.toHaveBeenCalled()
171+
})
172+
150173
it('returns 404 for authenticated workflow in an archived workspace', async () => {
151174
mockGetActiveWorkflowRecord.mockResolvedValue(null)
152175
mockGetWorkflowById.mockResolvedValue(createWorkflow())
@@ -310,6 +333,8 @@ describe('validateWorkflowAccess', () => {
310333
workspaceId: WORKSPACE_ID,
311334
keyTypes: ['workspace', 'personal'],
312335
})
336+
expect(mockCheckHybridAuth).not.toHaveBeenCalled()
337+
expect(mockUpdateApiKeyLastUsed).toHaveBeenCalledTimes(1)
313338
expect(mockUpdateApiKeyLastUsed).toHaveBeenCalledWith('key-1')
314339
expect(mockAuthorizeWorkflowByWorkspacePermission).toHaveBeenCalledWith({
315340
workflowId: WORKFLOW_ID,
@@ -382,6 +407,8 @@ describe('validateWorkflowAccess', () => {
382407
workspaceId: WORKSPACE_ID,
383408
keyTypes: ['workspace', 'personal'],
384409
})
410+
expect(mockCheckHybridAuth).not.toHaveBeenCalled()
411+
expect(mockUpdateApiKeyLastUsed).toHaveBeenCalledTimes(1)
385412
expect(mockUpdateApiKeyLastUsed).toHaveBeenCalledWith('key-1')
386413
expect(mockAuthorizeWorkflowByWorkspacePermission).toHaveBeenCalledWith({
387414
workflowId: WORKFLOW_ID,

apps/sim/app/api/workflows/middleware.ts

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ export interface WorkflowAccessOptions {
2727
async function getValidatedWorkflow(workflowId: string): Promise<ValidationResult> {
2828
const activeWorkflow = await getActiveWorkflowRecord(workflowId)
2929
if (activeWorkflow) {
30+
if (!activeWorkflow.workspaceId) {
31+
return {
32+
error: {
33+
message:
34+
'This workflow is not attached to a workspace. Personal workflows are deprecated and cannot be accessed.',
35+
status: 403,
36+
},
37+
}
38+
}
39+
3040
return { workflow: activeWorkflow }
3141
}
3242

@@ -71,39 +81,20 @@ export async function validateWorkflowAccess(
7181
const allowInternalSecret = normalizedOptions.allowInternalSecret ?? false
7282

7383
if (!requireDeployment) {
74-
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
75-
if (!auth.success || !auth.userId) {
76-
return {
77-
error: {
78-
message: auth.error || 'Unauthorized',
79-
status: 401,
80-
},
81-
}
82-
}
83-
8484
const workflowResult = await getValidatedWorkflow(workflowId)
8585
if (workflowResult.error || !workflowResult.workflow) {
8686
return workflowResult
8787
}
8888
const workflow = workflowResult.workflow
89+
const apiKeyHeader = request.headers.get('x-api-key')
8990

90-
if (auth.authType === AuthType.API_KEY) {
91-
const apiKeyHeader = request.headers.get('x-api-key')
92-
if (!apiKeyHeader) {
93-
return {
94-
error: {
95-
message: 'Unauthorized: Invalid API key',
96-
status: 401,
97-
},
98-
}
99-
}
100-
91+
if (apiKeyHeader) {
10192
const scopedApiKeyResult = await authenticateApiKeyFromHeader(apiKeyHeader, {
10293
workspaceId: workflow.workspaceId as string,
10394
keyTypes: ['workspace', 'personal'],
10495
})
10596

106-
if (!scopedApiKeyResult.success) {
97+
if (!scopedApiKeyResult.success || !scopedApiKeyResult.userId) {
10798
return {
10899
error: {
109100
message: 'Unauthorized: Invalid API key',
@@ -112,7 +103,11 @@ export async function validateWorkflowAccess(
112103
}
113104
}
114105

115-
if (!scopedApiKeyResult.userId) {
106+
if (scopedApiKeyResult.keyId) {
107+
await updateApiKeyLastUsed(scopedApiKeyResult.keyId)
108+
}
109+
110+
if (!scopedApiKeyResult.workspaceId) {
116111
return {
117112
error: {
118113
message: 'Unauthorized: Invalid API key',
@@ -121,26 +116,40 @@ export async function validateWorkflowAccess(
121116
}
122117
}
123118

124-
if (scopedApiKeyResult.keyId) {
125-
await updateApiKeyLastUsed(scopedApiKeyResult.keyId)
119+
const auth: AuthResult = {
120+
success: true,
121+
userId: scopedApiKeyResult.userId,
122+
workspaceId: scopedApiKeyResult.workspaceId,
123+
userName: scopedApiKeyResult.userName,
124+
userEmail: scopedApiKeyResult.userEmail,
125+
authType: AuthType.API_KEY,
126+
apiKeyType: scopedApiKeyResult.keyType,
126127
}
127128

128-
auth.workspaceId = scopedApiKeyResult.workspaceId
129-
auth.userId = scopedApiKeyResult.userId
130-
auth.userName = scopedApiKeyResult.userName
131-
auth.userEmail = scopedApiKeyResult.userEmail
132-
auth.apiKeyType = scopedApiKeyResult.keyType
129+
const authorization = await authorizeWorkflowByWorkspacePermission({
130+
workflowId,
131+
userId: scopedApiKeyResult.userId,
132+
action,
133+
workflow,
134+
})
135+
if (!authorization.allowed) {
136+
return {
137+
error: {
138+
message: authorization.message || 'Access denied',
139+
status: authorization.status,
140+
},
141+
}
142+
}
143+
144+
return { workflow, auth }
133145
}
134146

135-
if (
136-
auth.authType === AuthType.API_KEY &&
137-
auth.apiKeyType === 'workspace' &&
138-
auth.workspaceId !== workflow.workspaceId
139-
) {
147+
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
148+
if (!auth.success || !auth.userId) {
140149
return {
141150
error: {
142-
message: 'Workflow not found',
143-
status: 404,
151+
message: auth.error || 'Unauthorized',
152+
status: 401,
144153
},
145154
}
146155
}

0 commit comments

Comments
 (0)