ConvergeERP.AuditTrail.Core 1.0.0

dotnet add package ConvergeERP.AuditTrail.Core --version 1.0.0
                    
NuGet\Install-Package ConvergeERP.AuditTrail.Core -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ConvergeERP.AuditTrail.Core" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ConvergeERP.AuditTrail.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="ConvergeERP.AuditTrail.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ConvergeERP.AuditTrail.Core --version 1.0.0
                    
#r "nuget: ConvergeERP.AuditTrail.Core, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ConvergeERP.AuditTrail.Core@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ConvergeERP.AuditTrail.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=ConvergeERP.AuditTrail.Core&version=1.0.0
                    
Install as a Cake Tool

ConvergeERP Audit Trail Library

NuGet Version License: MIT

A comprehensive audit trail library for .NET applications providing automatic and manual auditing capabilities with enterprise-grade features for multi-tenant ERP systems.

🚀 Quick Start

Installation

dotnet add package ConvergeERP.AuditTrail.Core

Basic Setup

// Program.cs - Add this line first
builder.Services.AddHttpContextAccessor();

// Enable EF Core automatic database change tracking
builder.Services.AddDbContext<CustomerDbContext>((sp, options) => {
    options.UseSqlServer(connectionString);
    options.AddAuditInterceptor(sp);  // Enables automatic audit for all EF operations
});

// Configure audit trail (choose ONE of the options below)

⚙️ Complete Configuration Options

Option 1: Basic Setup (Development)

builder.Services.AddAuditTrail(
    contextOptions => {
        contextOptions.ServiceName = "CustomerService";
        contextOptions.ServiceVersion = "2.1.0";
    },
    maskingOptions => {
        // Configure sensitive field masking
        maskingOptions.SensitiveFieldPatterns.Add("password");
        maskingOptions.SensitiveFieldPatterns.Add("creditcard");
        maskingOptions.SensitiveFieldPatterns.Add("apikey");
        maskingOptions.PartialMaskFieldPatterns.Add("email");
        maskingOptions.PartialMaskFieldPatterns.Add("phone");
        maskingOptions.MaskCharacter = '*';
    },
    dispatcherOptions => {
        dispatcherOptions.MaxQueueSize = 10000;
        dispatcherOptions.EnableMetrics = true;
        dispatcherOptions.WorkerThreadCount = 4;
        dispatcherOptions.DispatchTimeout = TimeSpan.FromSeconds(5);
    },
    captureOptions => {
        // Configure UPDATE capture strategy
        captureOptions.UpdateStrategy = UpdateCaptureStrategy.FullState;
        captureOptions.AlwaysFullStateEntities.Add("FinancialTransaction");
        captureOptions.AlwaysFullStateEntities.Add("PayrollRecord");
    }
);

Option 2: Production-Ready with Resilience & Security

builder.Services.AddResilientAuditTrail(
    contextOptions => {
        contextOptions.ServiceName = "CustomerService";
        contextOptions.ServiceVersion = "2.1.0";
    },
    maskingOptions => {
        // Automatic masking - applies to ALL audit events
        maskingOptions.SensitiveFieldPatterns.Add("password");
        maskingOptions.SensitiveFieldPatterns.Add("creditcard");
        maskingOptions.SensitiveFieldPatterns.Add("apikey");
        maskingOptions.SensitiveFieldPatterns.Add("secret");
        maskingOptions.SensitiveFieldPatterns.Add("token");
        maskingOptions.PartialMaskFieldPatterns.Add("email");
        maskingOptions.PartialMaskFieldPatterns.Add("phone");
        maskingOptions.MaskCharacter = '*';
    },
    dispatcherOptions => {
        // Resilience and performance settings
        dispatcherOptions.MaxQueueSize = 15000;
        dispatcherOptions.CircuitBreakerThreshold = 10;
        dispatcherOptions.MaxRetryAttempts = 3;
        dispatcherOptions.RetryDelay = TimeSpan.FromMilliseconds(200);
        dispatcherOptions.CircuitBreakerTimeout = TimeSpan.FromSeconds(60);
        dispatcherOptions.EnableMetrics = true;
        dispatcherOptions.WorkerThreadCount = 4;
        dispatcherOptions.DispatchTimeout = TimeSpan.FromSeconds(5);
    },
    integrityOptions => {
        // Security features - apply to ALL audit events automatically
        integrityOptions.SecretKey = Environment.GetEnvironmentVariable("AUDIT_INTEGRITY_KEY");
        integrityOptions.EnableIntegrityHashing = true;      // Tamper detection
        integrityOptions.EnableSequenceChaining = true;     // Chain validation
        integrityOptions.EnableNonceGeneration = true;      // Replay protection
    }
);

