improvement(audit-log): add resource names and specific invitation actions

This commit is contained in:
waleed
2026-02-18 00:51:40 -08:00
parent 6d406c1868
commit eaa3315ba0
12 changed files with 55 additions and 20 deletions

View File

@@ -14,6 +14,7 @@ async function getCredentialSetWithAccess(credentialSetId: string, userId: strin
const [set] = await db
.select({
id: credentialSet.id,
name: credentialSet.name,
organizationId: credentialSet.organizationId,
providerId: credentialSet.providerId,
})
@@ -186,7 +187,8 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
description: `Removed member "${memberId}" from credential set "${id}"`,
resourceName: result.set.name,
description: `Removed member from credential set "${result.set.name}"`,
request: req,
})

View File

@@ -215,7 +215,8 @@ export async function DELETE(
action: AuditAction.KNOWLEDGE_BASE_DELETED,
resourceType: AuditResourceType.KNOWLEDGE_BASE,
resourceId: id,
description: `Deleted knowledge base "${id}"`,
resourceName: accessCheck.knowledgeBase.name,
description: `Deleted knowledge base "${accessCheck.knowledgeBase.name || id}"`,
request: _request,
})

View File

@@ -99,7 +99,7 @@ export interface EmbeddingData {
export interface KnowledgeBaseAccessResult {
hasAccess: true
knowledgeBase: Pick<KnowledgeBaseData, 'id' | 'userId' | 'workspaceId'>
knowledgeBase: Pick<KnowledgeBaseData, 'id' | 'userId' | 'workspaceId' | 'name'>
}
export interface KnowledgeBaseAccessDenied {
@@ -113,7 +113,7 @@ export type KnowledgeBaseAccessCheck = KnowledgeBaseAccessResult | KnowledgeBase
export interface DocumentAccessResult {
hasAccess: true
document: DocumentData
knowledgeBase: Pick<KnowledgeBaseData, 'id' | 'userId' | 'workspaceId'>
knowledgeBase: Pick<KnowledgeBaseData, 'id' | 'userId' | 'workspaceId' | 'name'>
}
export interface DocumentAccessDenied {
@@ -128,7 +128,7 @@ export interface ChunkAccessResult {
hasAccess: true
chunk: EmbeddingData
document: DocumentData
knowledgeBase: Pick<KnowledgeBaseData, 'id' | 'userId' | 'workspaceId'>
knowledgeBase: Pick<KnowledgeBaseData, 'id' | 'userId' | 'workspaceId' | 'name'>
}
export interface ChunkAccessDenied {
@@ -151,6 +151,7 @@ export async function checkKnowledgeBaseAccess(
id: knowledgeBase.id,
userId: knowledgeBase.userId,
workspaceId: knowledgeBase.workspaceId,
name: knowledgeBase.name,
})
.from(knowledgeBase)
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))
@@ -193,6 +194,7 @@ export async function checkKnowledgeBaseWriteAccess(
id: knowledgeBase.id,
userId: knowledgeBase.userId,
workspaceId: knowledgeBase.workspaceId,
name: knowledgeBase.name,
})
.from(knowledgeBase)
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))

View File

@@ -553,13 +553,16 @@ export async function PUT(
email: orgInvitation.email,
})
const auditActionMap = {
accepted: AuditAction.ORG_INVITATION_ACCEPTED,
rejected: AuditAction.ORG_INVITATION_REJECTED,
cancelled: AuditAction.ORG_INVITATION_CANCELLED,
} as const
recordAudit({
workspaceId: null,
actorId: session.user.id,
action:
status === 'accepted'
? AuditAction.ORG_INVITATION_ACCEPTED
: AuditAction.ORG_INVITATION_UPDATED,
action: auditActionMap[status],
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: session.user.name ?? undefined,

View File

@@ -14,6 +14,7 @@ async function getPermissionGroupWithAccess(groupId: string, userId: string) {
const [group] = await db
.select({
id: permissionGroup.id,
name: permissionGroup.name,
organizationId: permissionGroup.organizationId,
})
.from(permissionGroup)
@@ -158,9 +159,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
action: AuditAction.PERMISSION_GROUP_MEMBER_ADDED,
resourceType: AuditResourceType.PERMISSION_GROUP,
resourceId: id,
resourceName: result.group.name,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
description: `Added member ${userId} to permission group`,
description: `Added member ${userId} to permission group "${result.group.name}"`,
metadata: { targetUserId: userId, permissionGroupId: id },
request: req,
})
@@ -241,9 +243,10 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
action: AuditAction.PERMISSION_GROUP_MEMBER_REMOVED,
resourceType: AuditResourceType.PERMISSION_GROUP,
resourceId: id,
resourceName: result.group.name,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
description: `Removed member ${memberToRemove.userId} from permission group`,
description: `Removed member ${memberToRemove.userId} from permission group "${result.group.name}"`,
metadata: { targetUserId: memberToRemove.userId, memberId, permissionGroupId: id },
request: req,
})

View File

