mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
improvement(audit): enrich metadata across 23 audit log call sites (#3319)
* improvement(audit): enrich metadata across 23 audit log call sites * improvement(audit): enrich metadata across 23 audit log call sites
This commit is contained in:
@@ -150,6 +150,7 @@ export async function POST(
|
||||
})
|
||||
|
||||
recordAudit({
|
||||
workspaceId: null,
|
||||
actorId: session.user.id,
|
||||
actorName: session.user.name,
|
||||
actorEmail: session.user.email,
|
||||
@@ -158,7 +159,7 @@ export async function POST(
|
||||
resourceId: id,
|
||||
resourceName: result.set.name,
|
||||
description: `Resent credential set invitation to ${invitation.email}`,
|
||||
metadata: { invitationId, email: invitation.email },
|
||||
metadata: { invitationId, targetEmail: invitation.email },
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
resourceName: result.set.name,
|
||||
description: `Created invitation for credential set "${result.set.name}"${email ? ` to ${email}` : ''}`,
|
||||
metadata: { targetEmail: email || undefined },
|
||||
request: req,
|
||||
})
|
||||
|
||||
@@ -239,7 +240,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
||||
return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 })
|
||||
}
|
||||
|
||||
await db
|
||||
const [revokedInvitation] = await db
|
||||
.update(credentialSetInvitation)
|
||||
.set({ status: 'cancelled' })
|
||||
.where(
|
||||
@@ -248,6 +249,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
||||
eq(credentialSetInvitation.credentialSetId, id)
|
||||
)
|
||||
)
|
||||
.returning({ email: credentialSetInvitation.email })
|
||||
|
||||
recordAudit({
|
||||
workspaceId: null,
|
||||
@@ -259,6 +261,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
resourceName: result.set.name,
|
||||
description: `Revoked invitation "${invitationId}" for credential set "${result.set.name}"`,
|
||||
metadata: { targetEmail: revokedInvitation?.email ?? undefined },
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -151,8 +151,15 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
||||
}
|
||||
|
||||
const [memberToRemove] = await db
|
||||
.select()
|
||||
.select({
|
||||
id: credentialSetMember.id,
|
||||
credentialSetId: credentialSetMember.credentialSetId,
|
||||
userId: credentialSetMember.userId,
|
||||
status: credentialSetMember.status,
|
||||
email: user.email,
|
||||
})
|
||||
.from(credentialSetMember)
|
||||
.innerJoin(user, eq(credentialSetMember.userId, user.id))
|
||||
.where(and(eq(credentialSetMember.id, memberId), eq(credentialSetMember.credentialSetId, id)))
|
||||
.limit(1)
|
||||
|
||||
@@ -189,6 +196,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
resourceName: result.set.name,
|
||||
description: `Removed member from credential set "${result.set.name}"`,
|
||||
metadata: { targetEmail: memberToRemove.email ?? undefined },
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -148,6 +148,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
resourceName: name,
|
||||
description: `Duplicated folder "${sourceFolder.name}" as "${name}"`,
|
||||
metadata: {
|
||||
sourceId: sourceFolder.id,
|
||||
affected: { workflows: workflowStats.succeeded, folders: folderMapping.size },
|
||||
},
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -178,6 +178,12 @@ export async function DELETE(
|
||||
resourceId: id,
|
||||
resourceName: existingFolder.name,
|
||||
description: `Deleted folder "${existingFolder.name}"`,
|
||||
metadata: {
|
||||
affected: {
|
||||
workflows: deletionStats.workflows,
|
||||
subfolders: deletionStats.folders - 1,
|
||||
},
|
||||
},
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -281,6 +281,7 @@ export async function DELETE(
|
||||
resourceId: documentId,
|
||||
resourceName: accessCheck.document?.filename,
|
||||
description: `Deleted document "${documentId}" from knowledge base "${knowledgeBaseId}"`,
|
||||
metadata: { fileName: accessCheck.document?.filename },
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -255,6 +255,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
resourceId: knowledgeBaseId,
|
||||
resourceName: `${createdDocuments.length} document(s)`,
|
||||
description: `Uploaded ${createdDocuments.length} document(s) to knowledge base "${knowledgeBaseId}"`,
|
||||
metadata: {
|
||||
fileCount: createdDocuments.length,
|
||||
fileNames: createdDocuments.map((doc) => doc.filename),
|
||||
},
|
||||
request: req,
|
||||
})
|
||||
|
||||
@@ -316,6 +320,11 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
resourceId: knowledgeBaseId,
|
||||
resourceName: validatedData.filename,
|
||||
description: `Uploaded document "${validatedData.filename}" to knowledge base "${knowledgeBaseId}"`,
|
||||
metadata: {
|
||||
fileName: validatedData.filename,
|
||||
fileType: validatedData.mimeType,
|
||||
fileSize: validatedData.fileSize,
|
||||
},
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -598,7 +598,12 @@ export async function PUT(
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Organization invitation ${status} for ${orgInvitation.email}`,
|
||||
metadata: { invitationId, email: orgInvitation.email, status },
|
||||
metadata: {
|
||||
invitationId,
|
||||
targetEmail: orgInvitation.email,
|
||||
targetRole: orgInvitation.role,
|
||||
status,
|
||||
},
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -423,7 +423,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
resourceName: organizationEntry[0]?.name,
|
||||
description: `Invited ${inv.email} to organization as ${role}`,
|
||||
metadata: { invitationId: inv.id, email: inv.email, role },
|
||||
metadata: { invitationId: inv.id, targetEmail: inv.email, targetRole: role },
|
||||
request,
|
||||
})
|
||||
}
|
||||
@@ -558,7 +558,7 @@ export async function DELETE(
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Revoked organization invitation for ${result[0].email}`,
|
||||
metadata: { invitationId, email: result[0].email },
|
||||
metadata: { invitationId, targetEmail: result[0].email },
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -173,8 +173,15 @@ export async function PUT(
|
||||
}
|
||||
|
||||
const targetMember = await db
|
||||
.select()
|
||||
.select({
|
||||
id: member.id,
|
||||
role: member.role,
|
||||
userId: member.userId,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
})
|
||||
.from(member)
|
||||
.innerJoin(user, eq(member.userId, user.id))
|
||||
.where(and(eq(member.organizationId, organizationId), eq(member.userId, memberId)))
|
||||
.limit(1)
|
||||
|
||||
@@ -223,7 +230,12 @@ export async function PUT(
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Changed role for member ${memberId} to ${role}`,
|
||||
metadata: { targetUserId: memberId, newRole: role },
|
||||
metadata: {
|
||||
targetUserId: memberId,
|
||||
targetEmail: targetMember[0].email ?? undefined,
|
||||
targetName: targetMember[0].name ?? undefined,
|
||||
changes: [{ field: 'role', from: targetMember[0].role, to: role }],
|
||||
},
|
||||
request,
|
||||
})
|
||||
|
||||
@@ -286,8 +298,9 @@ export async function DELETE(
|
||||
}
|
||||
|
||||
const targetMember = await db
|
||||
.select({ id: member.id, role: member.role })
|
||||
.select({ id: member.id, role: member.role, email: user.email, name: user.name })
|
||||
.from(member)
|
||||
.innerJoin(user, eq(member.userId, user.id))
|
||||
.where(and(eq(member.organizationId, organizationId), eq(member.userId, targetUserId)))
|
||||
.limit(1)
|
||||
|
||||
@@ -331,7 +344,12 @@ export async function DELETE(
|
||||
session.user.id === targetUserId
|
||||
? 'Left the organization'
|
||||
: `Removed member ${targetUserId} from organization`,
|
||||
metadata: { targetUserId, wasSelfRemoval: session.user.id === targetUserId },
|
||||
metadata: {
|
||||
targetUserId,
|
||||
targetEmail: targetMember[0].email ?? undefined,
|
||||
targetName: targetMember[0].name ?? undefined,
|
||||
wasSelfRemoval: session.user.id === targetUserId,
|
||||
},
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Invited ${normalizedEmail} to organization as ${role}`,
|
||||
metadata: { invitationId, email: normalizedEmail, role },
|
||||
metadata: { invitationId, targetEmail: normalizedEmail, targetRole: role },
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -100,8 +100,9 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
const { userId } = addMemberSchema.parse(body)
|
||||
|
||||
const [orgMember] = await db
|
||||
.select({ id: member.id })
|
||||
.select({ id: member.id, email: user.email })
|
||||
.from(member)
|
||||
.innerJoin(user, eq(member.userId, user.id))
|
||||
.where(and(eq(member.userId, userId), eq(member.organizationId, result.group.organizationId)))
|
||||
.limit(1)
|
||||
|
||||
@@ -163,7 +164,11 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Added member ${userId} to permission group "${result.group.name}"`,
|
||||
metadata: { targetUserId: userId, permissionGroupId: id },
|
||||
metadata: {
|
||||
targetUserId: userId,
|
||||
targetEmail: orgMember.email ?? undefined,
|
||||
permissionGroupId: id,
|
||||
},
|
||||
request: req,
|
||||
})
|
||||
|
||||
@@ -218,8 +223,14 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
||||
}
|
||||
|
||||
const [memberToRemove] = await db
|
||||
.select()
|
||||
.select({
|
||||
id: permissionGroupMember.id,
|
||||
permissionGroupId: permissionGroupMember.permissionGroupId,
|
||||
userId: permissionGroupMember.userId,
|
||||
email: user.email,
|
||||
})
|
||||
.from(permissionGroupMember)
|
||||
.innerJoin(user, eq(permissionGroupMember.userId, user.id))
|
||||
.where(
|
||||
and(eq(permissionGroupMember.id, memberId), eq(permissionGroupMember.permissionGroupId, id))
|
||||
)
|
||||
@@ -247,7 +258,12 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Removed member ${memberToRemove.userId} from permission group "${result.group.name}"`,
|
||||
metadata: { targetUserId: memberToRemove.userId, memberId, permissionGroupId: id },
|
||||
metadata: {
|
||||
targetUserId: memberToRemove.userId,
|
||||
targetEmail: memberToRemove.email ?? undefined,
|
||||
memberId,
|
||||
permissionGroupId: id,
|
||||
},
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Reactivated schedule for workflow ${schedule.workflowId}`,
|
||||
metadata: { cronExpression: schedule.cronExpression, timezone: schedule.timezone },
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -271,6 +271,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||
resourceId: id,
|
||||
resourceName: workflowData?.name,
|
||||
description: `Deployed workflow "${workflowData?.name || id}"`,
|
||||
metadata: { version: deploymentVersionId },
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -347,6 +347,9 @@ export async function DELETE(
|
||||
resourceId: workflowId,
|
||||
resourceName: workflowData.name,
|
||||
description: `Deleted workflow "${workflowData.name}"`,
|
||||
metadata: {
|
||||
deleteTemplates: deleteTemplatesParam === 'delete',
|
||||
},
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
resourceId: workflowId,
|
||||
resourceName: workflowData.name ?? undefined,
|
||||
description: `Updated workflow variables`,
|
||||
metadata: { variableCount: Object.keys(variables).length },
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ export async function DELETE(
|
||||
.where(
|
||||
and(eq(apiKey.workspaceId, workspaceId), eq(apiKey.id, keyId), eq(apiKey.type, 'workspace'))
|
||||
)
|
||||
.returning({ id: apiKey.id, name: apiKey.name })
|
||||
.returning({ id: apiKey.id, name: apiKey.name, lastUsed: apiKey.lastUsed })
|
||||
|
||||
if (deletedRows.length === 0) {
|
||||
return NextResponse.json({ error: 'API key not found' }, { status: 404 })
|
||||
@@ -155,6 +155,7 @@ export async function DELETE(
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
resourceName: deletedKey.name,
|
||||
description: `Revoked workspace API key: ${deletedKey.name}`,
|
||||
metadata: { lastUsed: deletedKey.lastUsed?.toISOString() ?? null },
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -56,6 +56,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
resourceId: result.id,
|
||||
resourceName: name,
|
||||
description: `Duplicated workspace to "${name}"`,
|
||||
metadata: {
|
||||
sourceWorkspaceId,
|
||||
affected: { workflows: result.workflowsCount, folders: result.foldersCount },
|
||||
},
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||
resourceType: AuditResourceType.ENVIRONMENT,
|
||||
resourceId: workspaceId,
|
||||
description: `Updated environment variables`,
|
||||
metadata: { keysUpdated: Object.keys(variables) },
|
||||
metadata: { variableCount: Object.keys(variables).length },
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from 'crypto'
|
||||
import { db } from '@sim/db'
|
||||
import { permissions, workspace, workspaceEnvironment } from '@sim/db/schema'
|
||||
import { permissions, user, workspace, workspaceEnvironment } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
@@ -132,6 +132,21 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
||||
)
|
||||
}
|
||||
|
||||
// Capture existing permissions and user info for audit metadata
|
||||
const existingPerms = await db
|
||||
.select({
|
||||
userId: permissions.userId,
|
||||
permissionType: permissions.permissionType,
|
||||
email: user.email,
|
||||
})
|
||||
.from(permissions)
|
||||
.innerJoin(user, eq(permissions.userId, user.id))
|
||||
.where(and(eq(permissions.entityType, 'workspace'), eq(permissions.entityId, workspaceId)))
|
||||
|
||||
const permLookup = new Map(
|
||||
existingPerms.map((p) => [p.userId, { permission: p.permissionType, email: p.email }])
|
||||
)
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
for (const update of body.updates) {
|
||||
await tx
|
||||
@@ -182,7 +197,17 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Changed permissions for user ${update.userId} to ${update.permissions}`,
|
||||
metadata: { targetUserId: update.userId, newPermissions: update.permissions },
|
||||
metadata: {
|
||||
targetUserId: update.userId,
|
||||
targetEmail: permLookup.get(update.userId)?.email ?? undefined,
|
||||
changes: [
|
||||
{
|
||||
field: 'permissions',
|
||||
from: permLookup.get(update.userId)?.permission ?? null,
|
||||
to: update.permissions,
|
||||
},
|
||||
],
|
||||
},
|
||||
request,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -237,6 +237,7 @@ export async function DELETE(
|
||||
.limit(1)
|
||||
|
||||
// Delete workspace and all related data in a transaction
|
||||
let workspaceWorkflowCount = 0
|
||||
await db.transaction(async (tx) => {
|
||||
// Get all workflows in this workspace before deletion
|
||||
const workspaceWorkflows = await tx
|
||||
@@ -244,6 +245,8 @@ export async function DELETE(
|
||||
.from(workflow)
|
||||
.where(eq(workflow.workspaceId, workspaceId))
|
||||
|
||||
workspaceWorkflowCount = workspaceWorkflows.length
|
||||
|
||||
if (workspaceWorkflows.length > 0) {
|
||||
const workflowIds = workspaceWorkflows.map((w) => w.id)
|
||||
|
||||
@@ -299,6 +302,12 @@ export async function DELETE(
|
||||
resourceId: workspaceId,
|
||||
resourceName: workspaceRecord?.name,
|
||||
description: `Deleted workspace "${workspaceRecord?.name || workspaceId}"`,
|
||||
metadata: {
|
||||
affected: {
|
||||
workflows: workspaceWorkflowCount,
|
||||
},
|
||||
deleteTemplates,
|
||||
},
|
||||
request,
|
||||
})
|
||||
|
||||
|
||||
@@ -189,6 +189,7 @@ export async function GET(
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
resourceName: workspaceDetails.name,
|
||||
description: `Accepted workspace invitation to "${workspaceDetails.name}"`,
|
||||
metadata: { targetEmail: invitation.email },
|
||||
request: req,
|
||||
})
|
||||
|
||||
@@ -255,7 +256,7 @@ export async function DELETE(
|
||||
actorName: session.user.name ?? undefined,
|
||||
actorEmail: session.user.email ?? undefined,
|
||||
description: `Revoked workspace invitation for ${invitation.email}`,
|
||||
metadata: { invitationId, email: invitation.email },
|
||||
metadata: { invitationId, targetEmail: invitation.email },
|
||||
request: _request,
|
||||
})
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ export async function POST(req: NextRequest) {
|
||||
resourceId: workspaceId,
|
||||
resourceName: email,
|
||||
description: `Invited ${email} as ${permission}`,
|
||||
metadata: { email, role: permission },
|
||||
metadata: { targetEmail: email, targetRole: permission },
|
||||
request: req,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user