Skip to content

Commit 7ab7f96

Browse files
committed
feat(audit): record audit log for workflow lock/unlock
1 parent e1aa836 commit 7ab7f96

File tree

2 files changed

+53
-0
lines changed

2 files changed

+53
-0
lines changed

apps/sim/lib/audit/log.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ export const AuditAction = {
131131
WORKFLOW_DUPLICATED: 'workflow.duplicated',
132132
WORKFLOW_DEPLOYMENT_ACTIVATED: 'workflow.deployment_activated',
133133
WORKFLOW_DEPLOYMENT_REVERTED: 'workflow.deployment_reverted',
134+
WORKFLOW_LOCKED: 'workflow.locked',
135+
WORKFLOW_UNLOCKED: 'workflow.unlocked',
134136
WORKFLOW_VARIABLES_UPDATED: 'workflow.variables_updated',
135137

136138
// Workspaces

apps/sim/socket/database/operations.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
44
import { and, eq, inArray, or, sql } from 'drizzle-orm'
55
import { drizzle } from 'drizzle-orm/postgres-js'
66
import postgres from 'postgres'
7+
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
78
import { env } from '@/lib/core/config/env'
89
import { cleanupExternalWebhook } from '@/lib/webhooks/provider-subscriptions'
910
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
@@ -207,6 +208,17 @@ export async function persistWorkflowOperation(workflowId: string, operation: an
207208
}
208209
})
209210

211+
// Audit workflow-level lock/unlock operations
212+
if (
213+
target === OPERATION_TARGETS.BLOCKS &&
214+
op === BLOCKS_OPERATIONS.BATCH_TOGGLE_LOCKED &&
215+
userId
216+
) {
217+
auditWorkflowLockToggle(workflowId, userId).catch((error) => {
218+
logger.error('Failed to audit workflow lock toggle', { error, workflowId })
219+
})
220+
}
221+
210222
const duration = Date.now() - startTime
211223
if (duration > 100) {
212224
logger.warn('Slow socket DB operation:', {
@@ -226,6 +238,45 @@ export async function persistWorkflowOperation(workflowId: string, operation: an
226238
}
227239
}
228240

241+
/**
242+
* Records an audit log entry when all blocks in a workflow are locked or unlocked.
243+
* Only audits workflow-level transitions (all locked or all unlocked), not partial toggles.
244+
*/
245+
async function auditWorkflowLockToggle(workflowId: string, actorId: string): Promise<void> {
246+
const [wf] = await db
247+
.select({ name: workflow.name, workspaceId: workflow.workspaceId })
248+
.from(workflow)
249+
.where(eq(workflow.id, workflowId))
250+
251+
if (!wf) return
252+
253+
const blocks = await db
254+
.select({ locked: workflowBlocks.locked })
255+
.from(workflowBlocks)
256+
.where(eq(workflowBlocks.workflowId, workflowId))
257+
258+
if (blocks.length === 0) return
259+
260+
const allLocked = blocks.every((b) => b.locked)
261+
const allUnlocked = blocks.every((b) => !b.locked)
262+
263+
// Only audit workflow-level transitions, not partial toggles
264+
if (!allLocked && !allUnlocked) return
265+
266+
recordAudit({
267+
workspaceId: wf.workspaceId,
268+
actorId,
269+
action: allLocked ? AuditAction.WORKFLOW_LOCKED : AuditAction.WORKFLOW_UNLOCKED,
270+
resourceType: AuditResourceType.WORKFLOW,
271+
resourceId: workflowId,
272+
resourceName: wf.name,
273+
description: allLocked
274+
? `Locked workflow "${wf.name}"`
275+
: `Unlocked workflow "${wf.name}"`,
276+
metadata: { blockCount: blocks.length },
277+
})
278+
}
279+
229280
async function handleBlockOperationTx(
230281
tx: any,
231282
workflowId: string,

0 commit comments

Comments
 (0)