Option 3: Maximum Security with Monitoring

builder.Services.AddMonitoredAuditTrail(
    contextOptions => {
        contextOptions.ServiceName = "FinancialService";
        contextOptions.ServiceVersion = "3.1.0";
    },
    maskingOptions => {
        // Comprehensive masking for financial services
        maskingOptions.SensitiveFieldPatterns.Add("password");
        maskingOptions.SensitiveFieldPatterns.Add("creditcard");
        maskingOptions.SensitiveFieldPatterns.Add("ssn");
        maskingOptions.SensitiveFieldPatterns.Add("account_number");
        maskingOptions.SensitiveFieldPatterns.Add("routing_number");
        maskingOptions.SensitiveFieldPatterns.Add("apikey");
        maskingOptions.SensitiveFieldPatterns.Add("secret");
        maskingOptions.SensitiveFieldPatterns.Add("token");
        maskingOptions.PartialMaskFieldPatterns.Add("email");
        maskingOptions.PartialMaskFieldPatterns.Add("phone");
        maskingOptions.PartialMaskFieldPatterns.Add("customer_id");
        maskingOptions.MaskCharacter = '*';
    },
    dispatcherOptions => {
        // High-performance settings
        dispatcherOptions.MaxQueueSize = 25000;
        dispatcherOptions.CircuitBreakerThreshold = 8;
        dispatcherOptions.MaxRetryAttempts = 3;
        dispatcherOptions.RetryDelay = TimeSpan.FromMilliseconds(150);
        dispatcherOptions.CircuitBreakerTimeout = TimeSpan.FromSeconds(45);
        dispatcherOptions.EnableMetrics = true;
        dispatcherOptions.WorkerThreadCount = 6;
        dispatcherOptions.DispatchTimeout = TimeSpan.FromSeconds(3);
    },
    connectivityOptions => {
        // Service monitoring settings
        connectivityOptions.CheckInterval = TimeSpan.FromSeconds(30);
        connectivityOptions.DisconnectedThreshold = 50.0;     // 50% success rate
        connectivityOptions.DegradedThreshold = 90.0;         // 90% success rate
        connectivityOptions.EnablePeriodicReporting = true;
        connectivityOptions.ReportingIntervalMinutes = 5;
        connectivityOptions.SlowResponseThreshold = 3000.0;   // 3 seconds
    },
    integrityOptions => {
        // Maximum security features
        integrityOptions.SecretKey = Environment.GetEnvironmentVariable("AUDIT_INTEGRITY_KEY") 
            ?? throw new InvalidOperationException("AUDIT_INTEGRITY_KEY environment variable required");
        integrityOptions.EnableIntegrityHashing = true;       // Tamper detection
        integrityOptions.EnableSequenceChaining = true;      // Chain validation
        integrityOptions.EnableNonceGeneration = true;       // Replay protection
        integrityOptions.InstanceId = Environment.MachineName; // Instance identification
    }
);

// For ASP.NET Core apps, add background monitoring
builder.Services.AddHostedService(provider => 
    provider.GetRequiredService<AuditServiceConnectivityMonitor>());

Option 4: Background Jobs / Console Apps

