Skip to content

Commit 4b58c40

Browse files
committed
fix(invitations): record the invoked operation in invitation audit metadata
1 parent afd36e0 commit 4b58c40

3 files changed

Lines changed: 24 additions & 9 deletions

File tree

‎apps/sim/lib/invitations/__integration__/copilot-workspace-invitations.integration.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { db } from '@sim/db'
44
import {
5+
auditLog,
56
invitation,
67
invitationWorkspaceGrant,
78
member,
@@ -12,7 +13,7 @@ import {
1213
} from '@sim/db/schema'
1314
import { generateId } from '@sim/utils/id'
1415
import { and, eq, inArray } from 'drizzle-orm'
15-
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
16+
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
1617
import { withWorkspaceInvocationScope } from '@/lib/core/application/workspace-invocation-scope'
1718
import { createScopedCliTransport } from '@/lib/mothership/agent-cli/scoped-transport'
1819
import '@/app/api/v2/workspaces/[workspaceId]/invitations/route'
@@ -129,6 +130,7 @@ describe('chat-delegated workspace invitations', () => {
129130
await db
130131
.delete(invitation)
131132
.where(inArray(invitation.email, [newInvitee, pendingInvitee, writerInvitee]))
133+
await db.delete(auditLog).where(eq(auditLog.workspaceId, workspaceId))
132134
await db.delete(workspace).where(eq(workspace.id, workspaceId))
133135
await db.delete(organization).where(eq(organization.id, organizationId))
134136
await db.delete(user).where(inArray(user.id, [adminId, writerId]))
@@ -144,6 +146,19 @@ describe('chat-delegated workspace invitations', () => {
144146
const [created] = await pendingInvitationsFor(newInvitee)
145147
expect(created.inviterId).toBe(adminId)
146148
expect(await grantsOf(created.id)).toEqual([{ workspaceId, permission: 'write' }])
149+
await vi.waitFor(async () => {
150+
const [audit] = await db
151+
.select({ actorId: auditLog.actorId, metadata: auditLog.metadata })
152+
.from(auditLog)
153+
.where(and(eq(auditLog.workspaceId, workspaceId), eq(auditLog.resourceName, newInvitee)))
154+
expect(audit).toMatchObject({
155+
actorId: adminId,
156+
metadata: {
157+
operation: 'workspace_invitations.send_batch',
158+
actor: { kind: 'delegated', serviceId: 'copilot' },
159+
},
160+
})
161+
})
147162
})
148163

149164
it('adds the workspace to a pending organization invitation instead of creating another', async () => {

‎apps/sim/lib/invitations/application/send-invitation-batch.test.ts‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,12 +431,9 @@ describe('workspace invitation delegation', () => {
431431
}
432432
)
433433

434-
it('invites as the delegating user while they hold current admin authority', async () => {
434+
it('admits the delegating user while they hold current admin authority', async () => {
435435
const result = await sendWorkspaceInvitationBatch.execute({ principal: actor, input })
436436
expect(result).toMatchObject({ success: true, successful: ['person@example.com'] })
437-
expect(mocks.workspaceContext).toHaveBeenCalledWith(
438-
expect.objectContaining({ inviterId: 'admin-user', workspaceIds: ['workspace'] })
439-
)
440437
})
441438

442439
it('refuses an admin whose permission group disables invitations', async () => {

‎apps/sim/lib/invitations/application/send-invitation-batch.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { createLogger } from '@sim/logger'
1010
import { normalizeEmail } from '@sim/utils/string'
1111
import { eq } from 'drizzle-orm'
1212
import {
13+
type ApplicationOperation,
1314
assertOperationPrincipal,
1415
defineAuthorizedWorkspaceUseCase,
1516
ForbiddenOperationError,
@@ -78,13 +79,14 @@ export const sendInvitationBatch: OperationUseCase<
7879
async execute({ principal, input, request }) {
7980
requireAllowedWorkspacePrincipal(principal, invitationAuthorityOperations.workspace)
8081
assertOperationPrincipal(principal, invitationOperations.sendBatch)
81-
return executeInvitationBatch(principal, input, request)
82+
return executeInvitationBatch(principal, input, invitationOperations.sendBatch, request)
8283
},
8384
}
8485

8586
async function executeInvitationBatch(
8687
principal: Principal,
8788
input: SendInvitationBatchInput,
89+
operation: Pick<ApplicationOperation, 'id'>,
8890
request?: OrchestrationRequestContext
8991
): Promise<SendInvitationBatchResult> {
9092
const actorId = requirePrincipalSubjectUserId(principal)
@@ -151,7 +153,7 @@ async function executeInvitationBatch(
151153
email: inviter.email,
152154
metadata: {
153155
actor: toPrincipalActor(principal),
154-
operation: invitationOperations.sendBatch.id,
156+
operation: operation.id,
155157
},
156158
},
157159
}
@@ -333,7 +335,8 @@ export const sendWorkspaceInvitationBatch = defineAuthorizedWorkspaceUseCase({
333335
return resolveActiveWorkspaceApplicationContext(input.workspaceIds[0])
334336
},
335337
authorizationOptions: { delegation: { audience: 'sim:settings', isWithinScope: () => true } },
336-
execute: ({ principal, input, request }) => executeInvitationBatch(principal, input, request),
338+
execute: ({ principal, input, request }) =>
339+
executeInvitationBatch(principal, input, workspaceInvitationOperations.sendBatch, request),
337340
})
338341

339342
const organizationInvitationSendOperation = defineOrganizationOperation({
@@ -361,6 +364,6 @@ export const sendOrganizationInvitationBatch: OperationUseCase<
361364
await authorizeOrganizationOperation(principal, organizationInvitationSendOperation, {
362365
organizationId: input.organizationId,
363366
})
364-
return executeInvitationBatch(principal, input, request)
367+
return executeInvitationBatch(principal, input, organizationInvitationSendOperation, request)
365368
},
366369
}

0 commit comments

Comments
 (0)