Skip to content

Commit 4aa5923

Browse files
committed
add proper audit log for audit fetch and proper handling of api key hash in audit
1 parent 47adbb4 commit 4aa5923

File tree

8 files changed

+54
-40
lines changed

8 files changed

+54
-40
lines changed

packages/web/src/actions.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> =>
6161
}
6262
}
6363

64-
export const withAuth = async <T>(fn: (userId: string) => Promise<T>, allowSingleTenantUnauthedAccess: boolean = false, apiKey: ApiKeyPayload | undefined = undefined) => {
64+
export const withAuth = async <T>(fn: (userId: string, apiKeyHash: string | undefined) => Promise<T>, allowSingleTenantUnauthedAccess: boolean = false, apiKey: ApiKeyPayload | undefined = undefined) => {
6565
const session = await auth();
6666

6767
if (!session) {
@@ -95,7 +95,7 @@ export const withAuth = async <T>(fn: (userId: string) => Promise<T>, allowSingl
9595
},
9696
});
9797

98-
return fn(user.id);
98+
return fn(user.id, apiKeyOrError.apiKey.hash);
9999
} else if (
100100
env.SOURCEBOT_TENANCY_MODE === 'single' &&
101101
allowSingleTenantUnauthedAccess &&
@@ -109,11 +109,11 @@ export const withAuth = async <T>(fn: (userId: string) => Promise<T>, allowSingl
109109
}
110110

111111
// To support unauthed access a guest user is created in initialize.ts, which we return here
112-
return fn(SOURCEBOT_GUEST_USER_ID);
112+
return fn(SOURCEBOT_GUEST_USER_ID, undefined);
113113
}
114114
return notAuthenticated();
115115
}
116-
return fn(session.user.id);
116+
return fn(session.user.id, undefined);
117117
}
118118

119119
export const orgHasAvailability = async (domain: string): Promise<boolean> => {
@@ -505,10 +505,7 @@ export const createApiKey = async (name: string, domain: string): Promise<{ key:
505505
id: apiKey.hash,
506506
type: "api_key"
507507
},
508-
orgId: org.id,
509-
metadata: {
510-
api_key: name
511-
}
508+
orgId: org.id
512509
});
513510

514511
return {
@@ -1058,15 +1055,6 @@ export const createInvites = async (emails: string[], domain: string): Promise<{
10581055
} satisfies ServiceError;
10591056
}
10601057

1061-
const invites = await prisma.invite.createMany({
1062-
data: emails.map((email) => ({
1063-
recipientEmail: email,
1064-
hostUserId: userId,
1065-
orgId: org.id,
1066-
})),
1067-
skipDuplicates: true,
1068-
});
1069-
10701058
// Send invites to recipients
10711059
if (env.SMTP_CONNECTION_URL && env.EMAIL_FROM_ADDRESS) {
10721060
const origin = (await headers()).get('origin')!;

packages/web/src/app/api/(server)/ee/audit/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { serviceErrorResponse } from "@/lib/serviceError";
77
import { StatusCodes } from "http-status-codes";
88
import { ErrorCode } from "@/lib/errorCodes";
99
import { env } from "@/env.mjs";
10-
import { getEntitlements } from "@/features/entitlements/server";
10+
import { getEntitlements } from "@sourcebot/shared";
1111

1212
export const GET = async (request: NextRequest) => {
1313
const domain = request.headers.get("X-Org-Domain");
@@ -23,7 +23,7 @@ export const GET = async (request: NextRequest) => {
2323

2424
if (env.SOURCEBOT_EE_AUDIT_LOGGING_ENABLED === 'false') {
2525
return serviceErrorResponse({
26-
statusCode: StatusCodes.FORBIDDEN,
26+
statusCode: StatusCodes.NOT_FOUND,
2727
errorCode: ErrorCode.NOT_FOUND,
2828
message: "Audit logging is not enabled",
2929
});
@@ -37,7 +37,7 @@ export const GET = async (request: NextRequest) => {
3737
message: "Audit logging is not enabled for your license",
3838
});
3939
}
40-
40+
4141
const result = await fetchAuditRecords(domain, apiKey);
4242
if (isServiceError(result)) {
4343
return serviceErrorResponse(result);

packages/web/src/ee/features/audit/auditService.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { IAuditService, AuditEvent } from '@/ee/features/audit/types';
22
import { prisma } from '@/prisma';
33
import { Audit } from '@prisma/client';
4+
import { createLogger } from '@sourcebot/logger';
5+
6+
const logger = createLogger('audit-service');
47

58
export class AuditService implements IAuditService {
69
async createAudit(event: Omit<AuditEvent, 'sourcebotVersion'>): Promise<Audit | null> {
710
const sourcebotVersion = process.env.NEXT_PUBLIC_SOURCEBOT_VERSION || 'unknown';
11+
12+
try {
813
const audit = await prisma.audit.create({
914
data: {
1015
action: event.action,
@@ -16,8 +21,12 @@ export class AuditService implements IAuditService {
1621
metadata: event.metadata,
1722
orgId: event.orgId,
1823
},
19-
});
24+
});
2025

21-
return audit;
26+
return audit;
27+
} catch (error) {
28+
logger.error(`Error creating audit event: ${error}`, { event });
29+
return null;
30+
}
2231
}
2332
}

packages/web/src/ee/features/audit/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ export type AuditTarget = z.infer<typeof auditTargetSchema>;
1616
export const auditMetadataSchema = z.object({
1717
message: z.string().optional(),
1818
api_key: z.string().optional(),
19-
email: z.string().optional(),
20-
emails: z.string().optional(),
19+
emails: z.string().optional(), // comma separated list of emails
2120
})
2221
export type AuditMetadata = z.infer<typeof auditMetadataSchema>;
2322

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { prisma } from "@/prisma";
2-
import { serviceErrorResponse } from "@/lib/serviceError";
32
import { ErrorCode } from "@/lib/errorCodes";
43
import { StatusCodes } from "http-status-codes";
54
import { sew, withAuth, withOrgMembership } from "@/actions";
65
import { OrgRole } from "@sourcebot/db";
6+
import { createLogger } from "@sourcebot/logger";
7+
import { ServiceError } from "@/lib/serviceError";
8+
import { getAuditService } from "@/ee/features/audit/factory";
9+
10+
const auditService = getAuditService();
11+
const logger = createLogger('audit-utils');
712

813
export const fetchAuditRecords = async (domain: string, apiKey: string | undefined = undefined) => sew(() =>
914
withAuth((userId) =>
@@ -18,14 +23,27 @@ export const fetchAuditRecords = async (domain: string, apiKey: string | undefin
1823
}
1924
});
2025

26+
await auditService.createAudit({
27+
action: "audit.fetch",
28+
actor: {
29+
id: userId,
30+
type: "user"
31+
},
32+
target: {
33+
id: org.id.toString(),
34+
type: "org"
35+
},
36+
orgId: org.id
37+
})
38+
2139
return auditRecords;
2240
} catch (error) {
23-
console.error('Error fetching audit logs:', error);
24-
return serviceErrorResponse({
25-
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
26-
errorCode: ErrorCode.UNEXPECTED_ERROR,
27-
message: "Failed to fetch audit logs",
28-
});
41+
logger.error('Error fetching audit logs', { error });
42+
return {
43+
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
44+
errorCode: ErrorCode.UNEXPECTED_ERROR,
45+
message: "Failed to fetch audit logs",
46+
} satisfies ServiceError;
2947
}
3048
}, /* minRequiredRole = */ OrgRole.OWNER), /* allowSingleTenantUnauthedAccess = */ true, apiKey ? { apiKey, domain } : undefined)
3149
);

