diff --git a/src/Areas/Identity/Pages/Account/LogOut.cshtml b/src/Areas/Identity/Pages/Account/LogOut.cshtml index ed279e7c..71f50307 100644 --- a/src/Areas/Identity/Pages/Account/LogOut.cshtml +++ b/src/Areas/Identity/Pages/Account/LogOut.cshtml @@ -21,9 +21,6 @@ AuditEventType.Success, AuditObjectType.User, userId, - userId, - username, - HttpContextAccessor.HttpContext.GetClientIpAddress(), new { Username = username }); } @@ -42,9 +39,6 @@ AuditEventType.Success, AuditObjectType.User, userId, - userId, - username, - HttpContextAccessor.HttpContext.GetClientIpAddress(), new { Username = username }); } diff --git a/src/Areas/Identity/Pages/Account/Login.cshtml.cs b/src/Areas/Identity/Pages/Account/Login.cshtml.cs index 0b2a1236..7aa24292 100644 --- a/src/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/src/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -140,9 +140,6 @@ await _auditService.LogAsync( AuditEventType.Success, AuditObjectType.User, user?.Id, - user?.Id, - Input.Username, - HttpContext.GetClientIpAddress(), new { Username = Input.Username }); return LocalRedirect(returnUrl); } @@ -158,9 +155,6 @@ await _auditService.LogAsync( AuditEventType.Failure, AuditObjectType.User, null, - null, - Input.Username, - HttpContext.GetClientIpAddress(), new { Username = Input.Username, Reason = "Account locked out" }); return RedirectToPage("./Lockout"); } @@ -171,9 +165,6 @@ await _auditService.LogAsync( AuditEventType.Failure, AuditObjectType.User, null, - null, - Input.Username, - HttpContext.GetClientIpAddress(), new { Username = Input.Username, Reason = "Invalid credentials" }); ModelState.AddModelError(string.Empty, "Invalid login attempt."); return Page(); diff --git a/src/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs b/src/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs index a8838f55..a2342ab9 100644 --- a/src/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs +++ b/src/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Identity; using NodeGuard.Services; -using NodeGuard.Helpers; namespace NodeGuard.Areas.Identity.Pages.Account { @@ -118,10 +117,7 @@ await _auditService.LogAsync( AuditActionType.TwoFactorLogin, AuditEventType.Success, AuditObjectType.User, - userId, - userId, - user.UserName, - HttpContext.GetClientIpAddress(), + objectId: userId, new { Username = user.UserName }); return LocalRedirect(returnUrl); } @@ -132,10 +128,7 @@ await _auditService.LogAsync( AuditActionType.TwoFactorLogin, AuditEventType.Failure, AuditObjectType.User, - userId, - userId, - user.UserName, - HttpContext.GetClientIpAddress(), + objectId: userId, new { Username = user.UserName, Reason = "Account locked out" }); return RedirectToPage("./Lockout"); } @@ -146,10 +139,7 @@ await _auditService.LogAsync( AuditActionType.TwoFactorLogin, AuditEventType.Failure, AuditObjectType.User, - userId, - userId, - user.UserName, - HttpContext.GetClientIpAddress(), + objectId: userId, new { Username = user.UserName, Reason = "Invalid authenticator code" }); ModelState.AddModelError(string.Empty, "Invalid authenticator code."); return Page(); diff --git a/src/Pages/ChannelRequests.razor b/src/Pages/ChannelRequests.razor index 92e3dd82..f522affa 100644 --- a/src/Pages/ChannelRequests.razor +++ b/src/Pages/ChannelRequests.razor @@ -724,9 +724,6 @@ AuditEventType.Success, AuditObjectType.ChannelOperationRequest, request.Id.ToString(), - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { Amount = amount, SourceNodeId = _selectedSourceNodeId, DestNodeId = _selectedDestNode?.Id, Description = $"Channel open request created. Amount: {amount} BTC, SourceNodeId: {_selectedSourceNodeId}, DestNodeId: {_selectedDestNode?.Id}" }); if (_selectedUTXOs.Count > 0) @@ -742,9 +739,6 @@ AuditEventType.Failure, AuditObjectType.ChannelOperationRequest, null, - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { Error = createChannelResult.Item2, Description = $"Failed to create channel open request. Error: {createChannelResult.Item2}" }); } _utxoSelectorModalRef.ClearModal(); @@ -803,9 +797,6 @@ AuditEventType.Failure, AuditObjectType.ChannelOperationRequest, _selectedRequest.Id.ToString(), - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { RequestId = _selectedRequest.Id, Status = _selectedStatus, Description = $"Failed to {_selectedStatus.ToString().ToLower()} channel operation request" }); } else @@ -817,9 +808,6 @@ AuditEventType.Success, AuditObjectType.ChannelOperationRequest, _selectedRequest.Id.ToString(), - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { RequestId = _selectedRequest.Id, Status = _selectedStatus, Reason = _selectedRequest.ClosingReason, Description = $"Channel operation request {_selectedStatus.ToString().ToLower()}" }); await FetchRequests(); } @@ -892,9 +880,6 @@ AuditEventType.Success, AuditObjectType.ChannelOperationRequest, _selectedRequest.Id.ToString(), - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { RequestId = _selectedRequest.Id, Description = "PSBT signature collected for channel operation request" }); _selectedRequest = await ChannelOperationRequestRepository.GetById(_selectedRequest.Id); @@ -913,9 +898,6 @@ AuditEventType.Failure, AuditObjectType.ChannelOperationRequest, _selectedRequest.Id.ToString(), - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { RequestId = _selectedRequest.Id, Description = "Failed to save PSBT signature for channel operation request" }); } @@ -955,9 +937,6 @@ AuditEventType.Success, AuditObjectType.ChannelOperationRequest, _selectedRequest.Id.ToString(), - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { Amount = new Money(_selectedRequest.SatsAmount).ToUnit(MoneyUnit.BTC), SourceNodeId = _selectedRequest.SourceNodeId, DestNodeId = _selectedRequest.DestNodeId, IsHotWallet = true, Description = "Hot wallet channel open request created" }); } else @@ -968,9 +947,6 @@ AuditEventType.Failure, AuditObjectType.ChannelOperationRequest, null, - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { Error = createChannelResult.Item2, IsHotWallet = true, Description = "Failed to create hot wallet channel open request" }); _utxoSelectorModalRef.ClearModal(); await _approveOperationConfirmationModal.CloseModal(); @@ -1070,9 +1046,6 @@ AuditEventType.Success, AuditObjectType.ChannelOperationRequest, request.Id.ToString(), - LoggedUser.Id, - LoggedUser.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { RequestId = request.Id, NewStatus = ChannelOperationRequestStatus.Failed, Description = "Channel operation request marked as failed" }); } catch (Exception? e) @@ -1083,9 +1056,6 @@ AuditEventType.Failure, AuditObjectType.ChannelOperationRequest, _selectedRequestForMarkingAsFailed?.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - HttpContextAccessor.HttpContext?.GetClientIpAddress(), new { RequestId = _selectedRequestForMarkingAsFailed?.Id, Description = "Failed to mark channel operation request as failed" }); } finally diff --git a/src/Pages/Channels.razor b/src/Pages/Channels.razor index 4764adbb..42d9cfcf 100644 --- a/src/Pages/Channels.razor +++ b/src/Pages/Channels.razor @@ -505,14 +505,14 @@ { ToastService.ShowError("Something went wrong"); await AuditService.LogAsync(actionType, AuditEventType.Failure, AuditObjectType.Channel, - channel.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + channel.Id.ToString(), $"Failed to {(forceClose ? "force " : "")}close channel. ChanId: {channel.ChanId}"); } else { ToastService.ShowSuccess("Channel closed successfully"); await AuditService.LogAsync(actionType, AuditEventType.Success, AuditObjectType.Channel, - channel.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + channel.Id.ToString(), $"Channel {(forceClose ? "force " : "")}closed. ChanId: {channel.ChanId}"); await FetchData(); } @@ -615,14 +615,14 @@ { ToastService.ShowError("Something went wrong"); await AuditService.LogAsync(AuditActionType.Update, AuditEventType.Failure, AuditObjectType.Channel, - _selectedChannel.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + _selectedChannel.Id.ToString(), $"Failed to update channel. ChanId: {_selectedChannel.ChanId}"); } else { ToastService.ShowSuccess("Channel updated successfully"); await AuditService.LogAsync(AuditActionType.Update, AuditEventType.Success, AuditObjectType.Channel, - _selectedChannel.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + _selectedChannel.Id.ToString(), $"Channel updated. ChanId: {_selectedChannel.ChanId}"); } } @@ -637,14 +637,14 @@ { ToastService.ShowError("Something went wrong"); await AuditService.LogAsync(AuditActionType.Create, AuditEventType.Failure, AuditObjectType.LiquidityRule, - _currentLiquidityRule.ChannelId.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + _currentLiquidityRule.ChannelId.ToString(), $"Failed to add liquidity rule for channel. ChannelId: {_currentLiquidityRule.ChannelId}"); } else { ToastService.ShowSuccess("Liquidity rule added successfully"); await AuditService.LogAsync(AuditActionType.Create, AuditEventType.Success, AuditObjectType.LiquidityRule, - _currentLiquidityRule.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + _currentLiquidityRule.Id.ToString(), $"Liquidity rule created. ChannelId: {_currentLiquidityRule.ChannelId}, MinLocal: {_currentLiquidityRule.MinimumLocalBalance}%, MinRemote: {_currentLiquidityRule.MinimumRemoteBalance}%, Target: {_currentLiquidityRule.RebalanceTarget}%"); } } @@ -655,14 +655,14 @@ { ToastService.ShowError("Something went wrong"); await AuditService.LogAsync(AuditActionType.Update, AuditEventType.Failure, AuditObjectType.LiquidityRule, - _currentLiquidityRule.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + _currentLiquidityRule.Id.ToString(), $"Failed to update liquidity rule. RuleId: {_currentLiquidityRule.Id}"); } else { ToastService.ShowSuccess("Liquidity rule updated successfully"); await AuditService.LogAsync(AuditActionType.Update, AuditEventType.Success, AuditObjectType.LiquidityRule, - _currentLiquidityRule.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + _currentLiquidityRule.Id.ToString(), $"Liquidity rule updated. ChannelId: {_currentLiquidityRule.ChannelId}, MinLocal: {_currentLiquidityRule.MinimumLocalBalance}%, MinRemote: {_currentLiquidityRule.MinimumRemoteBalance}%, Target: {_currentLiquidityRule.RebalanceTarget}%"); } } @@ -735,14 +735,14 @@ { ToastService.ShowError("Something went wrong"); await AuditService.LogAsync(actionType, AuditEventType.Failure, AuditObjectType.Channel, - _selectedChannel.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + _selectedChannel.Id.ToString(), $"Failed to {(enabledLiquidityMngmt ? "enable" : "disable")} liquidity management. ChanId: {_selectedChannel.ChanId}"); } else { ToastService.ShowSuccess("Channel updated successfully"); await AuditService.LogAsync(actionType, AuditEventType.Success, AuditObjectType.Channel, - _selectedChannel.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + _selectedChannel.Id.ToString(), $"Liquidity management {(enabledLiquidityMngmt ? "enabled" : "disabled")}. ChanId: {_selectedChannel.ChanId}"); } } @@ -908,14 +908,14 @@ { ToastService.ShowSuccess("Channel marked as closed"); await AuditService.LogAsync(AuditActionType.MarkAsClosed, AuditEventType.Success, AuditObjectType.Channel, - contextItem.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + contextItem.Id.ToString(), $"Channel marked as closed. ChanId: {contextItem.ChanId}"); } else { ToastService.ShowError("Something went wrong: " + result.Item2); await AuditService.LogAsync(AuditActionType.MarkAsClosed, AuditEventType.Failure, AuditObjectType.Channel, - contextItem.Id.ToString(), LoggedUser?.Id, LoggedUser?.UserName, null, + contextItem.Id.ToString(), $"Failed to mark channel as closed. ChanId: {contextItem.ChanId}. Error: {result.Item2}"); } diff --git a/src/Pages/Nodes.razor b/src/Pages/Nodes.razor index 0ab51323..ecbe8fe0 100644 --- a/src/Pages/Nodes.razor +++ b/src/Pages/Nodes.razor @@ -615,9 +615,6 @@ AuditEventType.Success, AuditObjectType.Node, arg.Item.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, PubKey = arg.Item.PubKey }); } else @@ -629,9 +626,6 @@ AuditEventType.Failure, AuditObjectType.Node, null, - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, PubKey = arg.Item.PubKey, Error = addResult.Item2 }); } } @@ -656,9 +650,6 @@ AuditEventType.Success, AuditObjectType.Node, arg.Item.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, PubKey = arg.Item.PubKey }); } else @@ -669,9 +660,6 @@ AuditEventType.Failure, AuditObjectType.Node, arg.Item.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, PubKey = arg.Item.PubKey, Error = updateResult.Item2 }); } } @@ -722,9 +710,6 @@ AuditEventType.Success, AuditObjectType.Node, node.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { NodeName = node.Name, PubKey = node.PubKey }); _nodes = await NodeRepository.GetAll(); diff --git a/src/Pages/Wallets.razor b/src/Pages/Wallets.razor index 5326dbd9..a4febb82 100644 --- a/src/Pages/Wallets.razor +++ b/src/Pages/Wallets.razor @@ -916,9 +916,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Success, AuditObjectType.Wallet, arg.Item.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, IsHotWallet = arg.Item.IsHotWallet, MofN = arg.Item.MofN }); await GetData(); } @@ -930,9 +927,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Failure, AuditObjectType.Wallet, null, - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, Error = addResult.Item2 }); _wallets.Remove(arg.Item); } @@ -958,9 +952,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Failure, AuditObjectType.Wallet, arg.Item.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, Error = message }); } else @@ -971,9 +962,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Success, AuditObjectType.Wallet, arg.Item.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name }); await GetData(); } @@ -1011,9 +999,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Success, AuditObjectType.Wallet, arg.Item.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, IsHotWallet = arg.Item.IsHotWallet }); } else @@ -1024,9 +1009,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Failure, AuditObjectType.Wallet, arg.Item.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = arg.Item.Name, Error = updateResult.Item2 }); } @@ -1092,9 +1074,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Success, AuditObjectType.Wallet, _selectedWallet.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { WalletName = _selectedWallet.Name, KeyId = _selectedWalletKey.Id }); } else @@ -1105,9 +1084,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Failure, AuditObjectType.Wallet, _selectedWallet.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { WalletName = _selectedWallet.Name, KeyId = _selectedWalletKey.Id, Error = updateResult.Item2 }); } } @@ -1327,9 +1303,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Success, AuditObjectType.Wallet, walletId.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = walletName }); } else @@ -1340,9 +1313,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Failure, AuditObjectType.Wallet, walletId.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = walletName, Error = result.Item2 }); } @@ -1428,9 +1398,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Failure, AuditObjectType.Wallet, null, - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = _name, IsWatchOnly = _IsImportWalletModalWatchOnly, Error = result.Item2 }); return; } @@ -1445,9 +1412,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Success, AuditObjectType.Wallet, null, - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { Name = _name, IsWatchOnly = _IsImportWalletModalWatchOnly }); //Close modal @@ -1609,9 +1573,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Success, AuditObjectType.Wallet, _sourceTransferWallet?.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { SourceWallet = _sourceWalletName, TargetWallet = _targetWalletName, @@ -1627,9 +1588,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Failure, AuditObjectType.Wallet, _sourceTransferWallet?.Id.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { SourceWallet = _sourceWalletName, TargetWallet = _targetWalletName, @@ -1886,9 +1844,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Failure, AuditObjectType.UTXO, utxo.Outpoint.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { WalletId = _selectedWallet?.Id, Outpoint = utxo.Outpoint.ToString() }); return; } @@ -1898,9 +1853,6 @@ OnSubmit="TransferFundsHotWallet"/> AuditEventType.Success, AuditObjectType.UTXO, utxo.Outpoint.ToString(), - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { WalletId = _selectedWallet?.Id, Outpoint = utxo.Outpoint.ToString() }); // Updated the UI diff --git a/src/Services/AuditService.cs b/src/Services/AuditService.cs index 4a083d77..3e535e38 100644 --- a/src/Services/AuditService.cs +++ b/src/Services/AuditService.cs @@ -46,10 +46,31 @@ public async Task LogAsync( AuditEventType eventType, AuditObjectType objectAffected, string? objectId = null, - string? userId = null, - string? username = null, - string? ipAddress = null, object? details = null) + { + var (userId, username, ipAddress) = ExtractContextInfo(); + await LogInternalAsync(actionType, eventType, objectAffected, objectId, userId, username, ipAddress, details); + } + + public async Task LogSystemAsync( + AuditActionType actionType, + AuditEventType eventType, + AuditObjectType objectAffected, + string? objectId = null, + object? details = null) + { + await LogInternalAsync(actionType, eventType, objectAffected, objectId, null, "SYSTEM", null, details); + } + + private async Task LogInternalAsync( + AuditActionType actionType, + AuditEventType eventType, + AuditObjectType objectAffected, + string? objectId, + string? userId, + string? username, + string? ipAddress, + object? details) { try { @@ -92,44 +113,6 @@ public async Task LogAsync( } } - public async Task LogAsync( - AuditActionType actionType, - AuditEventType eventType, - AuditObjectType objectAffected, - string? objectId = null, - object? details = null) - { - var (userId, username, ipAddress) = ExtractContextInfo(); - - await LogAsync( - actionType, - eventType, - objectAffected, - objectId, - userId, - username, - ipAddress, - details); - } - - public async Task LogSystemAsync( - AuditActionType actionType, - AuditEventType eventType, - AuditObjectType objectAffected, - string? objectId = null, - object? details = null) - { - await LogAsync( - actionType, - eventType, - objectAffected, - objectId, - null, // No user ID for system operations - "SYSTEM", - null, // No IP address for system operations - details); - } - private (string? UserId, string? Username, string? IpAddress) ExtractContextInfo() { string? userId = null; diff --git a/src/Services/IAuditService.cs b/src/Services/IAuditService.cs index abd9879d..9cacc154 100644 --- a/src/Services/IAuditService.cs +++ b/src/Services/IAuditService.cs @@ -23,19 +23,6 @@ namespace NodeGuard.Services; public interface IAuditService { - /// - /// Log an audit event with all required fields - /// - Task LogAsync( - AuditActionType actionType, - AuditEventType eventType, - AuditObjectType objectAffected, - string? objectId = null, - string? userId = null, - string? username = null, - string? ipAddress = null, - object? details = null); - /// /// Log an audit event using HttpContext for user and IP extraction /// diff --git a/src/Shared/NewSwapModal.razor b/src/Shared/NewSwapModal.razor index bb98c50f..508d9958 100644 --- a/src/Shared/NewSwapModal.razor +++ b/src/Shared/NewSwapModal.razor @@ -314,9 +314,6 @@ AuditEventType.Success, AuditObjectType.SwapOut, response.Id, - LoggedUser?.Id, - LoggedUser?.UserName, - null, new { NodeId = _selectedNode.Id, diff --git a/test/NodeGuard.Tests/Services/AuditServiceTests.cs b/test/NodeGuard.Tests/Services/AuditServiceTests.cs index 53238834..7b9b4f46 100644 --- a/test/NodeGuard.Tests/Services/AuditServiceTests.cs +++ b/test/NodeGuard.Tests/Services/AuditServiceTests.cs @@ -93,55 +93,93 @@ private DefaultHttpContext CreateHttpContextWithUser( return httpContext; } - #region LogAsync_FullParameters + #region LogAsync_AutoContext [Fact] - public async Task LogAsync_FullParameters_SuccessfulLogging_CallsRepositoryAndLogger() + public async Task LogAsync_AutoContext_WithAuthenticatedUser_ExtractsUserInfoFromClaims() { // Arrange SetupSuccessfulRepository(); + var httpContext = CreateHttpContextWithUser("user-123", "johndoe"); + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContext); var service = CreateAuditService(); - var details = new { Operation = "Test", Value = 123 }; // Act await service.LogAsync( AuditActionType.Create, AuditEventType.Success, - AuditObjectType.User, - "object-123", - "user-456", - "johndoe", - "10.0.0.1", - details); + AuditObjectType.Wallet, + "wallet-456", + new { Amount = 50000 }); // Assert _auditLogRepositoryMock.Verify( x => x.AddAsync(It.Is(log => - log.ActionType == AuditActionType.Create && - log.EventType == AuditEventType.Success && - log.ObjectAffected == AuditObjectType.User && - log.ObjectId == "object-123" && - log.UserId == "user-456" && + log.UserId == "user-123" && log.Username == "johndoe" && - log.IpAddress == "10.0.0.1" && - log.Details != null)), + log.IpAddress == "192.168.1.100")), Times.Once); + } - _loggerMock.Verify( - x => x.Log( - LogLevel.Information, - It.IsAny(), - It.Is((v, t) => v.ToString()!.Contains("AUDIT:")), - null, - It.IsAny>()), + [Fact] + public async Task LogAsync_AutoContext_NoHttpContext_HandlesGracefully() + { + // Arrange + SetupSuccessfulRepository(); + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns((HttpContext?)null); + var service = CreateAuditService(); + + // Act + await service.LogAsync( + AuditActionType.Create, + AuditEventType.Success, + AuditObjectType.Wallet, + "wallet-456", + new { Amount = 50000 }); + + // Assert + _auditLogRepositoryMock.Verify( + x => x.AddAsync(It.Is(log => + log.UserId == null && + log.Username == null && + log.IpAddress == null)), Times.Once); } [Fact] - public async Task LogAsync_FullParameters_RepositoryFails_LogsErrorButDoesNotThrow() + public async Task LogAsync_AutoContext_UnauthenticatedUser_HandlesGracefully() + { + // Arrange + SetupSuccessfulRepository(); + var httpContext = new DefaultHttpContext(); + httpContext.Connection.RemoteIpAddress = System.Net.IPAddress.Parse("192.168.1.200"); + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContext); + var service = CreateAuditService(); + + // Act + await service.LogAsync( + AuditActionType.Update, + AuditEventType.Success, + AuditObjectType.Channel, + "channel-789", + null); + + // Assert + _auditLogRepositoryMock.Verify( + x => x.AddAsync(It.Is(log => + log.UserId == null && + log.Username == null && + log.IpAddress == "192.168.1.200")), + Times.Once); + } + + [Fact] + public async Task LogAsync_AutoContext_RepositoryFails_LogsErrorButDoesNotThrow() { // Arrange SetupFailedRepository("Database connection failed"); + var httpContext = CreateHttpContextWithUser(); + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContext); var service = CreateAuditService(); // Act @@ -166,9 +204,11 @@ public async Task LogAsync_FullParameters_RepositoryFails_LogsErrorButDoesNotThr } [Fact] - public async Task LogAsync_FullParameters_ExceptionDuringLogging_CatchesAndLogsError() + public async Task LogAsync_AutoContext_ExceptionDuringLogging_CatchesAndLogsError() { // Arrange + var httpContext = CreateHttpContextWithUser(); + _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContext); var service = CreateAuditService(); _auditLogRepositoryMock .Setup(x => x.AddAsync(It.IsAny())) @@ -180,9 +220,6 @@ public async Task LogAsync_FullParameters_ExceptionDuringLogging_CatchesAndLogsE AuditEventType.Success, AuditObjectType.User, "user-123", - "user-456", - "johndoe", - "10.0.0.1", new { Test = "data" }); // Assert @@ -197,40 +234,71 @@ public async Task LogAsync_FullParameters_ExceptionDuringLogging_CatchesAndLogsE It.IsAny>()), Times.Once); } - + #endregion - #region LogAsync_AutoContext + #region LogSystemAsync [Fact] - public async Task LogAsync_AutoContext_WithAuthenticatedUser_ExtractsUserInfoFromClaims() + public async Task LogSystemAsync_SuccessfulLogging_CreatesAuditLogWithSystemUser() { // Arrange SetupSuccessfulRepository(); - var httpContext = CreateHttpContextWithUser("user-123", "johndoe"); - _httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContext); var service = CreateAuditService(); // Act - await service.LogAsync( + await service.LogSystemAsync( AuditActionType.Create, AuditEventType.Success, AuditObjectType.Wallet, - "wallet-456", - new { Amount = 50000 }); + "wallet-123", + new { AutoGenerated = true }); // Assert _auditLogRepositoryMock.Verify( x => x.AddAsync(It.Is(log => - log.UserId == "user-123" && - log.Username == "johndoe" && - log.IpAddress == "192.168.1.100")), + log.ActionType == AuditActionType.Create && + log.EventType == AuditEventType.Success && + log.ObjectAffected == AuditObjectType.Wallet && + log.ObjectId == "wallet-123" && + log.UserId == null && + log.Username == "SYSTEM" && + log.IpAddress == null && + log.Details != null)), + Times.Once); + + _loggerMock.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => v.ToString()!.Contains("AUDIT:") && v.ToString()!.Contains("SYSTEM")), + null, + It.IsAny>()), Times.Once); } - - #endregion - #region LogSystemAsync + [Fact] + public async Task LogSystemAsync_WithoutDetails_LogsSuccessfully() + { + // Arrange + SetupSuccessfulRepository(); + var service = CreateAuditService(); + + // Act + await service.LogSystemAsync( + AuditActionType.Update, + AuditEventType.Success, + AuditObjectType.Channel, + "channel-456", + null); + + // Assert + _auditLogRepositoryMock.Verify( + x => x.AddAsync(It.Is(log => + log.Username == "SYSTEM" && + log.Details == null)), + Times.Once); + } [Fact] public async Task LogSystemAsync_RepositoryFails_LogsErrorButDoesNotThrow() @@ -259,5 +327,35 @@ public async Task LogSystemAsync_RepositoryFails_LogsErrorButDoesNotThrow() Times.Once); } + [Fact] + public async Task LogSystemAsync_ExceptionDuringLogging_CatchesAndLogsError() + { + // Arrange + var service = CreateAuditService(); + _auditLogRepositoryMock + .Setup(x => x.AddAsync(It.IsAny())) + .ThrowsAsync(new InvalidOperationException("Database error")); + + // Act + var act = async () => await service.LogSystemAsync( + AuditActionType.Delete, + AuditEventType.Success, + AuditObjectType.Node, + "node-789", + new { Reason = "Automated cleanup" }); + + // Assert + await act.Should().NotThrowAsync(); + + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v.ToString()!.Contains("Error logging audit event")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + #endregion }