feat: add request correlation IDs to audit logs and response headers

This commit is contained in:
SnapOtter
2026-06-13 17:04:27 +08:00
parent c1dc27f248
commit 1cf1f47d6f
10 changed files with 81 additions and 68 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ import { and, eq } from "drizzle-orm";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { z } from "zod";
import { db, schema } from "../db/index.js";
import { auditLog } from "../lib/audit.js";
import { auditFromRequest } from "../lib/audit.js";
import { getPermissions, hasEffectivePermission } from "../permissions.js";
import { computeKeyPrefix, hashPassword, requireAuth } from "../plugins/auth.js";
@@ -86,7 +86,7 @@ export async function apiKeyRoutes(app: FastifyInstance): Promise<void> {
return reply.status(409).send({ error: "Failed to create API key" });
}
await auditLog(request.log, "API_KEY_CREATED", { userId: user.id, keyId: id, keyName: name }, request.ip);
await auditFromRequest(request)("API_KEY_CREATED", { userId: user.id, keyId: id, keyName: name });
// Return the raw key ONCE — it cannot be retrieved again
return reply.status(201).send({
@@ -155,7 +155,7 @@ export async function apiKeyRoutes(app: FastifyInstance): Promise<void> {
await db.delete(schema.apiKeys).where(eq(schema.apiKeys.id, id));
await auditLog(request.log, "API_KEY_DELETED", { userId: user.id, keyId: id }, request.ip);
await auditFromRequest(request)("API_KEY_DELETED", { userId: user.id, keyId: id });
return reply.send({ ok: true });
},
+3 -3
View File
@@ -3,7 +3,7 @@ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { z } from "zod";
import { env } from "../../config.js";
import { db, schema } from "../../db/index.js";
import { auditLog } from "../../lib/audit.js";
import { auditFromRequest } from "../../lib/audit.js";
import { encrypt, isEncrypted } from "../../lib/encryption.js";
import { requirePermission } from "../../permissions.js";
@@ -115,11 +115,11 @@ export async function registerSiemRoutes(app: FastifyInstance): Promise<void> {
await db.insert(schema.settings).values({ key: SETTINGS_KEY, value });
}
await auditLog(request.log, "SETTINGS_UPDATED", {
await auditFromRequest(request)("SETTINGS_UPDATED", {
adminId: user.id,
username: user.username,
keys: [SETTINGS_KEY],
}, request.ip);
});
return reply.send({ ok: true });
},
+5 -5
View File
@@ -4,7 +4,7 @@ import { eq, sql } from "drizzle-orm";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { z } from "zod";
import { db, schema } from "../db/index.js";
import { auditLog } from "../lib/audit.js";
import { auditFromRequest } from "../lib/audit.js";
import { requirePermission } from "../permissions.js";
const ALL_PERMISSIONS: Permission[] = [
@@ -116,7 +116,7 @@ export async function rolesRoutes(app: FastifyInstance): Promise<void> {
createdBy: user.id,
});
await auditLog(request.log, "ROLE_CREATED", { adminId: user.id, roleId: id, roleName: name }, request.ip);
await auditFromRequest(request)("ROLE_CREATED", { adminId: user.id, roleId: id, roleName: name });
return reply.status(201).send({
id,
@@ -185,7 +185,7 @@ export async function rolesRoutes(app: FastifyInstance): Promise<void> {
}
await tx.update(schema.roles).set(updates).where(eq(schema.roles.id, id));
});
await auditLog(request.log, "ROLE_UPDATED", { adminId: user.id, roleId: id }, request.ip);
await auditFromRequest(request)("ROLE_UPDATED", { adminId: user.id, roleId: id });
return reply.send({ ok: true });
},
@@ -216,11 +216,11 @@ export async function rolesRoutes(app: FastifyInstance): Promise<void> {
.where(eq(schema.users.role, role.name));
await tx.delete(schema.roles).where(eq(schema.roles.id, id));
});
await auditLog(request.log, "ROLE_DELETED", {
await auditFromRequest(request)("ROLE_DELETED", {
adminId: user.id,
roleId: id,
roleName: role.name,
}, request.ip);
});
return reply.send({ ok: true });
},
+3 -3
View File
@@ -10,7 +10,7 @@ import { eq } from "drizzle-orm";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { z } from "zod";
import { db, schema } from "../db/index.js";
import { auditLog } from "../lib/audit.js";
import { auditFromRequest } from "../lib/audit.js";
import { env } from "../config.js";
import { encrypt, decrypt, isEncrypted } from "../lib/encryption.js";
import { requirePermission } from "../permissions.js";
@@ -115,11 +115,11 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
}
if (entries.length > 0) {
await auditLog(request.log, "SETTINGS_UPDATED", {
await auditFromRequest(request)("SETTINGS_UPDATED", {
adminId: admin.id,
username: admin.username,
keys: entries.map((e) => e.key),
}, request.ip);
});
}
return reply.send({ ok: true, updatedCount: entries.length });
+11 -16
View File
@@ -471,25 +471,20 @@ export function createToolRoute<T>(app: FastifyInstance, config: ToolRouteConfig
// Fire-and-forget: audit log must never block the response
import("../lib/audit.js")
.then(({ isToolAuditEnabled, auditLog }) =>
.then(({ isToolAuditEnabled, auditFromRequest }) =>
isToolAuditEnabled().then((enabled) => {
if (!enabled) return;
const user = getAuthUser(request);
return auditLog(
request.log,
"TOOL_EXECUTED",
{
userId: user?.id,
username: user?.username,
toolId: config.toolId,
inputFileCount: received.length,
totalInputSize: received.reduce((sum, r) => sum + r.size, 0),
outputFormat: (settings as Record<string, unknown>)?.format ?? null,
status: "success",
durationMs: Date.now() - startTime,
},
request.ip,
);
return auditFromRequest(request)("TOOL_EXECUTED", {
userId: user?.id,
username: user?.username,
toolId: config.toolId,
inputFileCount: received.length,
totalInputSize: received.reduce((sum, r) => sum + r.size, 0),
outputFormat: (settings as Record<string, unknown>)?.format ?? null,
status: "success",
durationMs: Date.now() - startTime,
});
}),
)
.catch(() => {});
+6 -6
View File
@@ -17,7 +17,7 @@ import sharp from "sharp";
import { z } from "zod";
import { env } from "../config.js";
import { db, schema } from "../db/index.js";
import { auditLog } from "../lib/audit.js";
import { auditFromRequest } from "../lib/audit.js";
import {
deleteStoredFile,
deleteThumbnail,
@@ -259,11 +259,11 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
return reply.status(400).send({ error: "No valid files uploaded" });
}
await auditLog(request.log, "FILE_UPLOADED", {
await auditFromRequest(request)("FILE_UPLOADED", {
userId,
count: created.length,
files: created.map((f) => f.originalName),
}, request.ip);
});
return reply.status(201).send({ files: created });
},
@@ -495,7 +495,7 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
.map((f) => f.id);
if (validIds.length === 0) {
await auditLog(request.log, "FILE_DELETED", { userId: user.id, count: 0, ids }, request.ip);
await auditFromRequest(request)("FILE_DELETED", { userId: user.id, count: 0, ids });
return reply.send({ deleted: 0 });
}
@@ -542,11 +542,11 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
await db.delete(schema.userFiles).where(inArray(schema.userFiles.id, chainIds));
}
await auditLog(request.log, "FILE_DELETED", {
await auditFromRequest(request)("FILE_DELETED", {
userId: user.id,
count: chainRows.length,
ids,
}, request.ip);
});
return reply.send({ deleted: chainRows.length });
});