services.AddAuditTrailForBackgroundJobs(
    contextOptions => {
        contextOptions.ServiceName = "BackgroundWorker";
        contextOptions.ServiceVersion = "1.5.0";
    },
    maskingOptions => {
        maskingOptions.SensitiveFieldPatterns.Add("password");
        maskingOptions.SensitiveFieldPatterns.Add("apikey");
        maskingOptions.PartialMaskFieldPatterns.Add("email");
    },
    dispatcherOptions => {
        dispatcherOptions.MaxQueueSize = 5000;
        dispatcherOptions.EnableMetrics = true;
    },
    integrityOptions => {
        // Security for background services
        integrityOptions.SecretKey = Environment.GetEnvironmentVariable("AUDIT_INTEGRITY_KEY");
        integrityOptions.EnableIntegrityHashing = true;
    }
);

// Usage in background jobs
using (AuditContextProvider.SetContext("tenant-abc", userId: null))
{
    await _auditLogger.LogSystemEventAsync("JobCompleted", "Processed 1,500 records");
}

Final Setup Steps

// Enable automatic HTTP request auditing (add this line)
app.UseAuditMiddleware();

// Set audit service endpoints via environment variables (choose one)
Environment.SetEnvironmentVariable("AUDIT_HTTP_ENDPOINT", "https://audit-api.company.com/events");
// OR
Environment.SetEnvironmentVariable("AUDIT_KAFKA_TOPIC", "audit-events-prod");

// Set security key (REQUIRED for integrity protection)
Environment.SetEnvironmentVariable("AUDIT_INTEGRITY_KEY", "your-32-character-secret-key-here");

🎯 Key Features

⚡ Automatic Auditing (Zero Code Required)

  • HTTP Middleware: Captures all HTTP requests/responses automatically
  • EF Core Interceptor: Automatically audits database changes (CREATE/UPDATE/DELETE)
  • Complete Coverage: Comprehensive audit trail without writing audit code

🎛️ Manual Auditing (Full Control)

  • Explicit Control: Add business context and compliance metadata
  • Rich API: Create, Update, Delete, Security, and System event logging
  • Custom Metadata: Attach business-specific information for reporting
  • Flexible Scoping: Platform vs Tenant level operations

🔐 Data Protection & Compliance

  • Field Masking: Automatic PII/sensitive data masking with [AuditMask] attributes
  • Pattern-Based Masking: Configure masking by field name patterns (25+ default patterns)
  • GDPR Compliance: Built-in privacy protection for personal data
  • SOX/HIPAA Ready: Enterprise compliance features

🏢 Multi-Tenant Architecture

  • Tenant Scoping: Automatic tenant context isolation
  • Platform Operations: System-wide operations separate from tenant data
  • Company Context: Multi-company support within tenants
  • Context Propagation: Automatic tenant/correlation ID tracking from headers/JWT

🚀 Resilient Dispatch

  • Multiple Targets: Kafka, HTTP endpoints, In-Memory
  • Circuit Breaker: Fault tolerance for external dependencies
  • Retry Logic: Configurable retry policies for failed dispatches
  • Performance Monitoring: Real-time metrics and health checks

📊 Audit Service Monitoring

  • Connectivity Detection: Monitors audit service availability
  • Circuit Breaker Status: Tracks when audit service appears down
  • Performance Metrics: Success rates, processing times, queue status
  • Automated Alerts: Logs system events when service issues detected

💾 Configurable UPDATE Capture

  • Multiple Strategies: Full-state, changed-fields-only, or changed-plus-context
  • Smart Defaults: Full state for compliance, optimized for large entities
  • Entity-Specific Rules: Configure per entity type

📊 Service Downtime Detection

Detection Capabilities

What It Detects How It Works Response
Service Downtime Circuit breaker opens after consecutive failures Events logged, fail-fast mode activated
Intermittent Issues Success rate drops below threshold Degraded status logged, retries continue
Slow Performance Processing time exceeds configured limits Slow status logged, monitoring continues
Complete Outage All dispatch attempts fail Disconnected status, events queued locally

Connectivity Status Events

{
  "action_type": "System Event",
  "resource_type": "AuditServiceConnectivityChange", 
  "description": "Audit service connectivity changed from Connected to Disconnected",
  "after_state": {
    "previous_status": "Connected",
    "new_status": "Disconnected", 
    "circuit_state": "Open",
    "success_rate": 15.5,
    "requires_attention": true
  }
}

Manual Status Checking