packages/web/src/features/search/fileSourceApi.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { getAuditService } from "@/ee/features/audit/factory";
1515
const auditService = getAuditService();
1616

1717
export const getFileSource = async ({ fileName, repository, branch }: FileSourceRequest, domain: string, apiKey: string | undefined = undefined): Promise<FileSourceResponse | ServiceError> => sew(() =>
18-
withAuth((userId) =>
18+
withAuth((userId, apiKeyHash) =>
1919
withOrgMembership(userId, domain, async ({ org }) => {
2020
const escapedFileName = escapeStringRegexp(fileName);
2121
const escapedRepository = escapeStringRegexp(repository);
@@ -48,8 +48,8 @@ export const getFileSource = async ({ fileName, repository, branch }: FileSource
4848
await auditService.createAudit({
4949
action: "query.file_source",
5050
actor: {
51-
id: apiKey ? apiKey : userId,
52-
type: apiKey ? "api_key" : "user"
51+
id: apiKeyHash ?? userId,
52+
type: apiKeyHash ? "api_key" : "user"
5353
},
5454
orgId: org.id,
5555
target: {

packages/web/src/features/search/listReposApi.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { getAuditService } from "@/ee/features/audit/factory";
99
const auditService = getAuditService();
1010

1111
export const listRepositories = async (domain: string, apiKey: string | undefined = undefined): Promise<ListRepositoriesResponse | ServiceError> => sew(() =>
12-
withAuth((userId) =>
12+
withAuth((userId, apiKeyHash) =>
1313
withOrgMembership(userId, domain, async ({ org }) => {
1414
const body = JSON.stringify({
1515
opts: {
@@ -50,8 +50,8 @@ export const listRepositories = async (domain: string, apiKey: string | undefine
5050
await auditService.createAudit({
5151
action: "query.list_repositories",
5252
actor: {
53-
id: apiKey ? apiKey : userId,
54-
type: apiKey ? "api_key" : "user"
53+
id: apiKeyHash ?? userId,
54+
type: apiKeyHash ? "api_key" : "user"
5555
},
5656
target: {
5757
id: org.id.toString(),

packages/web/src/features/search/searchApi.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const getFileWebUrl = (template: string, branch: string, fileName: string): stri
128128
}
129129

130130
export const search = async ({ query, matches, contextLines, whole }: SearchRequest, domain: string, apiKey: string | undefined = undefined) => sew(() =>
131-
withAuth((userId) =>
131+
withAuth((userId, apiKeyHash) =>
132132
withOrgMembership(userId, domain, async ({ org }) => {
133133
const transformedQuery = await transformZoektQuery(query, org.id);
134134
if (isServiceError(transformedQuery)) {
@@ -304,8 +304,8 @@ export const search = async ({ query, matches, contextLines, whole }: SearchRequ
304304
await auditService.createAudit({
305305
action: "query.code_search",
306306
actor: {
307-
id: apiKey ? apiKey : userId,
308-
type: apiKey ? "api_key" : "user"
307+
id: apiKeyHash ?? userId,
308+
type: apiKeyHash ? "api_key" : "user"
309309
},
310310
target: {
311311
id: org.id.toString(),

0 commit comments

Comments
 (0)