improvement(audit-log): add actorName/actorEmail to all routes, add workflow.updated event

This commit is contained in:
waleed
2026-02-18 09:49:44 -08:00
parent b8546759ac
commit a08b40838d
17 changed files with 93 additions and 0 deletions

View File

@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import {
@@ -198,9 +199,12 @@ export async function PUT(
`[${requestId}] Document updated: ${documentId} in knowledge base ${knowledgeBaseId}`
)
const auditSession = await getSession()
recordAudit({
workspaceId: accessCheck.knowledgeBase?.workspaceId ?? null,
actorId: userId,
actorName: auditSession?.user?.name,
actorEmail: auditSession?.user?.email,
action: AuditAction.DOCUMENT_UPDATED,
resourceType: AuditResourceType.DOCUMENT,
resourceId: documentId,
@@ -269,9 +273,12 @@ export async function DELETE(
`[${requestId}] Document deleted: ${documentId} from knowledge base ${knowledgeBaseId}`
)
const auditSession = await getSession()
recordAudit({
workspaceId: accessCheck.knowledgeBase?.workspaceId ?? null,
actorId: userId,
actorName: auditSession?.user?.name,
actorEmail: auditSession?.user?.email,
action: AuditAction.DOCUMENT_DELETED,
resourceType: AuditResourceType.DOCUMENT,
resourceId: documentId,

View File

@@ -209,6 +209,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const auditSession = await getSession()
if (body.bulk === true) {
try {
const validatedData = BulkCreateDocumentsSchema.parse(body)
@@ -248,6 +250,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: accessCheck.knowledgeBase?.workspaceId ?? null,
actorId: userId,
actorName: auditSession?.user?.name,
actorEmail: auditSession?.user?.email,
action: AuditAction.DOCUMENT_UPLOADED,
resourceType: AuditResourceType.DOCUMENT,
resourceId: knowledgeBaseId,
@@ -307,6 +311,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: accessCheck.knowledgeBase?.workspaceId ?? null,
actorId: userId,
actorName: auditSession?.user?.name,
actorEmail: auditSession?.user?.email,
action: AuditAction.DOCUMENT_UPLOADED,
resourceType: AuditResourceType.DOCUMENT,
resourceId: knowledgeBaseId,

View File

@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -136,9 +137,12 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
logger.info(`[${requestId}] Knowledge base updated: ${id} for user ${userId}`)
const auditSession = await getSession()
recordAudit({
workspaceId: accessCheck.knowledgeBase.workspaceId ?? null,
actorId: userId,
actorName: auditSession?.user?.name,
actorEmail: auditSession?.user?.email,
action: AuditAction.KNOWLEDGE_BASE_UPDATED,
resourceType: AuditResourceType.KNOWLEDGE_BASE,
resourceId: id,
@@ -209,9 +213,12 @@ export async function DELETE(
logger.info(`[${requestId}] Knowledge base deleted: ${id} for user ${userId}`)
const auditSession = await getSession()
recordAudit({
workspaceId: accessCheck.knowledgeBase.workspaceId ?? null,
actorId: userId,
actorName: auditSession?.user?.name,
actorEmail: auditSession?.user?.email,
action: AuditAction.KNOWLEDGE_BASE_DELETED,
resourceType: AuditResourceType.KNOWLEDGE_BASE,
resourceId: id,

View File

@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, isNull } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { McpDomainNotAllowedError, validateMcpDomain } from '@/lib/mcp/domain-check'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpService } from '@/lib/mcp/service'
@@ -87,9 +88,12 @@ export const PATCH = withMcpAuth<{ id: string }>('write')(
logger.info(`[${requestId}] Successfully updated MCP server: ${serverId}`)
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,

View File

@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, isNull } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { McpDomainNotAllowedError, validateMcpDomain } from '@/lib/mcp/domain-check'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpService } from '@/lib/mcp/service'
@@ -162,9 +163,12 @@ export const POST = withMcpAuth('write')(
// Silently fail
}
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_ADDED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
@@ -222,9 +226,12 @@ export const DELETE = withMcpAuth('admin')(
logger.info(`[${requestId}] Successfully deleted MCP server: ${serverId}`)
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_REMOVED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId!,

View File

@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
@@ -113,9 +114,12 @@ export const PATCH = withMcpAuth<RouteParams>('write')(
logger.info(`[${requestId}] Successfully updated workflow MCP server: ${serverId}`)
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
@@ -161,9 +165,12 @@ export const DELETE = withMcpAuth<RouteParams>('admin')(
mcpPubSub?.publishWorkflowToolsChanged({ serverId, workspaceId })
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_REMOVED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,

View File

@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
@@ -119,9 +120,12 @@ export const PATCH = withMcpAuth<RouteParams>('write')(
mcpPubSub?.publishWorkflowToolsChanged({ serverId, workspaceId })
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
@@ -177,9 +181,12 @@ export const DELETE = withMcpAuth<RouteParams>('write')(
mcpPubSub?.publishWorkflowToolsChanged({ serverId, workspaceId })
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,

View File

@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
@@ -198,9 +199,12 @@ export const POST = withMcpAuth<RouteParams>('write')(
mcpPubSub?.publishWorkflowToolsChanged({ serverId, workspaceId })
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,

View File

@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
import { eq, inArray, sql } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
@@ -189,9 +190,12 @@ export const POST = withMcpAuth('write')(
`[${requestId}] Successfully created workflow MCP server: ${body.name} (ID: ${serverId})`
)
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.MCP_SERVER_ADDED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,

View File

@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { validateInteger } from '@/lib/core/security/input-validation'
import { PlatformEvents } from '@/lib/core/telemetry'
@@ -262,9 +263,12 @@ export async function DELETE(
logger.info(`[${requestId}] Successfully deleted webhook: ${id}`)
}
const session = await getSession()
recordAudit({
workspaceId: webhookData.workflow.workspaceId || null,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.WEBHOOK_DELETED,
resourceType: AuditResourceType.WEBHOOK,
resourceId: id,

View File

@@ -680,9 +680,12 @@ export async function POST(request: NextRequest) {
// Telemetry should not fail the operation
}
const auditSession = await getSession()
recordAudit({
workspaceId: workflowRecord.workspaceId || null,
actorId: userId,
actorName: auditSession?.user?.name,
actorEmail: auditSession?.user?.email,
action: AuditAction.WEBHOOK_CREATED,
resourceType: AuditResourceType.WEBHOOK,
resourceId: savedWebhook.id,

View File

@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -62,9 +63,12 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
`[${requestId}] Successfully duplicated workflow ${sourceWorkflowId} to ${result.id} in ${elapsed}ms`
)
const session = await getSession()
recordAudit({
workspaceId: workspaceId || null,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.WORKFLOW_DUPLICATED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: result.id,

View File

@@ -5,6 +5,7 @@ import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { checkHybridAuth, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { env } from '@/lib/core/config/env'
import { PlatformEvents } from '@/lib/core/telemetry'
@@ -337,9 +338,12 @@ export async function DELETE(
// Don't fail the deletion if Socket.IO notification fails
}
const deleteSession = await getSession()
recordAudit({
workspaceId: workflowData.workspaceId || null,
actorId: userId,
actorName: deleteSession?.user?.name,
actorEmail: deleteSession?.user?.email,
action: AuditAction.WORKFLOW_DELETED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: workflowId,
@@ -421,6 +425,21 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
updates: updateData,
})
const session = await getSession()
recordAudit({
workspaceId: workflowData.workspaceId || null,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.WORKFLOW_UPDATED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: workflowId,
resourceName: updatedWorkflow?.name ?? workflowData.name,
description: `Updated workflow "${updatedWorkflow?.name ?? workflowData.name}"`,
metadata: updates,
request,
})
return NextResponse.json({ workflow: updatedWorkflow }, { status: 200 })
} catch (error: any) {
const elapsed = Date.now() - startTime

View File

@@ -5,6 +5,7 @@ import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils'
@@ -80,9 +81,12 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
})
.where(eq(workflow.id, workflowId))
const session = await getSession()
recordAudit({
workspaceId: workflowData.workspaceId ?? null,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.WORKFLOW_VARIABLES_UPDATED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: workflowId,

View File

@@ -5,6 +5,7 @@ import { and, asc, eq, inArray, isNull, min } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { getUserEntityPermissions, workspaceExists } from '@/lib/workspaces/permissions/utils'
@@ -189,9 +190,12 @@ export async function POST(req: NextRequest) {
logger.info(`[${requestId}] Successfully created empty workflow ${workflowId}`)
const session = await getSession()
recordAudit({
workspaceId,
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: AuditAction.WORKFLOW_CREATED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: workflowId,

View File

@@ -109,6 +109,7 @@ export const AuditAction = {
// Workflows
WORKFLOW_CREATED: 'workflow.created',
WORKFLOW_UPDATED: 'workflow.updated',
WORKFLOW_DELETED: 'workflow.deleted',
WORKFLOW_DEPLOYED: 'workflow.deployed',
WORKFLOW_UNDEPLOYED: 'workflow.undeployed',

View File

@@ -74,6 +74,7 @@ export const auditMock = {
WEBHOOK_CREATED: 'webhook.created',
WEBHOOK_DELETED: 'webhook.deleted',
WORKFLOW_CREATED: 'workflow.created',
WORKFLOW_UPDATED: 'workflow.updated',
WORKFLOW_DELETED: 'workflow.deleted',
WORKFLOW_DEPLOYED: 'workflow.deployed',
WORKFLOW_UNDEPLOYED: 'workflow.undeployed',