public class HealthController : ControllerBase
{
    private readonly AuditServiceConnectivityMonitor _monitor;
    
    [HttpGet("/health/audit-service")]
    public IActionResult GetAuditServiceHealth()
    {
        var summary = _monitor.GetConnectivitySummary();
        
        return summary.IsHealthy 
            ? Ok(summary) 
            : ServiceUnavailable(summary);
    }
}

🔒 Log Forgery & Corruption Protection

The audit trail library includes multiple layers of protection against log forgery, tampering, and corruption by malicious actors.

Built-in Protection Mechanisms

1. Immutable Event Structure
// Events are immutable once created - cannot be modified
public class AuditEvent
{
    public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow;  // Auto-set
    public Guid CorrelationId { get; set; } = Guid.NewGuid();              // Auto-generated
    public string SchemaVersion { get; set; } = "1.0";                     // Version locked
    
    // All state captured at creation time - no mutation allowed
}
2. Cryptographic Integrity Protection

When integrityOptions.EnableIntegrityHashing = true is configured, every audit event gets:

// Each event gets cryptographic protection automatically
{
  "id": 123,
  "sequenceNumber": 12345,
  "sequenceToken": "hash_of_previous_event_plus_current",
  "integrityHash": "hmac_sha256_of_entire_event",
  "nonce": "unique_value_prevents_replay",
  "instanceId": "server_001_prod"
}
3. Sequence Chaining (Blockchain-like)

Events are cryptographically linked - if any event is deleted or modified, the chain breaks and is immediately detected.

Protection Against Specific Attacks

Attack Type Protection Method How It Works
Log Injection Immutable structure + validation Cannot insert fake events into sequence
Event Tampering HMAC-SHA256 integrity hash Any modification breaks the hash
Sequence Manipulation Cryptographic chaining Each event links to previous via hash
Replay Attacks Nonce + timestamp validation Unique nonce prevents reuse
Batch Insertion Batch signatures Groups of events are signed together
Time Manipulation Server-side timestamps Timestamps set by secure server
Context Spoofing JWT/Header validation Tenant/user context from trusted sources

Real-World Attack Scenarios & Defenses

Scenario 1: Malicious Admin Tries to Delete Audit Logs
-- ❌ What attacker attempts:
DELETE FROM audit_events WHERE actor_id = 'compromised_admin';

-- ✅ How library protects:
-- 1. Sequence gaps detected: Missing sequence numbers 1001-1005
-- 2. Chain breaks detected: Event 1006 references missing event 1005
-- 3. Batch signatures invalid: Signature doesn't match remaining events
-- 4. Forensic flags raised: Sudden gap in admin's activity timeline
Scenario 2: Hacker Tries to Insert Fake "Legitimate" Actions
// ❌ What attacker attempts:
var fakeEvent = new AuditEvent
{
    ActionType = ActionType.Update,
    ActorId = "legitimate_user",
    Timestamp = DateTimeOffset.UtcNow.AddDays(-1), // Backdated
    // ... fake data to cover tracks
};

// ✅ How library protects:
// 1. No integrity hash - immediately flagged as forged
// 2. Wrong sequence number - doesn't fit in chain
// 3. Missing sequence token - can't link to previous event
// 4. Invalid nonce - replay detection triggers
// 5. Server timestamp validation - backdated timestamps rejected

Summary: Multi-Layer Defense

The audit trail library provides defense in depth against log forgery and corruption:

🔒 Layer 1: Immutable event structure (prevents modification)
🔒 Layer 2: Cryptographic integrity hashing (detects tampering)
🔒 Layer 3: Sequence chaining (prevents insertion/deletion)
🔒 Layer 4: Nonce generation (prevents replay attacks)
🔒 Layer 5: Real-time verification (immediate detection)
🔒 Layer 6: Forensic analysis (sophisticated attack detection)

Hackers cannot forge or corrupt audit logs without detection! 🛡️

💾 UPDATE Capture Strategies

Available Strategies

