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
This commit is contained in:
@@ -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<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<void> => {
|
||||||
|
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<unknown[]> => {
|
||||||
|
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 [];
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user