From 2998fad8e73b0e41b442ddf8a7edcc7514068e98 Mon Sep 17 00:00:00 2001 From: Matteo Date: Sat, 24 Jan 2026 17:12:16 +0100 Subject: [PATCH] feat(security): add audit logging utility - Add logAuditEvent function for security event logging - Add getAuditLogs function for retrieving audit logs - Gracefully handles disabled feature or missing table - Feature disabled by default via config flag --- backend/src/utils/audit.ts | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 backend/src/utils/audit.ts diff --git a/backend/src/utils/audit.ts b/backend/src/utils/audit.ts new file mode 100644 index 0000000..ae3ac32 --- /dev/null +++ b/backend/src/utils/audit.ts @@ -0,0 +1,91 @@ +/** + * Audit logging utility for security events + */ +import { PrismaClient } from "../generated/client"; + +const prisma = new PrismaClient(); + +export interface AuditLogData { + userId?: string; + action: string; + resource?: string; + ipAddress?: string; + userAgent?: string; + details?: Record; +} + +/** + * Log a security event to the audit log + * This should be called for important security-related actions + * Gracefully handles missing audit log table (feature disabled) + */ +export const logAuditEvent = async (data: AuditLogData): Promise => { + try { + // Check if audit logging is enabled via config + const { config } = await import("../config"); + if (!config.enableAuditLogging) { + return; // Feature disabled, silently skip + } + + await prisma.auditLog.create({ + data: { + userId: data.userId || null, + action: data.action, + resource: data.resource || null, + ipAddress: data.ipAddress || null, + userAgent: data.userAgent || null, + details: data.details ? JSON.stringify(data.details) : null, + }, + }); + } catch (error) { + // Don't fail the request if audit logging fails + // This handles cases where the table doesn't exist (feature disabled) + // or other database errors + if (process.env.NODE_ENV === "development") { + console.debug("Audit logging skipped (feature disabled or table missing):", error); + } + } +}; + +/** + * Get audit logs for a user (or all users if userId is not provided) + * Returns empty array if audit logging is disabled or table doesn't exist + */ +export const getAuditLogs = async ( + userId?: string, + limit: number = 100 +): Promise => { + try { + // Check if audit logging is enabled via config + const { config } = await import("../config"); + if (!config.enableAuditLogging) { + return []; // Feature disabled, return empty array + } + + const logs = await prisma.auditLog.findMany({ + where: userId ? { userId } : undefined, + orderBy: { createdAt: "desc" }, + take: limit, + include: { + user: { + select: { + id: true, + email: true, + name: true, + }, + }, + }, + }); + + return logs.map((log) => ({ + ...log, + details: log.details ? JSON.parse(log.details) : null, + })); + } catch (error) { + // Gracefully handle missing table or other errors + if (process.env.NODE_ENV === "development") { + console.debug("Failed to retrieve audit logs (feature disabled or table missing):", error); + } + return []; + } +};