Strategy When to Use Storage Efficiency Query Performance
Full State Critical entities, compliance requirements Standard Fastest
Changed Fields Only Large entities, frequent small changes 60-80% reduction Good with indexes
Changed Plus Context Balance of efficiency and usability 40-60% reduction Very Good

Strategy Examples

// Full State (Default)
{
  "beforeState": { "id": 123, "name": "John", "salary": 75000, "dept": "IT" },
  "afterState": { "id": 123, "name": "John", "salary": 80000, "dept": "IT" }
}

// Changed Fields Only
{
  "beforeState": { "salary": 75000 },
  "afterState": { 
    "salary": 80000,
    "_capture_strategy": "changed_fields_only",
    "_changed_count": 1
  }
}

// Changed Plus Context
{
  "beforeState": { "id": 123, "salary": 75000, "modifiedDate": "2024-01-01" },
  "afterState": { 
    "id": 123, 
    "salary": 80000, 
    "modifiedDate": "2024-01-15",
    "_capture_strategy": "changed_plus_context"
  }
}

📋 Usage Examples

// 1. Enable EF Core automatic auditing (already configured above)

// 2. Add field masking to entities
public class User
{
    [AuditMask(MaskingStrategy.Full)]     // ****
    public string Password { get; set; }
    
    [AuditMask(MaskingStrategy.Partial)]  // jo****@ex****om
    public string Email { get; set; }
}

// 3. Your business logic (no audit code needed!)
public async Task<User> CreateUser(CreateUserRequest request)
{
    var user = new User { ... };
    await _repository.CreateAsync(user);  // Automatically audited!
    return user;
}

Manual Auditing (For Business Context)

public class OrderService
{
    private readonly IAuditLogger _auditLogger;

    public async Task ProcessOrder(Order order)
    {
        // Manual audit with business context
        await _auditLogger.LogCreateAsync("Order", order.Id, order, new Dictionary<string, object>
        {
            ["order_source"] = "web_portal",
            ["payment_method"] = "credit_card",
            ["customer_tier"] = "premium",
            ["compliance_reason"] = "sox_requirement"
        });
    }
}

State Change Operations (Suspend, Activate, Archive)

User Account Suspension
public class UserManagementService
{
    private readonly IAuditLogger _auditLogger;
    private readonly IUserRepository _userRepository;

    // Automatic suspension (EF Core tracks the change)
    public async Task<bool> SuspendUserAutomatic(int userId, string reason)
    {
        var user = await _dbContext.Users.FindAsync(userId);
        if (user == null) return false;

        user.Status = UserStatus.Suspended;
        user.SuspendedDate = DateTimeOffset.UtcNow;
        user.SuspensionReason = reason;
        
        await _dbContext.SaveChangesAsync();  // Automatically audited as UPDATE
        return true;
    }

    // Manual suspension with rich business context
    public async Task<bool> SuspendUserManual(int userId, string reason, string approvedBy)
    {
        var user = await _userRepository.GetByIdAsync(userId);
        if (user == null) return false;

        var beforeState = new {
            user.Id,
            user.Name,
            user.Email,
            user.Status,
            user.SuspendedDate,
            user.SuspensionReason
        };

        // Apply suspension
        user.Status = UserStatus.Suspended;
        user.SuspendedDate = DateTimeOffset.UtcNow;
        user.SuspensionReason = reason;

        var afterState = new {
            user.Id,
            user.Name,
            user.Email,
            user.Status,
            user.SuspendedDate,
            user.SuspensionReason
        };

        // Manual audit with business context
        await _auditLogger.LogUpdateAsync(
            resourceType: "User",
            resourceId: userId.ToString(),
            beforeState: beforeState,
            afterState: afterState,
            metadata: new Dictionary<string, object>
            {
                ["suspension_reason"] = reason,
                ["approved_by"] = approvedBy,
                ["suspension_type"] = "manual",
                ["compliance_category"] = "user_management",
                ["can_appeal"] = true,
                ["review_date"] = DateTimeOffset.UtcNow.AddDays(30).ToString("O"),
                ["notification_sent"] = true
            }
        );

        await _userRepository.UpdateAsync(user);
        return true;
    }

