Skip to content

Commit 5438298

Browse files
authored
feat(audit-logging): Adds audit logging support (#355)
* add audit factory skeleton * add additional audit events * add more audit logs * delete account join request when redeeming an invite * add audit event for account request removed * wip api to fetch audits * add check for audit with public access and entitlement * fix issues with merge * add docs for audit logs * add proper audit log for audit fetch and proper handling of api key hash in audit * format nit * feedback
1 parent 1d95e82 commit 5438298

File tree

24 files changed

+932
-43
lines changed

24 files changed

+932
-43
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Added audit logging. [#355](https://github.com/sourcebot-dev/sourcebot/pull/355)
1012
<!-- @NOTE: this release includes a API change that affects the MCP package (@sourcebot/mcp). On release, bump the MCP package's version and delete this message. -->
1113

1214
### Fixed

docs/docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
]
7676
},
7777
"docs/configuration/transactional-emails",
78-
"docs/configuration/structured-logging"
78+
"docs/configuration/structured-logging",
79+
"docs/configuration/audit-logs"
7980
]
8081
},
8182
{
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
---
2+
title: Audit Logs
3+
sidebarTitle: Audit logs
4+
---
5+
6+
import LicenseKeyRequired from '/snippets/license-key-required.mdx'
7+
8+
<LicenseKeyRequired />
9+
10+
Audit logs are a collection of notable events performed by users within a Sourcebot deployment. Each audit log records information on the action taken, the user who performed the
11+
action, and when the action took place.
12+
13+
This feature gives security and compliance teams the necessary information to ensure proper governance and administration of your Sourcebot deployment.
14+
15+
## Enabling Audit Logs
16+
Audit logs must be explicitly enabled by setting the `SOURCEBOT_EE_AUDIT_LOGGING_ENABLED` [environment variable](/docs/configuration/environment-variables) to `true`
17+
18+
## Fetching Audit Logs
19+
Audit logs are stored in the [postgres database](/docs/overview#architecture) connected to Sourcebot. To fetch all of the audit logs, you can use the following API:
20+
21+
```bash icon="terminal" Fetch audit logs
22+
curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
23+
--header 'X-Org-Domain: ~' \
24+
--header 'X-Sourcebot-Api-Key: $SOURCEBOT_OWNER_API_KEY'
25+
```
26+
27+
```json icon="brackets-curly" wrap expandable Fetch audit logs example response
28+
[
29+
{
30+
"id": "cmc146k7m0003xgo2tri5t4br",
31+
"timestamp": "2025-06-17T22:48:08.914Z",
32+
"action": "api_key.created",
33+
"actorId": "cmc12tnje0000xgn58jj8655h",
34+
"actorType": "user",
35+
"targetId": "205d1da1c6c3772b81d4ad697f5851fa11195176c211055ff0c1509772645d6d",
36+
"targetType": "api_key",
37+
"sourcebotVersion": "unknown",
38+
"orgId": 1
39+
},
40+
{
41+
"id": "cmc146c8r0001xgo2xyu0p463",
42+
"timestamp": "2025-06-17T22:47:58.587Z",
43+
"action": "query.code_search",
44+
"actorId": "cmc12tnje0000xgn58jj8655h",
45+
"actorType": "user",
46+
"targetId": "1",
47+
"targetType": "org",
48+
"sourcebotVersion": "unknown",
49+
"metadata": {
50+
"message": "render branch:HEAD"
51+
},
52+
"orgId": 1
53+
},
54+
{
55+
"id": "cmc12vqgb0008xgn5nv5hl9y5",
56+
"timestamp": "2025-06-17T22:11:44.171Z",
57+
"action": "query.code_search",
58+
"actorId": "cmc12tnje0000xgn58jj8655h",
59+
"actorType": "user",
60+
"targetId": "1",
61+
"targetType": "org",
62+
"sourcebotVersion": "unknown",
63+
"metadata": {
64+
"message": "render branch:HEAD"
65+
},
66+
"orgId": 1
67+
},
68+
{
69+
"id": "cmc12txwn0006xgn51ow1odid",
70+
"timestamp": "2025-06-17T22:10:20.519Z",
71+
"action": "query.code_search",
72+
"actorId": "cmc12tnje0000xgn58jj8655h",
73+
"actorType": "user",
74+
"targetId": "1",
75+
"targetType": "org",
76+
"sourcebotVersion": "unknown",
77+
"metadata": {
78+
"message": "render branch:HEAD"
79+
},
80+
"orgId": 1
81+
},
82+
{
83+
"id": "cmc12tnjx0004xgn5qqeiv1ao",
84+
"timestamp": "2025-06-17T22:10:07.101Z",
85+
"action": "user.owner_created",
86+
"actorId": "cmc12tnje0000xgn58jj8655h",
87+
"actorType": "user",
88+
"targetId": "1",
89+
"targetType": "org",
90+
"sourcebotVersion": "unknown",
91+
"metadata": null,
92+
"orgId": 1
93+
},
94+
{
95+
"id": "cmc12tnjh0002xgn5h6vzu3rl",
96+
"timestamp": "2025-06-17T22:10:07.086Z",
97+
"action": "user.signed_in",
98+
"actorId": "cmc12tnje0000xgn58jj8655h",
99+
"actorType": "user",
100+
"targetId": "cmc12tnje0000xgn58jj8655h",
101+
"targetType": "user",
102+
"sourcebotVersion": "unknown",
103+
"metadata": null,
104+
"orgId": 1
105+
}
106+
]
107+
```
108+
109+
## Audit action types
110+
111+
| Action | Actor Type | Target Type |
112+
| :------- | :------ | :------|
113+
| `api_key.creation_failed` | `user` | `org` |
114+
| `api_key.created` | `user` | `api_key` |
115+
| `api_key.deletion_failed` | `user` | `org` |
116+
| `api_key.deleted` | `user` | `api_key` |
117+
| `user.creation_failed` | `user` | `user` |
118+
| `user.owner_created` | `user` | `org` |
119+
| `user.jit_provisioning_failed` | `user` | `org` |
120+
| `user.jit_provisioned` | `user` | `org` |
121+
| `user.join_request_creation_failed` | `user` | `org` |
122+
| `user.join_requested` | `user` | `org` |
123+
| `user.join_request_approve_failed` | `user` | `account_join_request` |
124+
| `user.join_request_approved` | `user` | `account_join_request` |
125+
| `user.join_request_removed` | `user` | `account_join_request` |
126+
| `user.invite_failed` | `user` | `org` |
127+
| `user.invites_created` | `user` | `org` |
128+
| `user.invite_accept_failed` | `user` | `invite` |
129+
| `user.invite_accepted` | `user` | `invite` |
130+
| `user.signed_in` | `user` | `user` |
131+
| `user.signed_out` | `user` | `user` |
132+
| `org.ownership_transfer_failed` | `user` | `org` |
133+
| `org.ownership_transferred` | `user` | `org` |
134+
| `query.file_source` | `user \| api_key` | `file` |
135+
| `query.code_search` | `user \| api_key` | `org` |
136+
| `query.list_repositories` | `user \| api_key` | `org` |
137+
138+
139+
## Response schema
140+
141+
```json icon="brackets-curly" expandable wrap Audit log fetch response schema
142+
{
143+
"$schema": "http://json-schema.org/draft-07/schema#",
144+
"title": "FetchAuditLogsResponse",
145+
"type": "array",
146+
"items": {
147+
"type": "object",
148+
"required": [
149+
"id",
150+
"timestamp",
151+
"action",
152+
"actorId",
153+
"actorType",
154+
"targetId",
155+
"targetType",
156+
"sourcebotVersion",
157+
"metadata",
158+
"orgId"
159+
],
160+
"properties": {
161+
"id": {
162+
"type": "string"
163+
},
164+
"timestamp": {
165+
"type": "string",
166+
"format": "date-time"
167+
},
168+
"action": {
169+
"type": "string"
170+
},
171+
"actorId": {
172+
"type": "string"
173+
},
174+
"actorType": {
175+
"type": "string",
176+
"enum": ["user", "api_key"]
177+
},
178+
"targetId": {
179+
"type": "string"
180+
},
181+
"targetType": {
182+
"type": "string",
183+
"enum": ["user", "org", "file", "api_key", "account_join_request", "invite"]
184+
},
185+
"sourcebotVersion": {
186+
"type": "string"
187+
},
188+
"metadata": {
189+
"anyOf": [
190+
{
191+
"type": "object",
192+
"properties": {
193+
"message": { "type": "string" },
194+
"api_key": { "type": "string" },
195+
"emails": { "type": "string" }
196+
},
197+
"additionalProperties": false
198+
},
199+
{
200+
"type": "null"
201+
}
202+
]
203+
},
204+
"orgId": {
205+
"type": "integer"
206+
}
207+
},
208+
"additionalProperties": false
209+
}
210+
}
211+
212+
```

docs/docs/configuration/environment-variables.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ The following environment variables allow you to configure your Sourcebot deploy
3939
### Enterprise Environment Variables
4040
| Variable | Default | Description |
4141
| :------- | :------ | :---------- |
42+
| `SOURCEBOT_EE_AUDIT_LOGGING_ENABLED` | `false` | <p>Enables/disables audit logging</p> |
4243
| `AUTH_EE_ENABLE_JIT_PROVISIONING` | `false` | <p>Enables/disables just-in-time user provisioning for SSO providers.</p> |
4344
| `AUTH_EE_GITHUB_BASE_URL` | `https://github.com` | <p>The base URL for GitHub Enterprise SSO authentication.</p> |
4445
| `AUTH_EE_GITHUB_CLIENT_ID` | `-` | <p>The client ID for GitHub Enterprise SSO authentication.</p> |

docs/docs/configuration/structured-logging.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
2-
title: Structured logging
2+
title: Structured Logging
3+
sidebarTitle: Structured logging
34
---
45

56
By default, Sourcebot will output logs to the console in a human readable format. If you'd like Sourcebot to output structured JSON logs, set the following env vars:

docs/docs/deployment-guide.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Watch this 1:51 minute video to get a quick overview of how to deploy Sourcebot
3333
<Step title="Create a config.json">
3434
Create a `config.json` file that tells Sourcebot which repositories to sync and index:
3535

36-
```bash
36+
```bash wrap icon="terminal" Create example config
3737
touch config.json
3838
echo '{
3939
"$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json",
@@ -58,7 +58,7 @@ Watch this 1:51 minute video to get a quick overview of how to deploy Sourcebot
5858

5959
In the same directory as `config.json`, run the following command to start your instance:
6060

61-
``` bash
61+
``` bash icon="terminal" Start the Sourcebot container
6262
docker run \
6363
-p 3000:3000 \
6464
--pull=always \
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- CreateTable
2+
CREATE TABLE "Audit" (
3+
"id" TEXT NOT NULL,
4+
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
5+
"action" TEXT NOT NULL,
6+
"actorId" TEXT NOT NULL,
7+
"actorType" TEXT NOT NULL,
8+
"targetId" TEXT NOT NULL,
9+
"targetType" TEXT NOT NULL,
10+
"sourcebotVersion" TEXT NOT NULL,
11+
"metadata" JSONB,
12+
"orgId" INTEGER NOT NULL,
13+
14+
CONSTRAINT "Audit_pkey" PRIMARY KEY ("id")
15+
);
16+
17+
-- CreateIndex
18+
CREATE INDEX "Audit_actorId_actorType_targetId_targetType_orgId_idx" ON "Audit"("actorId", "actorType", "targetId", "targetType", "orgId");
19+
20+
-- AddForeignKey
21+
ALTER TABLE "Audit" ADD CONSTRAINT "Audit_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Org"("id") ON DELETE CASCADE ON UPDATE CASCADE;

packages/db/prisma/schema.prisma

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ model Org {
172172
/// List of pending invites to this organization
173173
invites Invite[]
174174
175+
audits Audit[]
176+
175177
accountRequests AccountRequest[]
176178
177179
searchContexts SearchContext[]
@@ -227,6 +229,24 @@ model ApiKey {
227229
228230
}
229231

232+
model Audit {
233+
id String @id @default(cuid())
234+
timestamp DateTime @default(now())
235+
236+
action String
237+
actorId String
238+
actorType String
239+
targetId String
240+
targetType String
241+
sourcebotVersion String
242+
metadata Json?
243+
244+
org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
245+
orgId Int
246+
247+
@@index([actorId, actorType, targetId, targetType, orgId])
248+
}
249+
230250
// @see : https://authjs.dev/concepts/database-models#user
231251
model User {
232252
id String @id @default(cuid())

packages/shared/src/entitlements.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,16 @@ const entitlements = [
3636
"public-access",
3737
"multi-tenancy",
3838
"sso",
39-
"code-nav"
39+
"code-nav",
40+
"audit"
4041
] as const;
4142
export type Entitlement = (typeof entitlements)[number];
4243

4344
const entitlementsByPlan: Record<Plan, Entitlement[]> = {
4445
oss: [],
4546
"cloud:team": ["billing", "multi-tenancy", "sso", "code-nav"],
46-
"self-hosted:enterprise": ["search-contexts", "sso", "code-nav"],
47-
"self-hosted:enterprise-unlimited": ["search-contexts", "public-access", "sso", "code-nav"],
47+
"self-hosted:enterprise": ["search-contexts", "sso", "code-nav", "audit"],
48+
"self-hosted:enterprise-unlimited": ["search-contexts", "public-access", "sso", "code-nav", "audit"],
4849
// Special entitlement for https://demo.sourcebot.dev
4950
"cloud:demo": ["public-access", "code-nav", "search-contexts"],
5051
} as const;

0 commit comments

Comments
 (0)