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
<PackageReference Include="ConvergeERP.AuditTrail.Core" Version="1.0.0" />
<PackageVersion Include="ConvergeERP.AuditTrail.Core" Version="1.0.0" />
<PackageReference Include="ConvergeERP.AuditTrail.Core" />
paket add ConvergeERP.AuditTrail.Core --version 1.0.0
#r "nuget: ConvergeERP.AuditTrail.Core, 1.0.0"
#:package ConvergeERP.AuditTrail.Core@1.0.0
#addin nuget:?package=ConvergeERP.AuditTrail.Core&version=1.0.0
#tool nuget:?package=ConvergeERP.AuditTrail.Core&version=1.0.0
ConvergeERP Audit Trail Library
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
Automatic Auditing (Recommended for Most Services)
// 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-Idheader ortenant_idJWT claim - CompanyId:
X-Company-Idheader orcompany_idJWT claim - UserId: JWT
subclaim orX-User-Idheader - CorrelationId:
X-Correlation-Idheader 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
Kafka (Production Recommended)
// 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
- Integration Guide - Detailed setup instructions
- Security Guarantees - Data protection details
- Performance Analysis - Thread safety validation
- Platform Compliance - Multi-tenant architecture compliance
📞 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 | Versions 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. |
-
net10.0
- Confluent.Kafka (>= 2.6.1)
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.AspNetCore.Http.Extensions (>= 2.2.0)
- Microsoft.AspNetCore.Http.Features (>= 5.0.17)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Http (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
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 |