    // Reactivation (unsuspend)
    public async Task<bool> ReactivateUser(int userId, string reactivationReason, string approvedBy)
    {
        var user = await _userRepository.GetByIdAsync(userId);
        if (user == null || user.Status != UserStatus.Suspended) return false;

        var suspensionDuration = user.SuspendedDate.HasValue 
            ? DateTimeOffset.UtcNow - user.SuspendedDate.Value 
            : TimeSpan.Zero;

        await _auditLogger.LogUpdateAsync(
            resourceType: "User",
            resourceId: userId.ToString(),
            beforeState: new {
                user.Id,
                user.Status,
                user.SuspendedDate,
                user.SuspensionReason
            },
            afterState: new {
                user.Id,
                Status = UserStatus.Active,
                SuspendedDate = (DateTimeOffset?)null,
                SuspensionReason = (string?)null,
                ReactivatedDate = DateTimeOffset.UtcNow
            },
            metadata: new Dictionary<string, object>
            {
                ["action_type"] = "reactivation",
                ["reactivation_reason"] = reactivationReason,
                ["approved_by"] = approvedBy,
                ["suspension_duration_hours"] = suspensionDuration.TotalHours,
                ["reactivation_method"] = "manual_review"
            }
        );

        user.Status = UserStatus.Active;
        user.SuspendedDate = null;
        user.SuspensionReason = null;
        
        await _userRepository.UpdateAsync(user);
        return true;
    }
}

Account and Service Management
public class AccountManagementService
{
    private readonly IAuditLogger _auditLogger;

    // Archive old accounts
    public async Task<bool> ArchiveAccount(int accountId, string archiveReason)
    {
        var account = await _accountRepository.GetByIdAsync(accountId);
        
        await _auditLogger.LogUpdateAsync(
            resourceType: "Account",
            resourceId: accountId.ToString(),
            beforeState: account,
            afterState: new {
                account.Id,
                account.Name,
                Status = AccountStatus.Archived,
                ArchivedDate = DateTimeOffset.UtcNow,
                ArchiveReason = archiveReason,
                account.CreatedDate
            },
            metadata: new Dictionary<string, object>
            {
                ["archive_reason"] = archiveReason,
                ["account_age_days"] = (DateTimeOffset.UtcNow - account.CreatedDate).TotalDays,
                ["data_retention_period"] = "7_years",
                ["can_restore"] = true
            }
        );

        account.Status = AccountStatus.Archived;
        account.ArchivedDate = DateTimeOffset.UtcNow;
        account.ArchiveReason = archiveReason;
        
        await _accountRepository.UpdateAsync(account);
        return true;
    }

    // Subscription suspension
    public async Task<bool> SuspendSubscription(int subscriptionId, SuspensionReason reason)
    {
        var subscription = await _subscriptionRepository.GetByIdAsync(subscriptionId);
        
        await _auditLogger.LogUpdateAsync(
            resourceType: "Subscription",
            resourceId: subscriptionId.ToString(),
            beforeState: subscription,
            afterState: new {
                subscription.Id,
                subscription.PlanId,
                subscription.CustomerId,
                Status = SubscriptionStatus.Suspended,
                SuspendedDate = DateTimeOffset.UtcNow,
                SuspensionReason = reason.ToString(),
                subscription.ExpiryDate
            },
            metadata: new Dictionary<string, object>
            {
                ["suspension_trigger"] = reason.ToString(),
                ["grace_period_days"] = 7,
                ["auto_cancel_date"] = DateTimeOffset.UtcNow.AddDays(7).ToString("O"),
                ["notification_sent"] = true,
                ["billing_paused"] = true
            }
        );

        subscription.Status = SubscriptionStatus.Suspended;
        subscription.SuspendedDate = DateTimeOffset.UtcNow;
        subscription.SuspensionReason = reason.ToString();
        
        await _subscriptionRepository.UpdateAsync(subscription);
        return true;
    }
}
Delete Operations
public class DataManagementService
{
    private readonly IAuditLogger _auditLogger;

