Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- [EE] Added three new audit actions covering the full org membership lifecycle: `org.member_added`, `org.member_removed`, and `org.member_left`. [#1165](https://github.com/sourcebot-dev/sourcebot/pull/1165)

## [4.17.0] - 2026-04-30

### Added
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/configuration/audit-logs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
| `user.signed_out` | `user` | `user` |
| `org.member_promoted_to_owner` | `user` | `user` |
| `org.owner_demoted_to_member` | `user` | `user` |
| `org.member_added` | `user` | `user` |
| `org.member_removed` | `user` | `user` |
| `org.member_left` | `user` | `user` |


## Response schema
Expand Down
10 changes: 10 additions & 0 deletions packages/web/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,16 @@ export const approveAccountRequest = async (requestId: string) => sew(async () =
type: "account_join_request"
}
});

await auditService.createAudit({
action: "org.member_added",
actor: { id: user.id, type: "user" },
target: { id: request.requestedById, type: "user" },
orgId: org.id,
metadata: {
message: `${user.id} approved join request ${requestId} for ${request.requestedById}`,
},
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return {
success: true,
}
Expand Down
20 changes: 20 additions & 0 deletions packages/web/src/app/invite/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ export const joinOrganization = async (inviteLinkId?: string) => sew(async () =>
return addUserToOrgRes;
}

await auditService.createAudit({
action: "org.member_added",
actor: { id: user.id, type: "user" },
target: { id: user.id, type: "user" },
orgId: org.id,
metadata: {
message: `${user.id} joined the organization via invite link`,
},
});

return {
success: true,
}
Expand Down Expand Up @@ -135,6 +145,16 @@ export const redeemInvite = async (inviteId: string): Promise<{ success: boolean
}
});

await auditService.createAudit({
action: "org.member_added",
actor: { id: user.id, type: "user" },
target: { id: user.id, type: "user" },
orgId: invite.org.id,
metadata: {
message: `${user.id} joined the organization by accepting invite ${inviteId}`,
},
});

return {
success: true,
};
Expand Down
25 changes: 24 additions & 1 deletion packages/web/src/features/userManagement/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import { ErrorCode } from "@/lib/errorCodes";
import { notFound, ServiceError } from "@/lib/serviceError";
import { withAuth } from "@/middleware/withAuth";
import { withMinimumOrgRole } from "@/middleware/withMinimumOrgRole";
import { getAuditService } from "@/ee/features/audit/factory";
import { OrgRole, Prisma } from "@sourcebot/db";
import { StatusCodes } from "http-status-codes";

const auditService = getAuditService();

export const removeMemberFromOrg = async (memberId: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
withAuth(async ({ org, role, prisma }) =>
withAuth(async ({ user, org, role, prisma }) =>
withMinimumOrgRole(role, OrgRole.OWNER, async () => {
const guardError = await prisma.$transaction(async (tx) => {
const targetMember = await tx.userToOrg.findUnique({
Expand Down Expand Up @@ -58,6 +61,16 @@ export const removeMemberFromOrg = async (memberId: string): Promise<{ success:
return guardError;
}

await auditService.createAudit({
action: "org.member_removed",
actor: { id: user.id, type: "user" },
target: { id: memberId, type: "user" },
orgId: org.id,
metadata: {
message: `${user.id} removed ${memberId} from the organization`,
},
});

return { success: true };
}))
);
Expand Down Expand Up @@ -98,6 +111,16 @@ export const leaveOrg = async (): Promise<{ success: boolean } | ServiceError> =
return guardError;
}

await auditService.createAudit({
action: "org.member_left",
actor: { id: user.id, type: "user" },
target: { id: user.id, type: "user" },
orgId: org.id,
metadata: {
message: `${user.id} left the organization`,
},
});

return {
success: true,
}
Expand Down
20 changes: 20 additions & 0 deletions packages/web/src/lib/authUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
type: "org"
}
});

await auditService.createAudit({
action: "org.member_added",
actor: { id: user.id, type: "user" },
target: { id: user.id, type: "user" },
orgId: SINGLE_TENANT_ORG_ID,
metadata: {
message: `${user.id} joined the organization as the initial owner`,
},
});
} else if (!defaultOrg.memberApprovalRequired) {
const hasAvailability = await orgHasAvailability();
if (!hasAvailability) {
Expand All @@ -118,6 +128,16 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
role: OrgRole.MEMBER,
}
});

await auditService.createAudit({
action: "org.member_added",
actor: { id: user.id, type: "user" },
target: { id: user.id, type: "user" },
orgId: SINGLE_TENANT_ORG_ID,
metadata: {
message: `${user.id} joined the organization (member approval not required)`,
},
});
}

// Dynamic import to avoid circular dependency:
Expand Down
Loading