/** * Comprehensive Audit Logging System for LocalGreenChain * * Provides complete transparency through immutable audit trails * tracking all system actions, data modifications, and user activities. */ import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; // Audit Entry Types export type AuditAction = | 'CREATE' | 'UPDATE' | 'DELETE' | 'READ' | 'QUERY' | 'TRANSFER' | 'VERIFY' | 'SIGN' | 'EXPORT' | 'LOGIN' | 'LOGOUT' | 'API_CALL' | 'ERROR' | 'PLANT_REGISTER' | 'PLANT_CLONE' | 'PLANT_TRANSFER' | 'TRANSPORT_EVENT' | 'TRANSPORT_VERIFY' | 'DEMAND_SIGNAL' | 'SUPPLY_COMMIT' | 'FARM_UPDATE' | 'BATCH_CREATE' | 'BATCH_HARVEST' | 'AGENT_TASK' | 'AGENT_ALERT' | 'SYSTEM_EVENT' | 'CONFIG_CHANGE'; export type AuditSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL'; export type AuditCategory = | 'PLANT' | 'TRANSPORT' | 'DEMAND' | 'SUPPLY' | 'FARM' | 'ENVIRONMENT' | 'USER' | 'SYSTEM' | 'AGENT' | 'BLOCKCHAIN' | 'API' | 'SECURITY'; export interface AuditActor { id: string; type: 'USER' | 'SYSTEM' | 'AGENT' | 'API' | 'ANONYMOUS'; name?: string; ip?: string; userAgent?: string; sessionId?: string; } export interface AuditResource { type: string; id: string; name?: string; previousState?: any; newState?: any; } export interface AuditEntry { id: string; timestamp: string; action: AuditAction; category: AuditCategory; severity: AuditSeverity; actor: AuditActor; resource?: AuditResource; description: string; metadata?: Record; correlationId?: string; parentId?: string; hash: string; previousHash: string; } export interface AuditQuery { startDate?: string; endDate?: string; actions?: AuditAction[]; categories?: AuditCategory[]; severities?: AuditSeverity[]; actorId?: string; actorType?: AuditActor['type']; resourceType?: string; resourceId?: string; correlationId?: string; searchTerm?: string; limit?: number; offset?: number; } export interface AuditStats { totalEntries: number; entriesByAction: Record; entriesByCategory: Record; entriesBySeverity: Record; entriesByActorType: Record; entriesLast24h: number; entriesLast7d: number; entriesLast30d: number; topActors: Array<{ id: string; count: number }>; topResources: Array<{ type: string; count: number }>; errorRate24h: number; lastEntryAt: string | null; } export interface AuditReport { generatedAt: string; period: { start: string; end: string }; stats: AuditStats; highlights: string[]; anomalies: AuditEntry[]; complianceStatus: { dataIntegrity: boolean; chainValid: boolean; noTampering: boolean; }; } class AuditLog { private entries: AuditEntry[] = []; private dataDir: string; private dataFile: string; private autoSaveInterval: NodeJS.Timeout | null = null; private genesisHash = '0'; constructor() { this.dataDir = path.join(process.cwd(), 'data'); this.dataFile = path.join(this.dataDir, 'audit-log.json'); this.load(); this.startAutoSave(); } private load(): void { try { if (fs.existsSync(this.dataFile)) { const data = JSON.parse(fs.readFileSync(this.dataFile, 'utf-8')); this.entries = data.entries || []; console.log(`[AuditLog] Loaded ${this.entries.length} audit entries`); } else { this.entries = []; this.logSystemEvent('Audit log initialized', 'INFO'); } } catch (error) { console.error('[AuditLog] Error loading audit log:', error); this.entries = []; } } private save(): void { try { if (!fs.existsSync(this.dataDir)) { fs.mkdirSync(this.dataDir, { recursive: true }); } fs.writeFileSync(this.dataFile, JSON.stringify({ entries: this.entries, exportedAt: new Date().toISOString(), version: '1.0.0' }, null, 2)); } catch (error) { console.error('[AuditLog] Error saving audit log:', error); } } private startAutoSave(): void { // Auto-save every 30 seconds this.autoSaveInterval = setInterval(() => this.save(), 30000); } private generateId(): string { return `audit_${Date.now()}_${crypto.randomBytes(8).toString('hex')}`; } private calculateHash(entry: Omit): string { const data = JSON.stringify({ id: entry.id, timestamp: entry.timestamp, action: entry.action, category: entry.category, severity: entry.severity, actor: entry.actor, resource: entry.resource, description: entry.description, metadata: entry.metadata, correlationId: entry.correlationId, parentId: entry.parentId, previousHash: entry.previousHash }); return crypto.createHash('sha256').update(data).digest('hex'); } private getPreviousHash(): string { if (this.entries.length === 0) { return this.genesisHash; } return this.entries[this.entries.length - 1].hash; } /** * Log an audit entry */ log( action: AuditAction, category: AuditCategory, description: string, options: { severity?: AuditSeverity; actor?: Partial; resource?: AuditResource; metadata?: Record; correlationId?: string; parentId?: string; } = {} ): AuditEntry { const entry: Omit = { id: this.generateId(), timestamp: new Date().toISOString(), action, category, severity: options.severity || 'INFO', actor: { id: options.actor?.id || 'system', type: options.actor?.type || 'SYSTEM', name: options.actor?.name, ip: options.actor?.ip, userAgent: options.actor?.userAgent, sessionId: options.actor?.sessionId }, resource: options.resource, description, metadata: options.metadata, correlationId: options.correlationId, parentId: options.parentId, previousHash: this.getPreviousHash() }; const hash = this.calculateHash(entry); const fullEntry: AuditEntry = { ...entry, hash }; this.entries.push(fullEntry); // Log critical events immediately if (fullEntry.severity === 'CRITICAL' || fullEntry.severity === 'ERROR') { console.log(`[AuditLog] ${fullEntry.severity}: ${fullEntry.description}`); this.save(); } return fullEntry; } /** * Log a system event */ logSystemEvent(description: string, severity: AuditSeverity = 'INFO', metadata?: Record): AuditEntry { return this.log('SYSTEM_EVENT', 'SYSTEM', description, { severity, metadata }); } /** * Log an API call */ logApiCall( endpoint: string, method: string, actor: Partial, options: { statusCode?: number; responseTime?: number; error?: string; requestBody?: any; queryParams?: any; } = {} ): AuditEntry { const severity: AuditSeverity = options.error ? 'ERROR' : (options.statusCode && options.statusCode >= 400) ? 'WARNING' : 'INFO'; return this.log('API_CALL', 'API', `${method} ${endpoint}`, { severity, actor, metadata: { endpoint, method, statusCode: options.statusCode, responseTime: options.responseTime, error: options.error, requestBody: options.requestBody, queryParams: options.queryParams } }); } /** * Log a plant action */ logPlantAction( action: 'PLANT_REGISTER' | 'PLANT_CLONE' | 'PLANT_TRANSFER', plantId: string, actor: Partial, options: { previousState?: any; newState?: any; metadata?: Record; } = {} ): AuditEntry { const descriptions: Record = { PLANT_REGISTER: `Plant ${plantId} registered`, PLANT_CLONE: `Plant ${plantId} cloned`, PLANT_TRANSFER: `Plant ${plantId} transferred` }; return this.log(action, 'PLANT', descriptions[action], { actor, resource: { type: 'plant', id: plantId, previousState: options.previousState, newState: options.newState }, metadata: options.metadata }); } /** * Log a transport event */ logTransportEvent( eventType: string, plantId: string, actor: Partial, options: { fromLocation?: { lat: number; lng: number }; toLocation?: { lat: number; lng: number }; distance?: number; carbonFootprint?: number; metadata?: Record; } = {} ): AuditEntry { return this.log('TRANSPORT_EVENT', 'TRANSPORT', `Transport event: ${eventType} for plant ${plantId}`, { actor, resource: { type: 'transport', id: plantId }, metadata: { eventType, ...options } }); } /** * Log an agent action */ logAgentAction( agentName: string, taskType: string, success: boolean, options: { executionTime?: number; error?: string; metadata?: Record; } = {} ): AuditEntry { return this.log('AGENT_TASK', 'AGENT', `Agent ${agentName} executed ${taskType}`, { severity: success ? 'INFO' : 'WARNING', actor: { id: agentName, type: 'AGENT', name: agentName }, metadata: { taskType, success, executionTime: options.executionTime, error: options.error, ...options.metadata } }); } /** * Log a blockchain action */ logBlockchainAction( action: 'CREATE' | 'VERIFY' | 'UPDATE', blockIndex: number, blockHash: string, options: { valid?: boolean; error?: string; metadata?: Record; } = {} ): AuditEntry { const severity: AuditSeverity = options.error ? 'ERROR' : options.valid === false ? 'WARNING' : 'INFO'; return this.log(action, 'BLOCKCHAIN', `Block ${blockIndex} ${action.toLowerCase()}d`, { severity, resource: { type: 'block', id: blockIndex.toString(), name: blockHash.substring(0, 16) }, metadata: { blockHash, valid: options.valid, error: options.error, ...options.metadata } }); } /** * Query audit entries */ query(queryParams: AuditQuery): { entries: AuditEntry[]; total: number } { let filtered = [...this.entries]; if (queryParams.startDate) { filtered = filtered.filter(e => e.timestamp >= queryParams.startDate!); } if (queryParams.endDate) { filtered = filtered.filter(e => e.timestamp <= queryParams.endDate!); } if (queryParams.actions && queryParams.actions.length > 0) { filtered = filtered.filter(e => queryParams.actions!.includes(e.action)); } if (queryParams.categories && queryParams.categories.length > 0) { filtered = filtered.filter(e => queryParams.categories!.includes(e.category)); } if (queryParams.severities && queryParams.severities.length > 0) { filtered = filtered.filter(e => queryParams.severities!.includes(e.severity)); } if (queryParams.actorId) { filtered = filtered.filter(e => e.actor.id === queryParams.actorId); } if (queryParams.actorType) { filtered = filtered.filter(e => e.actor.type === queryParams.actorType); } if (queryParams.resourceType) { filtered = filtered.filter(e => e.resource?.type === queryParams.resourceType); } if (queryParams.resourceId) { filtered = filtered.filter(e => e.resource?.id === queryParams.resourceId); } if (queryParams.correlationId) { filtered = filtered.filter(e => e.correlationId === queryParams.correlationId); } if (queryParams.searchTerm) { const term = queryParams.searchTerm.toLowerCase(); filtered = filtered.filter(e => e.description.toLowerCase().includes(term) || e.actor.name?.toLowerCase().includes(term) || e.resource?.name?.toLowerCase().includes(term) ); } const total = filtered.length; // Sort by timestamp descending (newest first) filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp)); // Apply pagination const offset = queryParams.offset || 0; const limit = queryParams.limit || 100; filtered = filtered.slice(offset, offset + limit); return { entries: filtered, total }; } /** * Get audit statistics */ getStats(): AuditStats { const now = new Date(); const day = 24 * 60 * 60 * 1000; const entriesByAction: Record = {}; const entriesByCategory: Record = {}; const entriesBySeverity: Record = {}; const entriesByActorType: Record = {}; const actorCounts: Record = {}; const resourceCounts: Record = {}; let entriesLast24h = 0; let entriesLast7d = 0; let entriesLast30d = 0; let errorsLast24h = 0; for (const entry of this.entries) { // Count by action entriesByAction[entry.action] = (entriesByAction[entry.action] || 0) + 1; // Count by category entriesByCategory[entry.category] = (entriesByCategory[entry.category] || 0) + 1; // Count by severity entriesBySeverity[entry.severity] = (entriesBySeverity[entry.severity] || 0) + 1; // Count by actor type entriesByActorType[entry.actor.type] = (entriesByActorType[entry.actor.type] || 0) + 1; // Count by actor actorCounts[entry.actor.id] = (actorCounts[entry.actor.id] || 0) + 1; // Count by resource type if (entry.resource?.type) { resourceCounts[entry.resource.type] = (resourceCounts[entry.resource.type] || 0) + 1; } // Time-based counts const entryTime = new Date(entry.timestamp).getTime(); const age = now.getTime() - entryTime; if (age <= day) { entriesLast24h++; if (entry.severity === 'ERROR' || entry.severity === 'CRITICAL') { errorsLast24h++; } } if (age <= 7 * day) { entriesLast7d++; } if (age <= 30 * day) { entriesLast30d++; } } const topActors = Object.entries(actorCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([id, count]) => ({ id, count })); const topResources = Object.entries(resourceCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([type, count]) => ({ type, count })); return { totalEntries: this.entries.length, entriesByAction: entriesByAction as Record, entriesByCategory: entriesByCategory as Record, entriesBySeverity: entriesBySeverity as Record, entriesByActorType: entriesByActorType as Record, entriesLast24h, entriesLast7d, entriesLast30d, topActors, topResources, errorRate24h: entriesLast24h > 0 ? errorsLast24h / entriesLast24h : 0, lastEntryAt: this.entries.length > 0 ? this.entries[this.entries.length - 1].timestamp : null }; } /** * Verify the integrity of the audit chain */ verifyIntegrity(): { valid: boolean; errors: string[] } { const errors: string[] = []; for (let i = 0; i < this.entries.length; i++) { const entry = this.entries[i]; // Verify hash const { hash, ...entryWithoutHash } = entry; const calculatedHash = this.calculateHash(entryWithoutHash as any); if (calculatedHash !== hash) { errors.push(`Entry ${entry.id} has invalid hash (index ${i})`); } // Verify chain linkage if (i === 0) { if (entry.previousHash !== this.genesisHash) { errors.push(`First entry should have genesis hash as previousHash`); } } else { if (entry.previousHash !== this.entries[i - 1].hash) { errors.push(`Entry ${entry.id} has broken chain link at index ${i}`); } } } return { valid: errors.length === 0, errors }; } /** * Generate an audit report */ generateReport(startDate: string, endDate: string): AuditReport { const { entries } = this.query({ startDate, endDate, limit: 10000 }); const stats = this.getStats(); const integrity = this.verifyIntegrity(); // Find anomalies (errors, critical events) const anomalies = entries.filter(e => e.severity === 'ERROR' || e.severity === 'CRITICAL' ).slice(0, 50); // Generate highlights const highlights: string[] = []; if (stats.entriesLast24h > 0) { highlights.push(`${stats.entriesLast24h} events logged in the last 24 hours`); } if (stats.errorRate24h > 0.05) { highlights.push(`Warning: Error rate is ${(stats.errorRate24h * 100).toFixed(1)}% in last 24h`); } if (anomalies.length > 0) { highlights.push(`${anomalies.length} anomalies detected in the reporting period`); } if (!integrity.valid) { highlights.push(`ALERT: Audit chain integrity compromised - ${integrity.errors.length} issues found`); } return { generatedAt: new Date().toISOString(), period: { start: startDate, end: endDate }, stats, highlights, anomalies, complianceStatus: { dataIntegrity: integrity.valid, chainValid: integrity.valid, noTampering: integrity.errors.length === 0 } }; } /** * Get recent entries */ getRecent(limit: number = 50): AuditEntry[] { return this.entries.slice(-limit).reverse(); } /** * Get entries by correlation ID (for tracking related actions) */ getByCorrelation(correlationId: string): AuditEntry[] { return this.entries.filter(e => e.correlationId === correlationId); } /** * Export audit log to various formats */ export(format: 'json' | 'csv' | 'summary', query?: AuditQuery): string { const { entries } = this.query(query || { limit: 10000 }); switch (format) { case 'json': return JSON.stringify(entries, null, 2); case 'csv': const headers = ['Timestamp', 'Action', 'Category', 'Severity', 'Actor', 'Actor Type', 'Resource', 'Description']; const rows = entries.map(e => [ e.timestamp, e.action, e.category, e.severity, e.actor.id, e.actor.type, e.resource?.id || '', `"${e.description.replace(/"/g, '""')}"` ]); return [headers.join(','), ...rows.map(r => r.join(','))].join('\n'); case 'summary': const stats = this.getStats(); return ` AUDIT LOG SUMMARY ================= Generated: ${new Date().toISOString()} Total Entries: ${stats.totalEntries} Last 24 Hours: ${stats.entriesLast24h} Last 7 Days: ${stats.entriesLast7d} Last 30 Days: ${stats.entriesLast30d} Error Rate (24h): ${(stats.errorRate24h * 100).toFixed(2)}% Top Actions: ${Object.entries(stats.entriesByAction) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([action, count]) => ` - ${action}: ${count}`) .join('\n')} Top Categories: ${Object.entries(stats.entriesByCategory) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([cat, count]) => ` - ${cat}: ${count}`) .join('\n')} `.trim(); default: return JSON.stringify(entries); } } /** * Force save (for shutdown) */ forceSave(): void { this.save(); } /** * Cleanup on shutdown */ shutdown(): void { if (this.autoSaveInterval) { clearInterval(this.autoSaveInterval); } this.save(); } } // Singleton instance let auditLogInstance: AuditLog | null = null; export function getAuditLog(): AuditLog { if (!auditLogInstance) { auditLogInstance = new AuditLog(); } return auditLogInstance; } export default AuditLog;