    // Soft delete (recommended)
    public async Task<bool> SoftDeleteCustomer(int customerId, string deletionReason)
    {
        var customer = await _dbContext.Customers.FindAsync(customerId);
        if (customer == null) return false;

        // EF Core will automatically audit this as UPDATE
        customer.IsDeleted = true;
        customer.DeletedDate = DateTimeOffset.UtcNow;
        customer.DeletionReason = deletionReason;
        
        await _dbContext.SaveChangesAsync();
        
        // Additional business event
        await _auditLogger.LogSystemEventAsync(
            eventType: "CustomerSoftDeleted",
            description: $"Customer {customerId} marked as deleted",
            metadata: new Dictionary<string, object>
            {
                ["customer_id"] = customerId,
                ["deletion_reason"] = deletionReason,
                ["can_restore"] = true,
                ["data_retention_policy"] = "keep_for_compliance"
            }
        );
        
        return true;
    }

    // Hard delete with audit
    public async Task<bool> HardDeleteExpiredData(int entityId, string entityType)
    {
        var entity = await _repository.GetByIdAsync(entityId);
        if (entity == null) return false;

        // Manual audit BEFORE deletion (since entity won't exist after)
        await _auditLogger.LogDeleteAsync(
            resourceType: entityType,
            resourceId: entityId.ToString(),
            beforeState: entity,
            metadata: new Dictionary<string, object>
            {
                ["deletion_type"] = "hard_delete",
                ["deletion_reason"] = "data_expiry",
                ["compliance_rule"] = "gdpr_right_to_be_forgotten",
                ["irreversible"] = true
            }
        );

        await _repository.DeleteAsync(entityId);
        return true;
    }

    // Bulk operations
    public async Task<int> BulkArchiveInactiveUsers(TimeSpan inactivePeriod)
    {
        var cutoffDate = DateTimeOffset.UtcNow - inactivePeriod;
        var inactiveUsers = await _dbContext.Users
            .Where(u => u.LastLoginDate < cutoffDate && u.Status == UserStatus.Active)
            .ToListAsync();

        foreach (var user in inactiveUsers)
        {
            await _auditLogger.LogUpdateAsync(
                resourceType: "User",
                resourceId: user.Id.ToString(),
                beforeState: new { user.Id, user.Status, user.LastLoginDate },
                afterState: new { 
                    user.Id, 
                    Status = UserStatus.Archived, 
                    user.LastLoginDate,
                    ArchivedDate = DateTimeOffset.UtcNow
                },
                metadata: new Dictionary<string, object>
                {
                    ["archive_reason"] = "bulk_inactivity_cleanup",
                    ["inactive_days"] = (DateTimeOffset.UtcNow - user.LastLoginDate).TotalDays,
                    ["batch_operation"] = true,
                    ["batch_size"] = inactiveUsers.Count
                }
            );

            user.Status = UserStatus.Archived;
            user.ArchivedDate = DateTimeOffset.UtcNow;
        }

        await _dbContext.SaveChangesAsync();
        return inactiveUsers.Count;
    }
}
Security and Administrative Actions
public class SecurityService
{
    private readonly IAuditLogger _auditLogger;

    // Password reset
    public async Task<bool> ResetUserPassword(int userId, string adminId, string reason)
    {
        var user = await _userRepository.GetByIdAsync(userId);
        var newPassword = GenerateTemporaryPassword();

        await _auditLogger.LogSecurityEventAsync(
            eventType: "PasswordReset",
            description: $"Password reset for user {userId}",
            metadata: new Dictionary<string, object>
            {
                ["target_user_id"] = userId,
                ["admin_user_id"] = adminId,
                ["reset_reason"] = reason,
                ["reset_method"] = "admin_initiated",
                ["temporary_password"] = true,
                ["force_change_on_login"] = true
            }
        );

        user.Password = HashPassword(newPassword);
        user.MustChangePassword = true;
        user.PasswordResetDate = DateTimeOffset.UtcNow;
        
        await _userRepository.UpdateAsync(user);
        return true;
    }

