import { Pool } from "pg"; const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); export interface AuditLogEntry { userId?: string; userEmail?: string; action: string; resource: string; resourceId?: string; details?: Record; ipAddress?: string; userAgent?: string; } /** * Log an audit event */ export async function log(entry: AuditLogEntry): Promise { try { const id = crypto.randomUUID(); await pool.query( `INSERT INTO "audit_log" (id, user_id, user_email, action, resource, resource_id, details, ip_address, user_agent) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [ id, entry.userId || null, entry.userEmail || null, entry.action, entry.resource, entry.resourceId || null, entry.details ? JSON.stringify(entry.details) : null, entry.ipAddress || null, entry.userAgent || null, ] ); } catch (error) { console.error("Failed to write audit log:", error); // Don't throw - audit logging should not break the main flow } } // Convenience functions for common audit events export const audit = { log, // Auth events signIn: (userId: string, email: string, ipAddress?: string, userAgent?: string) => log({ userId, userEmail: email, action: "sign_in", resource: "auth", ipAddress, userAgent, }), signOut: (userId: string, email: string, ipAddress?: string) => log({ userId, userEmail: email, action: "sign_out", resource: "auth", ipAddress, }), signInFailed: (email: string, reason: string, ipAddress?: string) => log({ userEmail: email, action: "sign_in_failed", resource: "auth", details: { reason }, ipAddress, }), // User management events userCreated: (actorId: string, actorEmail: string, targetUserId: string, targetEmail: string) => log({ userId: actorId, userEmail: actorEmail, action: "create", resource: "user", resourceId: targetUserId, details: { targetEmail }, }), userUpdated: (actorId: string, actorEmail: string, targetUserId: string, changes: Record) => log({ userId: actorId, userEmail: actorEmail, action: "update", resource: "user", resourceId: targetUserId, details: { changes }, }), userDeleted: (actorId: string, actorEmail: string, targetUserId: string, targetEmail: string) => log({ userId: actorId, userEmail: actorEmail, action: "delete", resource: "user", resourceId: targetUserId, details: { targetEmail }, }), userBanned: (actorId: string, actorEmail: string, targetUserId: string, reason?: string) => log({ userId: actorId, userEmail: actorEmail, action: "ban", resource: "user", resourceId: targetUserId, details: { reason }, }), userUnbanned: (actorId: string, actorEmail: string, targetUserId: string) => log({ userId: actorId, userEmail: actorEmail, action: "unban", resource: "user", resourceId: targetUserId, }), userRoleChanged: (actorId: string, actorEmail: string, targetUserId: string, oldRole: string, newRole: string) => log({ userId: actorId, userEmail: actorEmail, action: "role_change", resource: "user", resourceId: targetUserId, details: { oldRole, newRole }, }), // Role management events roleCreated: (actorId: string, actorEmail: string, roleId: string, roleName: string) => log({ userId: actorId, userEmail: actorEmail, action: "create", resource: "role", resourceId: roleId, details: { roleName }, }), roleUpdated: (actorId: string, actorEmail: string, roleId: string, changes: Record) => log({ userId: actorId, userEmail: actorEmail, action: "update", resource: "role", resourceId: roleId, details: { changes }, }), roleDeleted: (actorId: string, actorEmail: string, roleId: string, roleName: string) => log({ userId: actorId, userEmail: actorEmail, action: "delete", resource: "role", resourceId: roleId, details: { roleName }, }), // Settings events settingsUpdated: (actorId: string, actorEmail: string, setting: string, oldValue: unknown, newValue: unknown) => log({ userId: actorId, userEmail: actorEmail, action: "update", resource: "settings", resourceId: setting, details: { oldValue, newValue }, }), }; /** * Purge old audit logs based on retention policy */ export async function purgeOldLogs(retentionDays?: number): Promise { const days = retentionDays || parseInt(process.env.AUDIT_LOG_RETENTION_DAYS || "90"); try { const result = await pool.query( `DELETE FROM "audit_log" WHERE timestamp < NOW() - INTERVAL '${days} days'` ); return result.rowCount || 0; } catch (error) { console.error("Failed to purge audit logs:", error); return 0; } }