@@ -35,12 +35,14 @@ export async function DELETE(
const result = await db
.delete(apiKey)
.where(and(eq(apiKey.id, keyId), eq(apiKey.userId, userId)))
.returning({ id: apiKey.id })
.returning({ id: apiKey.id, name: apiKey.name })
if (!result.length) {
return NextResponse.json({ error: 'API key not found' }, { status: 404 })
}
const deletedKey = result[0]
recordAudit({
workspaceId: null,
actorId: userId,
@@ -49,7 +51,8 @@ export async function DELETE(
resourceId: keyId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
description: `Revoked personal API key: ${keyId}`,
resourceName: deletedKey.name,
description: `Revoked personal API key: ${deletedKey.name}`,
request,
})

View File

@@ -137,12 +137,14 @@ export async function DELETE(
.where(
and(eq(apiKey.workspaceId, workspaceId), eq(apiKey.id, keyId), eq(apiKey.type, 'workspace'))
)
.returning({ id: apiKey.id })
.returning({ id: apiKey.id, name: apiKey.name })
if (deletedRows.length === 0) {
return NextResponse.json({ error: 'API key not found' }, { status: 404 })
}
const deletedKey = deletedRows[0]
recordAudit({
workspaceId,
actorId: userId,
@@ -151,7 +153,8 @@ export async function DELETE(
resourceId: keyId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
description: `Revoked workspace API key: ${keyId}`,
resourceName: deletedKey.name,
description: `Revoked workspace API key: ${deletedKey.name}`,
request,
})

View File

@@ -258,6 +258,7 @@ export async function PUT(request: NextRequest, { params }: RouteParams) {
action: AuditAction.NOTIFICATION_UPDATED,
resourceType: AuditResourceType.NOTIFICATION,
resourceId: notificationId,
resourceName: subscription.notificationType,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
description: `Updated ${subscription.notificationType} notification subscription`,
@@ -313,12 +314,17 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
eq(workspaceNotificationSubscription.workspaceId, workspaceId)
)
)
.returning({ id: workspaceNotificationSubscription.id })
.returning({
id: workspaceNotificationSubscription.id,
notificationType: workspaceNotificationSubscription.notificationType,
})
if (deleted.length === 0) {
return NextResponse.json({ error: 'Notification not found' }, { status: 404 })
}
const deletedSubscription = deleted[0]
logger.info('Deleted notification subscription', {
workspaceId,
subscriptionId: notificationId,
@@ -332,7 +338,8 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
resourceId: notificationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
description: `Deleted notification subscription`,
resourceName: deletedSubscription.notificationType,
description: `Deleted ${deletedSubscription.notificationType} notification subscription`,
request,
})

View File

@@ -263,6 +263,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
action: AuditAction.NOTIFICATION_CREATED,
resourceType: AuditResourceType.NOTIFICATION,
resourceId: subscription.id,
resourceName: data.notificationType,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
description: `Created ${data.notificationType} notification subscription`,

View File

@@ -229,6 +229,13 @@ export async function DELETE(
`Deleting workspace ${workspaceId} for user ${session.user.id}, deleteTemplates: ${deleteTemplates}`
)
// Fetch workspace name before deletion for audit logging
const [workspaceRecord] = await db
.select({ name: workspace.name })
.from(workspace)
.where(eq(workspace.id, workspaceId))
.limit(1)
// Delete workspace and all related data in a transaction
await db.transaction(async (tx) => {
// Get all workflows in this workspace before deletion
@@ -290,7 +297,8 @@ export async function DELETE(
action: AuditAction.WORKSPACE_DELETED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: workspaceId,
description: 'Deleted workspace',
resourceName: workspaceRecord?.name,
description: `Deleted workspace "${workspaceRecord?.name || workspaceId}"`,
request,
})

View File

@@ -89,7 +89,8 @@ export const AuditAction = {
ORG_MEMBER_ROLE_CHANGED: 'org_member.role_changed',
ORG_INVITATION_CREATED: 'org_invitation.created',
ORG_INVITATION_ACCEPTED: 'org_invitation.accepted',
ORG_INVITATION_UPDATED: 'org_invitation.updated',
ORG_INVITATION_REJECTED: 'org_invitation.rejected',
ORG_INVITATION_CANCELLED: 'org_invitation.cancelled',
ORG_INVITATION_REVOKED: 'org_invitation.revoked',
// Permission Groups

View File

@@ -62,7 +62,8 @@ export const auditMock = {
ORG_MEMBER_ROLE_CHANGED: 'org_member.role_changed',
ORG_INVITATION_CREATED: 'org_invitation.created',
ORG_INVITATION_ACCEPTED: 'org_invitation.accepted',
ORG_INVITATION_UPDATED: 'org_invitation.updated',
ORG_INVITATION_REJECTED: 'org_invitation.rejected',
ORG_INVITATION_CANCELLED: 'org_invitation.cancelled',
ORG_INVITATION_REVOKED: 'org_invitation.revoked',
PERMISSION_GROUP_CREATED: 'permission_group.created',
PERMISSION_GROUP_UPDATED: 'permission_group.updated',