    // Role changes
    public async Task<bool> ChangeUserRole(int userId, string oldRole, string newRole, string adminId)
    {
        await _auditLogger.LogSecurityEventAsync(
            eventType: "RoleChange",
            description: $"User role changed from {oldRole} to {newRole}",
            metadata: new Dictionary<string, object>
            {
                ["target_user_id"] = userId,
                ["old_role"] = oldRole,
                ["new_role"] = newRole,
                ["changed_by"] = adminId,
                ["permission_impact"] = GetPermissionDifference(oldRole, newRole),
                ["effective_immediately"] = true
            }
        );

        // Update user role (will also be captured by EF interceptor)
        var user = await _userRepository.GetByIdAsync(userId);
        user.Role = newRole;
        user.RoleChangedDate = DateTimeOffset.UtcNow;
        user.RoleChangedBy = adminId;
        
        await _userRepository.UpdateAsync(user);
        return true;
    }
}

Control Attributes

// Skip automatic auditing for sensitive endpoints
[AuditIgnore]
[HttpPost("sensitive-operation")]
public async Task<ActionResult> SensitiveOperation() { }

// Fine-tune automatic auditing
[Audit(IncludeRequestBody = true, IncludeResponseBody = false)]
[HttpPost("create-user")]
public async Task<ActionResult> CreateUser() { }

📊 Audit Event Structure

{
  "id": 123,
  "actionType": "Create",                // Human-readable text, not numbers
  "scope": "Tenant Level",               // "Tenant Level" or "Platform Level"
  "actorType": "Human User",             // "Human User" or "System Process"
  "resourceType": "User",
  "resourceId": "user-123",
  "beforeState": null,
  "afterState": {
    "id": "user-123",
    "email": "jo****@ex****om",          // Automatically masked
    "password": "****",
    "_meta_diff": [                      // Automatic diff computation
      { "field": "status", "before": "pending", "after": "active" }
    ]
  },
  "maskedFields": ["email", "password"],
  "timestamp": "2024-01-15T10:30:45Z",
  "serviceName": "UserService",
  "serviceVersion": "2.1.0",
  "tenantId": "tenant-456",
  "correlationId": "abc-123-def"
}

🏢 Multi-Tenant Context

Automatic Context Resolution

The library automatically extracts context from:

  • TenantId: X-Tenant-Id header or tenant_id JWT claim
  • CompanyId: X-Company-Id header or company_id JWT claim
  • UserId: JWT sub claim or X-User-Id header
  • CorrelationId: X-Correlation-Id header or auto-generated

Scope Separation

  • PLATFORM scope: System-level operations (tenant provisioning, platform config)
  • TENANT scope: Business operations within a specific tenant
// Tenant event (automatic)
await _auditLogger.LogCreateAsync("Customer", id, customer);

// Platform event (explicit)
var platformEvent = new AuditEvent
{
    Scope = AuditScope.Platform,
    ActionType = ActionType.System,
    ResourceType = "Tenant",
    ResourceId = newTenantId
};
await _auditLogger.LogAsync(platformEvent);

🔌 Dispatch Targets

// Configure Kafka producer - implement IKafkaProducer
services.AddSingleton<IKafkaProducer>(sp => new YourKafkaProducer(config));

// Set environment variable
// AUDIT_KAFKA_TOPIC=audit-events

HTTP Endpoint (External Systems)

// Set environment variable
// AUDIT_HTTP_ENDPOINT=https://your-audit-api.com/events

In-Memory (Development/Testing)

// Default when no other dispatcher is configured
// Events are processed in-memory with configurable queue

⚙️ Integration

Compatible With

  • ✅ .NET 10+
  • ✅ Entity Framework Core
  • ✅ ASP.NET Core
  • ✅ Multi-tenant applications
  • ✅ Microservices architecture

Compliance Standards

  • ✅ SOX (Sarbanes-Oxley)
  • ✅ GDPR (General Data Protection Regulation)
  • ✅ HIPAA (Health Insurance Portability and Accountability Act)
  • ✅ PCI DSS (Payment Card Industry Data Security Standard)

📚 Documentation

📞 Support

For questions, issues, or feature requests, please contact the Model Carbon Team or visit our internal documentation.

📄 License

This project is licensed under the MIT License.


Made by the Model Carbon Team

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 700 1/12/2026