Skip to content

Commit 6f2e6eb

Browse files
fix(workflows): add api-key audit metadata
1 parent b59a92b commit 6f2e6eb

File tree

7 files changed

+41
-11
lines changed

7 files changed

+41
-11
lines changed

apps/docs/content/docs/en/execution/api.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ curl -H "x-api-key: YOUR_API_KEY" \
1919

2020
You can generate API keys from the Sim platform and navigate to **Settings**, then go to **Sim Keys** and click **Create**.
2121

22+
Workflow lifecycle and deployment endpoints can also be used programmatically with session authentication or API keys, and public workflows remain available where the specific endpoint already supports public access.
23+
2224
## Logs API
2325

2426
All API responses include information about your workflow execution limits and usage:

apps/sim/app/api/workflows/[id]/deploy/route.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ describe('Workflow deploy route', () => {
156156
auth: {
157157
success: true,
158158
userId: 'api-user',
159+
userName: 'API Key Actor',
160+
userEmail: 'api@example.com',
159161
authType: 'api_key',
160162
},
161163
})
@@ -183,8 +185,8 @@ describe('Workflow deploy route', () => {
183185
expect(mockRecordAudit).toHaveBeenCalledWith(
184186
expect.objectContaining({
185187
actorId: 'api-user',
186-
actorName: undefined,
187-
actorEmail: undefined,
188+
actorName: 'API Key Actor',
189+
actorEmail: 'api@example.com',
188190
})
189191
)
190192
})
@@ -195,6 +197,8 @@ describe('Workflow deploy route', () => {
195197
auth: {
196198
success: true,
197199
userId: 'api-user',
200+
userName: 'API Key Actor',
201+
userEmail: 'api@example.com',
198202
authType: 'api_key',
199203
},
200204
})
@@ -214,8 +218,8 @@ describe('Workflow deploy route', () => {
214218
expect(mockRecordAudit).toHaveBeenCalledWith(
215219
expect.objectContaining({
216220
actorId: 'api-user',
217-
actorName: undefined,
218-
actorEmail: undefined,
221+
actorName: 'API Key Actor',
222+
actorEmail: 'api@example.com',
219223
})
220224
)
221225
})

apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,13 @@ describe('Workflow deployment version revert route', () => {
125125
it('allows API-key auth for revert using hybrid auth userId', async () => {
126126
mockValidateWorkflowAccess.mockResolvedValue({
127127
workflow: { id: 'wf-1', name: 'Test Workflow', workspaceId: 'ws-1' },
128-
auth: { success: true, userId: 'api-user', authType: 'api_key' },
128+
auth: {
129+
success: true,
130+
userId: 'api-user',
131+
userName: 'API Key Actor',
132+
userEmail: 'api@example.com',
133+
authType: 'api_key',
134+
},
129135
})
130136

131137
const req = new NextRequest('http://localhost:3000/api/workflows/wf-1/deployments/3/revert', {
@@ -144,8 +150,8 @@ describe('Workflow deployment version revert route', () => {
144150
expect(mockRecordAudit).toHaveBeenCalledWith(
145151
expect.objectContaining({
146152
actorId: 'api-user',
147-
actorName: undefined,
148-
actorEmail: undefined,
153+
actorName: 'API Key Actor',
154+
actorEmail: 'api@example.com',
149155
})
150156
)
151157
})

apps/sim/app/api/workflows/[id]/deployments/[version]/route.test.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,13 @@ describe('Workflow deployment version route', () => {
164164
it('allows API-key auth for activation using hybrid auth userId', async () => {
165165
mockValidateWorkflowAccess.mockResolvedValue({
166166
workflow: { id: 'wf-1', name: 'Test Workflow', workspaceId: 'ws-1' },
167-
auth: { success: true, userId: 'api-user', authType: 'api_key' },
167+
auth: {
168+
success: true,
169+
userId: 'api-user',
170+
userName: 'API Key Actor',
171+
userEmail: 'api@example.com',
172+
authType: 'api_key',
173+
},
168174
})
169175

170176
const req = new NextRequest('http://localhost:3000/api/workflows/wf-1/deployments/3', {
@@ -184,15 +190,16 @@ describe('Workflow deployment version route', () => {
184190
workflowId: 'wf-1',
185191
userId: 'api-user',
186192
action: 'admin',
193+
workflow: { id: 'wf-1', name: 'Test Workflow', workspaceId: 'ws-1' },
187194
})
188195
expect(mockSaveTriggerWebhooksForDeploy).toHaveBeenCalledWith(
189196
expect.objectContaining({ userId: 'api-user' })
190197
)
191198
expect(mockRecordAudit).toHaveBeenCalledWith(
192199
expect.objectContaining({
193200
actorId: 'api-user',
194-
actorName: undefined,
195-
actorEmail: undefined,
201+
actorName: 'API Key Actor',
202+
actorEmail: 'api@example.com',
196203
})
197204
)
198205
})
@@ -243,6 +250,7 @@ describe('Workflow deployment version route', () => {
243250
workflowId: 'wf-1',
244251
userId: 'user-1',
245252
action: 'admin',
253+
workflow: { id: 'wf-1', name: 'Test Workflow', workspaceId: 'ws-1' },
246254
})
247255
expect(mockDbSelect).not.toHaveBeenCalled()
248256
expect(mockSaveTriggerWebhooksForDeploy).not.toHaveBeenCalled()

apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export async function PATCH(
129129
workflowId: id,
130130
userId: auth.userId,
131131
action: 'admin',
132+
workflow: workflowData,
132133
})
133134
if (!authorization.allowed) {
134135
return createErrorResponse(authorization.message || 'Access denied', authorization.status)

apps/sim/lib/api-key/service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { db } from '@sim/db'
2-
import { apiKey as apiKeyTable } from '@sim/db/schema'
2+
import { apiKey as apiKeyTable, user as userTable } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
44
import { and, eq } from 'drizzle-orm'
55
import { authenticateApiKey } from '@/lib/api-key/auth'
@@ -33,6 +33,8 @@ export interface ApiKeyAuthOptions {
3333
export interface ApiKeyAuthResult {
3434
success: boolean
3535
userId?: string
36+
userName?: string | null
37+
userEmail?: string | null
3638
keyId?: string
3739
keyType?: 'personal' | 'workspace'
3840
workspaceId?: string
@@ -68,12 +70,15 @@ export async function authenticateApiKeyFromHeader(
6870
.select({
6971
id: apiKeyTable.id,
7072
userId: apiKeyTable.userId,
73+
userName: userTable.name,
74+
userEmail: userTable.email,
7175
workspaceId: apiKeyTable.workspaceId,
7276
type: apiKeyTable.type,
7377
key: apiKeyTable.key,
7478
expiresAt: apiKeyTable.expiresAt,
7579
})
7680
.from(apiKeyTable)
81+
.innerJoin(userTable, eq(apiKeyTable.userId, userTable.id))
7782

7883
// Apply filters
7984
const conditions = []
@@ -154,6 +159,8 @@ export async function authenticateApiKeyFromHeader(
154159
return {
155160
success: true,
156161
userId: storedKey.userId,
162+
userName: storedKey.userName,
163+
userEmail: storedKey.userEmail,
157164
keyId: storedKey.id,
158165
keyType: storedKey.type as 'personal' | 'workspace',
159166
workspaceId: storedKey.workspaceId || options.workspaceId || undefined,

apps/sim/lib/auth/hybrid.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ export async function checkHybridAuth(
218218
success: true,
219219
userId: result.userId!,
220220
workspaceId: result.workspaceId,
221+
userName: result.userName,
222+
userEmail: result.userEmail,
221223
authType: AuthType.API_KEY,
222224
apiKeyType: result.keyType,
223225
}

0 commit comments

Comments
 (0)