diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml index 216bea3..053fb22 100644 --- a/.github/workflows/build-check.yml +++ b/.github/workflows/build-check.yml @@ -6,6 +6,9 @@ on: - main workflow_dispatch: +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/remove-old-packages.yml b/.github/workflows/remove-old-packages.yml index feebf7f..ff97f4d 100644 --- a/.github/workflows/remove-old-packages.yml +++ b/.github/workflows/remove-old-packages.yml @@ -4,6 +4,10 @@ on: - cron: '0 0 * * 0' # elke zondag workflow_dispatch: +permissions: + contents: read + packages: write + jobs: cleanup: runs-on: ubuntu-latest diff --git a/RustRconServerManager.Backend/Controllers/AuthController.cs b/RustRconServerManager.Backend/Controllers/AuthController.cs index 1d3b1d9..d4b639d 100644 --- a/RustRconServerManager.Backend/Controllers/AuthController.cs +++ b/RustRconServerManager.Backend/Controllers/AuthController.cs @@ -472,7 +472,7 @@ private async Task> GetAccessibleServers(ApplicationUser user) [HttpPost("forgot-password")] public async Task ForgotPassword(Authorization_ForgotPasswordDTO model) { - _logger.LogDebug("[FORGOT-PASSWORD] Request received for email: {Email}", model.Email); + _logger.LogDebug("[FORGOT-PASSWORD] Request received"); // Always return 200 to prevent email enumeration var genericMessage = "If an account with that email exists, a recovery code has been sent."; @@ -490,7 +490,7 @@ public async Task ForgotPassword(Authorization_ForgotPasswordDTO return Ok(new { message = genericMessage }); } - _logger.LogDebug("[FORGOT-PASSWORD] User found: {Email}", user.Email); + _logger.LogDebug("[FORGOT-PASSWORD] User found: {UserId}", user.Id); // Generate random 6-digit code (overwrites any existing code) var code = RandomNumberGenerator.GetInt32(100000, 999999).ToString(); diff --git a/RustRconServerManager.Backend/Controllers/DashboardController.cs b/RustRconServerManager.Backend/Controllers/DashboardController.cs index 9fad694..795d0b0 100644 --- a/RustRconServerManager.Backend/Controllers/DashboardController.cs +++ b/RustRconServerManager.Backend/Controllers/DashboardController.cs @@ -352,12 +352,12 @@ public async Task BanPlayer([FromBody] BanPlayerRequest request) { string banCommand = $"banid {request.SteamId} \"{request.PlayerName}\" \"[GLOBAL BAN] {request.Reason}\" {request.DurationHours}"; _rconService.SendRconCommand(banCommand, server.Id); - _logger.LogInformation("Sent global ban command to server {ServerName} (ID: {ServerId}) for player {PlayerName}", server.Name, server.Id, request.PlayerName); + _logger.LogInformation("Sent global ban command to server {ServerName} (ID: {ServerId}) for player {PlayerName}", server.Name?.Replace("\r", "").Replace("\n", ""), server.Id, request.PlayerName?.Replace("\r", "").Replace("\n", "")); } catch (Exception ex) { // Log but don't fail if server is offline - ban is still tracked in database - _logger.LogWarning(ex, "Could not send global ban command to server {ServerName}", server.Name); + _logger.LogWarning(ex, "Could not send global ban command to server {ServerName}", server.Name?.Replace("\r", "").Replace("\n", "")); } } } @@ -870,7 +870,7 @@ public async Task ToggleGlobalBan(int banId, [FromBody] ToggleGlo // If toggling OFF a global ban, unban from all servers if (ban.IsGlobalBan && !request.IsGlobalBan) { - _logger.LogInformation("[GLOBAL UNBAN] Toggling off global ban for {SteamId}. Unbanning from all servers...", ban.SteamId); + _logger.LogInformation("[GLOBAL UNBAN] Toggling off global ban for {SteamId}. Unbanning from all servers...", ban.SteamId?.Replace("\r", "").Replace("\n", "")); // Get current user email for audit trail var userEmail = User.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value ?? "System"; @@ -902,7 +902,7 @@ public async Task ToggleGlobalBan(int banId, [FromBody] ToggleGlo { string unbanCommand = $"unban {playerBan.SteamId}"; _rconService.SendRconCommand(unbanCommand, playerBan.ServerId); - _logger.LogInformation("[GLOBAL UNBAN] Sent unban command for {SteamId} on server {ServerId}", ban.SteamId, playerBan.ServerId); + _logger.LogInformation("[GLOBAL UNBAN] Sent unban command for {SteamId} on server {ServerId}", ban.SteamId?.Replace("\r", "").Replace("\n", ""), playerBan.ServerId); } catch (Exception ex) { @@ -914,7 +914,7 @@ public async Task ToggleGlobalBan(int banId, [FromBody] ToggleGlo } await dbContext.SaveChangesAsync(); - _logger.LogInformation("[GLOBAL UNBAN] Removed all bans for {SteamId} from database", ban.SteamId); + _logger.LogInformation("[GLOBAL UNBAN] Removed all bans for {SteamId} from database", ban.SteamId?.Replace("\r", "").Replace("\n", "")); return Ok(new { message = $"Player {ban.SteamId} has been unbanned from all servers" }); } @@ -966,7 +966,7 @@ public async Task DeleteBan(int banId, [FromBody] DeleteBanReques // If this is a global ban, unban from all servers if (ban.IsGlobalBan && ban.ServerId == -1) { - _logger.LogInformation("[GLOBAL UNBAN] Removing global ban for {SteamId}", ban.SteamId); + _logger.LogInformation("[GLOBAL UNBAN] Removing global ban for {SteamId}", ban.SteamId?.Replace("\r", "").Replace("\n", "")); // Find all server-specific bans created by this global ban var relatedServerBans = await dbContext.PlayerBans @@ -994,7 +994,7 @@ public async Task DeleteBan(int banId, [FromBody] DeleteBanReques { string unbanCommand = $"unban {serverBan.SteamId}"; _rconService.SendRconCommand(unbanCommand, serverBan.ServerId); - _logger.LogInformation("[GLOBAL UNBAN] Sent unban command for {SteamId} on server {ServerId}", ban.SteamId, serverBan.ServerId); + _logger.LogInformation("[GLOBAL UNBAN] Sent unban command for {SteamId} on server {ServerId}", ban.SteamId?.Replace("\r", "").Replace("\n", ""), serverBan.ServerId); } catch (Exception ex) { @@ -1057,7 +1057,7 @@ public async Task DeleteBan(int banId, [FromBody] DeleteBanReques { string unbanCommand = $"unban {ban.SteamId}"; _rconService.SendRconCommand(unbanCommand, ban.ServerId); - _logger.LogInformation("[UNBAN] Sent unban command for SteamID {SteamId} on server {ServerId}", ban.SteamId, ban.ServerId); + _logger.LogInformation("[UNBAN] Sent unban command for SteamID {SteamId} on server {ServerId}", ban.SteamId?.Replace("\r", "").Replace("\n", ""), ban.ServerId); } catch (Exception ex) { @@ -1068,7 +1068,7 @@ public async Task DeleteBan(int banId, [FromBody] DeleteBanReques // Remove the ban record from database dbContext.PlayerBans.Remove(ban); await dbContext.SaveChangesAsync(); - _logger.LogInformation("[UNBAN] Removed ban record for SteamID {SteamId} from database", ban.SteamId); + _logger.LogInformation("[UNBAN] Removed ban record for SteamID {SteamId} from database", ban.SteamId?.Replace("\r", "").Replace("\n", "")); return Ok(new { message = "Player unbanned successfully on server and database updated" }); } @@ -1342,12 +1342,12 @@ public async Task GiveItem([FromBody] GiveItemRequest request) // Build the RCON command: inventory.giveto var command = $"inventory.giveto {request.SteamId} {request.ShortName} {request.Quantity}"; - _logger.LogInformation("[GIVE ITEM] Executing command: {Command} for player {PlayerName}", command, request.PlayerName); + _logger.LogInformation("[GIVE ITEM] Executing command: {Command} for player {PlayerName}", command?.Replace("\r", "").Replace("\n", ""), request.PlayerName?.Replace("\r", "").Replace("\n", "")); // Execute the RCON command var response = await _rconService.ExecuteCommandWithResponse(command, request.ServerId); - _logger.LogDebug("[GIVE ITEM] Command response: {Response}", response); + _logger.LogDebug("[GIVE ITEM] Command response: {Response}", response?.Replace("\r", "").Replace("\n", "")); return Ok(new { success = true, diff --git a/RustRconServerManager.Backend/Controllers/ModsController.cs b/RustRconServerManager.Backend/Controllers/ModsController.cs index a5d600f..ca1ff73 100644 --- a/RustRconServerManager.Backend/Controllers/ModsController.cs +++ b/RustRconServerManager.Backend/Controllers/ModsController.cs @@ -77,7 +77,7 @@ public async Task GetPlugins() // Execute RCON command to get plugins string? rconResponse = await _rconBackgroundService.ExecuteCommandWithResponse(command, serverId); - _logger.LogInformation("RCON Response for '{Command}': {Response}", command, rconResponse ?? "NULL"); + _logger.LogInformation("RCON Response for '{Command}': {Response}", command?.Replace("\r", "").Replace("\n", ""), rconResponse?.Replace("\r", "").Replace("\n", "") ?? "NULL"); if (string.IsNullOrWhiteSpace(rconResponse)) { @@ -182,12 +182,12 @@ public async Task LoadPlugin([FromBody] Mods_ReloadPluginDTO requ ? $"c.load {request.PluginName}" : $"o.load {request.PluginName}"; - _logger.LogInformation("Executing command: {Command} for server {ServerId}", command, serverId); + _logger.LogInformation("Executing command: {Command} for server {ServerId}", command?.Replace("\r", "").Replace("\n", ""), serverId); // Execute RCON command string? rconResponse = await _rconBackgroundService.ExecuteCommandWithResponse(command, serverId); - _logger.LogInformation("RCON Response for '{Command}': {Response}", command, rconResponse ?? "NULL"); + _logger.LogInformation("RCON Response for '{Command}': {Response}", command?.Replace("\r", "").Replace("\n", ""), rconResponse?.Replace("\r", "").Replace("\n", "") ?? "NULL"); return Ok(new { @@ -249,12 +249,12 @@ public async Task ReloadPlugin([FromBody] Mods_ReloadPluginDTO re ? $"c.reload {request.PluginName}" : $"o.reload {request.PluginName}"; - _logger.LogInformation("Executing command: {Command} for server {ServerId}", command, serverId); + _logger.LogInformation("Executing command: {Command} for server {ServerId}", command?.Replace("\r", "").Replace("\n", ""), serverId); // Execute RCON command string? rconResponse = await _rconBackgroundService.ExecuteCommandWithResponse(command, serverId); - _logger.LogInformation("RCON Response for '{Command}': {Response}", command, rconResponse ?? "NULL"); + _logger.LogInformation("RCON Response for '{Command}': {Response}", command?.Replace("\r", "").Replace("\n", ""), rconResponse?.Replace("\r", "").Replace("\n", "") ?? "NULL"); return Ok(new { @@ -316,12 +316,12 @@ public async Task UnloadPlugin([FromBody] Mods_UnloadPluginDTO re ? $"c.unload {request.PluginName}" : $"o.unload {request.PluginName}"; - _logger.LogInformation("Executing command: {Command} for server {ServerId}", command, serverId); + _logger.LogInformation("Executing command: {Command} for server {ServerId}", command?.Replace("\r", "").Replace("\n", ""), serverId); // Execute RCON command string? rconResponse = await _rconBackgroundService.ExecuteCommandWithResponse(command, serverId); - _logger.LogInformation("RCON Response for '{Command}': {Response}", command, rconResponse ?? "NULL"); + _logger.LogInformation("RCON Response for '{Command}': {Response}", command?.Replace("\r", "").Replace("\n", ""), rconResponse?.Replace("\r", "").Replace("\n", "") ?? "NULL"); return Ok(new { @@ -377,12 +377,12 @@ public async Task ReloadAll() ? "c.reload *" : "o.reload *"; - _logger.LogInformation("Executing command: {Command} for server {ServerId}", command, serverId); + _logger.LogInformation("Executing command: {Command} for server {ServerId}", command?.Replace("\r", "").Replace("\n", ""), serverId); // Execute RCON command string? rconResponse = await _rconBackgroundService.ExecuteCommandWithResponse(command, serverId); - _logger.LogInformation("RCON Response for '{Command}': {Response}", command, rconResponse ?? "NULL"); + _logger.LogInformation("RCON Response for '{Command}': {Response}", command?.Replace("\r", "").Replace("\n", ""), rconResponse?.Replace("\r", "").Replace("\n", "") ?? "NULL"); return Ok(new { @@ -444,7 +444,7 @@ public async Task SetPluginSource([FromBody] Mods_SetPluginSource _dbContext.ServerPluginSources.Remove(existingEntry); await _dbContext.SaveChangesAsync(); - _logger.LogInformation("Removed plugin source for {PluginName} on server {ServerId}", request.PluginName, serverId); + _logger.LogInformation("Removed plugin source for {PluginName} on server {ServerId}", request.PluginName?.Replace("\r", "").Replace("\n", ""), serverId); return Ok(new { @@ -468,7 +468,7 @@ public async Task SetPluginSource([FromBody] Mods_SetPluginSource await _dbContext.SaveChangesAsync(); _logger.LogInformation("Updated plugin source for {PluginName} on server {ServerId} to {Source}", - request.PluginName, serverId, request.Source.Value); + request.PluginName?.Replace("\r", "").Replace("\n", ""), serverId, request.Source.Value); return Ok(new { @@ -491,7 +491,7 @@ public async Task SetPluginSource([FromBody] Mods_SetPluginSource await _dbContext.SaveChangesAsync(); _logger.LogInformation("Created plugin source for {PluginName} on server {ServerId} as {Source}", - request.PluginName, serverId, request.Source.Value); + request.PluginName?.Replace("\r", "").Replace("\n", ""), serverId, request.Source.Value); return Ok(new { @@ -636,7 +636,7 @@ public async Task CheckPluginVersion([FromQuery] string pluginNam } catch (Exception ex) { - _logger.LogError(ex, $"Error checking version for plugin {pluginName}"); + _logger.LogError(ex, "Error checking version for plugin {PluginName}", pluginName?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, ApiErrorHelper.FormatError("Error checking plugin version", ex)); } } diff --git a/RustRconServerManager.Backend/Controllers/PanelSettingsController.cs b/RustRconServerManager.Backend/Controllers/PanelSettingsController.cs index ceb4b0e..5688bb0 100644 --- a/RustRconServerManager.Backend/Controllers/PanelSettingsController.cs +++ b/RustRconServerManager.Backend/Controllers/PanelSettingsController.cs @@ -69,7 +69,7 @@ public async Task> GetMySettings() _dbContext.PanelSettings.Add(panelSettings); await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"[PanelSettingsController] Created default panel settings for SystemProfile {applicationUser.SystemProfileId}"); + _logger.LogInformation("[PanelSettingsController] Created default panel settings for SystemProfile {SystemProfileId}", applicationUser.SystemProfileId); } return Ok(MapToDto(panelSettings)); @@ -164,6 +164,9 @@ public async Task PurgeData([FromBody] PurgeDataRequestDto reques var applicationUser = await _dbContext.Users.FindAsync(userId); if (applicationUser == null) return NotFound("User not found"); + if (!applicationUser.isAdmin) + return Forbid(); + var serverIds = await _dbContext.RconServers .Where(s => s.SystemProfileId == applicationUser.SystemProfileId) .Select(s => s.Id) @@ -215,7 +218,7 @@ public async Task PurgeData([FromBody] PurgeDataRequestDto reques } _logger.LogInformation("[PanelSettingsController] User {UserId} purged {Count} records from {Category} (older than {Days} days)", - userId, deleted, request.Category, request.OlderThanDays); + userId, deleted, request.Category?.Replace("\r", "").Replace("\n", ""), request.OlderThanDays); return Ok(new { deleted, category = request.Category }); } @@ -256,8 +259,8 @@ public async Task> SetLogLevel([FromBody] SetLogL LogLevelState.Minimum = parsedLevel; - _logger.LogInformation("[PanelSettingsController] Minimum log level changed to {Level} by {User}", - parsedLevel, currentUser.Email); + _logger.LogInformation("[PanelSettingsController] Minimum log level changed to {Level} by {UserId}", + parsedLevel, currentUser.Id); return Ok(MapToDto(panelSettings)); } @@ -295,8 +298,8 @@ public async Task> SetAutoUpdate([FromBody] SetAu _autoUpdateFlagFileService.Write(dto.AutoUpdateEnabled); - _logger.LogInformation("[PanelSettingsController] Auto-update {State} by {User}", - dto.AutoUpdateEnabled ? "enabled" : "disabled", currentUser.Email); + _logger.LogInformation("[PanelSettingsController] Auto-update {State} by {UserId}", + dto.AutoUpdateEnabled ? "enabled" : "disabled", currentUser.Id); return Ok(MapToDto(panelSettings)); } @@ -330,8 +333,8 @@ public async Task> SetDeveloperMode([FromBody] Se panelSettings.UpdatedAt = DateTime.UtcNow; await _dbContext.SaveChangesAsync(); - _logger.LogInformation("[PanelSettingsController] Developer mode {State} by {User}", - dto.DeveloperModeEnabled ? "enabled" : "disabled", currentUser.Email); + _logger.LogInformation("[PanelSettingsController] Developer mode {State} by {UserId}", + dto.DeveloperModeEnabled ? "enabled" : "disabled", currentUser.Id); return Ok(MapToDto(panelSettings)); } @@ -365,8 +368,8 @@ public async Task> SetAnalytics([FromBody] SetAna panelSettings.UpdatedAt = DateTime.UtcNow; await _dbContext.SaveChangesAsync(); - _logger.LogInformation("[PanelSettingsController] Anonymous analytics {State} by {User}", - dto.AnalyticsEnabled ? "enabled" : "disabled", currentUser.Email); + _logger.LogInformation("[PanelSettingsController] Anonymous analytics {State} by {UserId}", + dto.AnalyticsEnabled ? "enabled" : "disabled", currentUser.Id); return Ok(MapToDto(panelSettings)); } @@ -424,8 +427,8 @@ public async Task> SetSteamApiKey([FromBody] SetS panelSettings.UpdatedAt = DateTime.UtcNow; await _dbContext.SaveChangesAsync(); - _logger.LogInformation("[PanelSettingsController] Steam API key {Action} by {User}", - dto.RemoveKey ? "removed" : "updated", currentUser.Email); + _logger.LogInformation("[PanelSettingsController] Steam API key {Action} by {UserId}", + dto.RemoveKey ? "removed" : "updated", currentUser.Id); return Ok(MapToDto(panelSettings)); } @@ -508,8 +511,8 @@ public async Task> UpsertVacBanOverride await _dbContext.SaveChangesAsync(); - _logger.LogInformation("[PanelSettingsController] VAC-ban override upserted for SteamID {SteamId} by {User}", - steamId, currentUser.Email); + _logger.LogInformation("[PanelSettingsController] VAC-ban override upserted for SteamID {SteamId} by {UserId}", + steamId.Replace("\r", "").Replace("\n", ""), currentUser.Id); return Ok(new DeveloperVacBanOverrideDto { @@ -547,8 +550,8 @@ public async Task DeleteVacBanOverride(string steamId) if (deleted == 0) return NotFound("Override not found"); - _logger.LogInformation("[PanelSettingsController] VAC-ban override removed for SteamID {SteamId} by {User}", - steamId, currentUser.Email); + _logger.LogInformation("[PanelSettingsController] VAC-ban override removed for SteamID {SteamId} by {UserId}", + steamId.Replace("\r", "").Replace("\n", ""), currentUser.Id); return Ok(); } diff --git a/RustRconServerManager.Backend/Controllers/PermissionsManagerController.cs b/RustRconServerManager.Backend/Controllers/PermissionsManagerController.cs index 29ca130..df247c9 100644 --- a/RustRconServerManager.Backend/Controllers/PermissionsManagerController.cs +++ b/RustRconServerManager.Backend/Controllers/PermissionsManagerController.cs @@ -3,9 +3,9 @@ using Microsoft.EntityFrameworkCore; using RustRconServerManager.Backend.Database; using RustRconServerManager.Backend.Extensions; +using RustRconServerManager.Backend.Helpers; using RustRconServerManager.Backend.Interfaces; using RustRconServerManager.Backend.Models; -using RustRconServerManager.Backend.Helpers; using RustRconServerManager.Shared.PermissionsManager; using System.Text.RegularExpressions; @@ -153,7 +153,7 @@ public async Task GetGroupPlayers([FromQuery] string groupName) // Execute RCON command to get group details string? rconResponse = await _rconBackgroundService.ExecuteCommandWithResponse(command, serverId); - _logger.LogInformation("RCON Response for '{Command}': {Response}", command, rconResponse ?? "NULL"); + _logger.LogInformation("RCON Response for '{Command}': {Response}", command.Replace("\r", "").Replace("\n", ""), rconResponse?.Replace("\r", "").Replace("\n", "") ?? "NULL"); if (string.IsNullOrWhiteSpace(rconResponse)) { @@ -163,7 +163,7 @@ public async Task GetGroupPlayers([FromQuery] string groupName) // Parse the RCON response to extract players var players = ParsePlayersFromGroupResponse(rconResponse, groupName); - _logger.LogInformation("Parsed {Count} players for group {GroupName}", players.Count, groupName); + _logger.LogInformation("Parsed {Count} players for group {GroupName}", players.Count, groupName.Replace("\r", "").Replace("\n", "")); return Ok(players); } @@ -277,12 +277,12 @@ public async Task AddPlayerToGroup([FromBody] PermissionsManager_ ? $"c.usergroup add {request.SteamId} {request.GroupName}" : $"o.usergroup add {request.SteamId} {request.GroupName}"; - _logger.LogInformation("Executing command: {Command} for server {ServerId}", command, serverId); + _logger.LogInformation("Executing command: {Command} for server {ServerId}", command.Replace("\r", "").Replace("\n", ""), serverId); // Execute RCON command string? rconResponse = await _rconBackgroundService.ExecuteCommandWithResponse(command, serverId); - _logger.LogInformation("RCON Response for '{Command}': {Response}", command, rconResponse ?? "NULL"); + _logger.LogInformation("RCON Response for '{Command}': {Response}", command.Replace("\r", "").Replace("\n", ""), rconResponse?.Replace("\r", "").Replace("\n", "") ?? "NULL"); return Ok(new { message = $"Player {request.SteamId} added to group {request.GroupName}", @@ -342,12 +342,12 @@ public async Task RemovePlayerFromGroup([FromBody] PermissionsMan ? $"c.usergroup remove {request.SteamId} {request.GroupName}" : $"o.usergroup remove {request.SteamId} {request.GroupName}"; - _logger.LogInformation("Executing command: {Command} for server {ServerId}", command, serverId); + _logger.LogInformation("Executing command: {Command} for server {ServerId}", command.Replace("\r", "").Replace("\n", ""), serverId); // Execute RCON command string? rconResponse = await _rconBackgroundService.ExecuteCommandWithResponse(command, serverId); - _logger.LogInformation("RCON Response for '{Command}': {Response}", command, rconResponse ?? "NULL"); + _logger.LogInformation("RCON Response for '{Command}': {Response}", command.Replace("\r", "").Replace("\n", ""), rconResponse?.Replace("\r", "").Replace("\n", "") ?? "NULL"); return Ok(new { message = $"Player {request.SteamId} removed from group {request.GroupName}", diff --git a/RustRconServerManager.Backend/Controllers/PlayerInspectController.cs b/RustRconServerManager.Backend/Controllers/PlayerInspectController.cs index 87e5e21..9a41ea4 100644 --- a/RustRconServerManager.Backend/Controllers/PlayerInspectController.cs +++ b/RustRconServerManager.Backend/Controllers/PlayerInspectController.cs @@ -210,7 +210,7 @@ public async Task GetPlayerData(string steamId) } catch (Exception ex) { - _logger.LogError(ex, $"Error fetching player data for Steam ID {steamId}"); + _logger.LogError(ex, "Error fetching player data for Steam ID {SteamId}", steamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, new { message = "Internal server error" }); } } @@ -257,7 +257,7 @@ public async Task GetPlayerChatMessages(string steamId, [FromQuer } catch (Exception ex) { - _logger.LogError(ex, $"Error fetching chat messages for Steam ID {steamId}"); + _logger.LogError(ex, "Error fetching chat messages for Steam ID {SteamId}", steamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, new { message = "Internal server error" }); } } @@ -304,7 +304,7 @@ public async Task SearchPlayerChatMessages(string steamId, [FromQ } catch (Exception ex) { - _logger.LogError(ex, $"Error searching chat messages for Steam ID {steamId}"); + _logger.LogError(ex, "Error searching chat messages for Steam ID {SteamId}", steamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, new { message = "Internal server error" }); } } @@ -354,7 +354,7 @@ public async Task GetPlayerKills(string steamId, [FromQuery] int } catch (Exception ex) { - _logger.LogError(ex, $"Error fetching kills for Steam ID {steamId}"); + _logger.LogError(ex, "Error fetching kills for Steam ID {SteamId}", steamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, new { message = "Internal server error" }); } } @@ -404,7 +404,7 @@ public async Task GetPlayerDeaths(string steamId, [FromQuery] int } catch (Exception ex) { - _logger.LogError(ex, $"Error fetching deaths for Steam ID {steamId}"); + _logger.LogError(ex, "Error fetching deaths for Steam ID {SteamId}", steamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, new { message = "Internal server error" }); } } @@ -496,7 +496,7 @@ public async Task ClearPlayerData(string steamId, [FromBody] Clea await dbContext.SaveChangesAsync(); _logger.LogInformation("[PlayerInspect] Cleared data for {SteamId} on server {ServerId}: Chat={Chat}, Kills={Kills}, Deaths={Deaths}, Notes={Notes}, Reports={Reports}, BanHistory={BanHistory}, PlayerDeleted={Deleted}", - steamId, request.ServerId, deletedChat, deletedKills, deletedDeaths, deletedNotes, deletedReports, deletedBanHistory, request.DeletePlayer); + steamId?.Replace("\r", "").Replace("\n", ""), request.ServerId, deletedChat, deletedKills, deletedDeaths, deletedNotes, deletedReports, deletedBanHistory, request.DeletePlayer); return Ok(new { @@ -512,7 +512,7 @@ public async Task ClearPlayerData(string steamId, [FromBody] Clea } catch (Exception ex) { - _logger.LogError(ex, "Error clearing player data for {SteamId}", steamId); + _logger.LogError(ex, "Error clearing player data for {SteamId}", steamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, new { message = "Internal server error" }); } } @@ -562,7 +562,7 @@ public async Task GetPlayerInventory(string steamId) } catch (Exception ex) { - _logger.LogError(ex, "Error getting player inventory for {SteamId}", steamId); + _logger.LogError(ex, "Error getting player inventory for {SteamId}", steamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, "An error occurred while fetching the inventory."); } } diff --git a/RustRconServerManager.Backend/Controllers/PlayerNotesController.cs b/RustRconServerManager.Backend/Controllers/PlayerNotesController.cs index 9702d57..f15af79 100644 --- a/RustRconServerManager.Backend/Controllers/PlayerNotesController.cs +++ b/RustRconServerManager.Backend/Controllers/PlayerNotesController.cs @@ -61,7 +61,7 @@ public async Task> GetPlayerNotes(string st } catch (Exception ex) { - _logger.LogError(ex, "[PlayerNotesController] Error getting notes for player {SteamId}", steamId); + _logger.LogError(ex, "[PlayerNotesController] Error getting notes for player {SteamId}", steamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, new { error = "Error retrieving player notes" }); } } @@ -100,7 +100,7 @@ public async Task> CreatePlayerNote([FromBody] Creat await _dbContext.SaveChangesAsync(); _logger.LogInformation("[PlayerNotesController] User {UserId} created note {NoteId} for player {SteamId} on server {ServerId}", - userId, playerNote.Id, request.SteamId, request.ServerId); + userId, playerNote.Id, request.SteamId?.Replace("\r", "").Replace("\n", ""), request.ServerId); var dto = new PlayerNoteDTO { @@ -117,7 +117,7 @@ public async Task> CreatePlayerNote([FromBody] Creat } catch (Exception ex) { - _logger.LogError(ex, "[PlayerNotesController] Error creating note for player {SteamId}", request.SteamId); + _logger.LogError(ex, "[PlayerNotesController] Error creating note for player {SteamId}", request.SteamId?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, new { error = "Error creating player note" }); } } @@ -155,7 +155,7 @@ public async Task> UpdatePlayerNote(int noteId, [Fro var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; _logger.LogInformation("[PlayerNotesController] User {UserId} updated note {NoteId} for player {SteamId}", - userId, noteId, playerNote.SteamId); + userId, noteId, playerNote.SteamId?.Replace("\r", "").Replace("\n", "")); var dto = new PlayerNoteDTO { @@ -202,7 +202,7 @@ public async Task DeletePlayerNote(int noteId) var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; _logger.LogInformation("[PlayerNotesController] User {UserId} deleted note {NoteId} for player {SteamId}", - userId, noteId, playerNote.SteamId); + userId, noteId, playerNote.SteamId?.Replace("\r", "").Replace("\n", "")); return NoContent(); } diff --git a/RustRconServerManager.Backend/Controllers/PresetCommandsController.cs b/RustRconServerManager.Backend/Controllers/PresetCommandsController.cs index a7563cc..93ac276 100644 --- a/RustRconServerManager.Backend/Controllers/PresetCommandsController.cs +++ b/RustRconServerManager.Backend/Controllers/PresetCommandsController.cs @@ -5,6 +5,7 @@ using RustRconServerManager.Backend.Extensions; using RustRconServerManager.Backend.Models; using RustRconServerManager.Shared.PresetCommand; +using System.Security.Claims; namespace RustRconServerManager.Backend.Controllers { @@ -34,7 +35,7 @@ public async Task>> GetPresetCommands(int se { if (!await User.HasServerAccess(_dbContext, serverId)) { - _logger.LogWarning($"[PresetCommandsController] User {User.GetEmail()} attempted to access server {serverId} without authorization"); + _logger.LogWarning("[PresetCommandsController] User {UserId} attempted to access server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, serverId); return Forbid(); } @@ -49,7 +50,7 @@ public async Task>> GetPresetCommands(int se } catch (Exception ex) { - _logger.LogError(ex, $"[PresetCommandsController] Error getting preset commands for server {serverId}"); + _logger.LogError(ex, "[PresetCommandsController] Error getting preset commands for server {ServerId}", serverId); return StatusCode(500, new { error = "Failed to retrieve preset commands" }); } } @@ -73,7 +74,7 @@ public async Task> CreatePresetCommand([FromBody] { if (!await User.HasServerAccess(_dbContext, dto.RconServerId.Value)) { - _logger.LogWarning($"[PresetCommandsController] User {User.GetEmail()} attempted to create preset for server {dto.RconServerId} without authorization"); + _logger.LogWarning("[PresetCommandsController] User {UserId} attempted to create preset for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, dto.RconServerId); return Forbid(); } } @@ -92,7 +93,7 @@ public async Task> CreatePresetCommand([FromBody] _dbContext.PresetCommands.Add(command); await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"[PresetCommandsController] User {User.GetEmail()} created preset command {command.Id} '{command.Name}'"); + _logger.LogInformation("[PresetCommandsController] User {UserId} created preset command {PresetId} '{PresetName}'", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, command.Id, command.Name?.Replace("\r", "").Replace("\n", "")); return CreatedAtAction(nameof(GetPresetCommands), new { serverId = command.RconServerId ?? 0 }, MapToDto(command)); } @@ -118,7 +119,7 @@ public async Task> UpdatePresetCommand(int id, [F // Verify access to the server the preset belongs to if (existing.RconServerId.HasValue && !await User.HasServerAccess(_dbContext, existing.RconServerId.Value)) { - _logger.LogWarning($"[PresetCommandsController] User {User.GetEmail()} attempted to update preset {id} without authorization"); + _logger.LogWarning("[PresetCommandsController] User {UserId} attempted to update preset {PresetId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id); return Forbid(); } @@ -138,13 +139,13 @@ public async Task> UpdatePresetCommand(int id, [F await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"[PresetCommandsController] User {User.GetEmail()} updated preset command {id}"); + _logger.LogInformation("[PresetCommandsController] User {UserId} updated preset command {PresetId}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id); return Ok(MapToDto(existing)); } catch (Exception ex) { - _logger.LogError(ex, $"[PresetCommandsController] Error updating preset command {id}"); + _logger.LogError(ex, "[PresetCommandsController] Error updating preset command {PresetId}", id); return StatusCode(500, new { error = "Failed to update preset command" }); } } @@ -164,20 +165,20 @@ public async Task DeletePresetCommand(int id) // Verify access to the server the preset belongs to if (command.RconServerId.HasValue && !await User.HasServerAccess(_dbContext, command.RconServerId.Value)) { - _logger.LogWarning($"[PresetCommandsController] User {User.GetEmail()} attempted to delete preset {id} without authorization"); + _logger.LogWarning("[PresetCommandsController] User {UserId} attempted to delete preset {PresetId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id); return Forbid(); } _dbContext.PresetCommands.Remove(command); await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"[PresetCommandsController] User {User.GetEmail()} deleted preset command {id}"); + _logger.LogInformation("[PresetCommandsController] User {UserId} deleted preset command {PresetId}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id); return NoContent(); } catch (Exception ex) { - _logger.LogError(ex, $"[PresetCommandsController] Error deleting preset command {id}"); + _logger.LogError(ex, "[PresetCommandsController] Error deleting preset command {PresetId}", id); return StatusCode(500, new { error = "Failed to delete preset command" }); } } diff --git a/RustRconServerManager.Backend/Controllers/PublicImagesController.cs b/RustRconServerManager.Backend/Controllers/PublicImagesController.cs index 92f113f..68ec459 100644 --- a/RustRconServerManager.Backend/Controllers/PublicImagesController.cs +++ b/RustRconServerManager.Backend/Controllers/PublicImagesController.cs @@ -45,7 +45,16 @@ public async Task GetServerImage(string instanceHash, int serverI } // Try to serve from disk first (fast) - var filePath = _mapStorageService.GetServerImageFilePath(instanceHash, serverId, filename); + string filePath; + try + { + filePath = _mapStorageService.GetServerImageFilePath(instanceHash, serverId, filename); + } + catch (ArgumentException) + { + return NotFound("Invalid image path."); + } + if (System.IO.File.Exists(filePath)) { var imageBytes = await System.IO.File.ReadAllBytesAsync(filePath); diff --git a/RustRconServerManager.Backend/Controllers/RconController.ServerManagement.cs b/RustRconServerManager.Backend/Controllers/RconController.ServerManagement.cs index 5f83f80..318941b 100644 --- a/RustRconServerManager.Backend/Controllers/RconController.ServerManagement.cs +++ b/RustRconServerManager.Backend/Controllers/RconController.ServerManagement.cs @@ -46,22 +46,17 @@ public async Task DeleteServer(int serverId) { RconServer server = await dbContext.RconServers.SingleOrDefaultAsync(s => s.Id == serverId && s.SystemProfileId == user.SystemProfileId); - - if (server.SystemProfileId != user.SystemProfileId) + + if (server == null) { - return Unauthorized("Server does not match users systemprofile."); + return NotFound("Server not found or does not belong to your systemprofile."); } - else - { - dbContext.RconServers.Remove(server); - await dbContext.SaveChangesAsync(); - await _rconBackgroundService.DisconnectServerAsync(server.Id); - await _rconBackgroundService.HandleDeleteServer(server); - return Ok("Server successfully deleted"); - } - - - + + dbContext.RconServers.Remove(server); + await dbContext.SaveChangesAsync(); + await _rconBackgroundService.DisconnectServerAsync(server.Id); + await _rconBackgroundService.HandleDeleteServer(server); + return Ok("Server successfully deleted"); } diff --git a/RustRconServerManager.Backend/Controllers/SchedulerController.cs b/RustRconServerManager.Backend/Controllers/SchedulerController.cs index 39be0b1..ed60c96 100644 --- a/RustRconServerManager.Backend/Controllers/SchedulerController.cs +++ b/RustRconServerManager.Backend/Controllers/SchedulerController.cs @@ -44,7 +44,7 @@ public async Task>> GetScheduledCommands( { if (!await User.HasServerAccess(_dbContext, serverId)) { - _logger.LogWarning($"[SchedulerController] User {User.GetEmail()} attempted to access server {serverId} without authorization"); + _logger.LogWarning("[SchedulerController] User {UserId} attempted to access server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, serverId); return Forbid(); } @@ -55,7 +55,7 @@ public async Task>> GetScheduledCommands( } catch (Exception ex) { - _logger.LogError(ex, $"[SchedulerController] Error getting scheduled commands for server {serverId}"); + _logger.LogError(ex, "[SchedulerController] Error getting scheduled commands for server {ServerId}", serverId); return StatusCode(500, new { error = "Failed to retrieve scheduled commands" }); } } @@ -71,7 +71,7 @@ public async Task> CreateScheduledCommand([Fro { if (!await User.HasServerAccess(_dbContext, dto.RconServerId)) { - _logger.LogWarning($"[SchedulerController] User {User.GetEmail()} attempted to create command for server {dto.RconServerId} without authorization"); + _logger.LogWarning("[SchedulerController] User {UserId} attempted to create command for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, dto.RconServerId); return Forbid(); } @@ -105,7 +105,7 @@ public async Task> CreateScheduledCommand([Fro var utcOffset = dto.UtcOffsetMinutes ?? 0; var created = await _scheduledCommandService.CreateScheduledCommandAsync(command, utcOffset); - _logger.LogInformation($"[SchedulerController] User {User.GetEmail()} created scheduled command {created.Id} for server {created.RconServerId} (utcOffset={utcOffset})"); + _logger.LogInformation("[SchedulerController] User {UserId} created scheduled command {CommandId} for server {ServerId} (utcOffset={UtcOffset})", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, created.Id, created.RconServerId, utcOffset); return Ok(MapToDto(created)); } @@ -130,7 +130,7 @@ public async Task> UpdateScheduledCommand(int if (!await User.HasServerAccess(_dbContext, existing.RconServerId)) { - _logger.LogWarning($"[SchedulerController] User {User.GetEmail()} attempted to update command {id} for server {existing.RconServerId} without authorization"); + _logger.LogWarning("[SchedulerController] User {UserId} attempted to update command {CommandId} for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, existing.RconServerId); return Forbid(); } @@ -160,13 +160,13 @@ public async Task> UpdateScheduledCommand(int var utcOffset = dto.UtcOffsetMinutes ?? 0; var updated = await _scheduledCommandService.UpdateScheduledCommandAsync(existing, utcOffset); - _logger.LogInformation($"[SchedulerController] User {User.GetEmail()} updated scheduled command {id} (utcOffset={utcOffset})"); + _logger.LogInformation("[SchedulerController] User {UserId} updated scheduled command {CommandId} (utcOffset={UtcOffset})", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, utcOffset); return Ok(MapToDto(updated)); } catch (Exception ex) { - _logger.LogError(ex, $"[SchedulerController] Error updating scheduled command {id}"); + _logger.LogError(ex, "[SchedulerController] Error updating scheduled command {CommandId}", id); return StatusCode(500, new { error = "Failed to update scheduled command" }); } } @@ -185,19 +185,19 @@ public async Task DeleteScheduledCommand(int id) if (!await User.HasServerAccess(_dbContext, command.RconServerId)) { - _logger.LogWarning($"[SchedulerController] User {User.GetEmail()} attempted to delete command {id} for server {command.RconServerId} without authorization"); + _logger.LogWarning("[SchedulerController] User {UserId} attempted to delete command {CommandId} for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, command.RconServerId); return Forbid(); } await _scheduledCommandService.DeleteScheduledCommandAsync(id); - _logger.LogInformation($"[SchedulerController] User {User.GetEmail()} deleted scheduled command {id}"); + _logger.LogInformation("[SchedulerController] User {UserId} deleted scheduled command {CommandId}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id); return NoContent(); } catch (Exception ex) { - _logger.LogError(ex, $"[SchedulerController] Error deleting scheduled command {id}"); + _logger.LogError(ex, "[SchedulerController] Error deleting scheduled command {CommandId}", id); return StatusCode(500, new { error = "Failed to delete scheduled command" }); } } @@ -216,35 +216,35 @@ public async Task ExecuteNow(int id) if (!await User.HasServerAccess(_dbContext, command.RconServerId)) { - _logger.LogWarning($"[SchedulerController] User {User.GetEmail()} attempted to execute command {id} for server {command.RconServerId} without authorization"); + _logger.LogWarning("[SchedulerController] User {UserId} attempted to execute command {CommandId} for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, command.RconServerId); return Forbid(); } if (!_rconConnectionManager.TryGetClient(command.RconServerId, out var client) || !client.IsConnected) { - _logger.LogWarning($"[SchedulerController] Server {command.RconServerId} is not connected"); + _logger.LogWarning("[SchedulerController] Server {ServerId} is not connected", command.RconServerId); await _scheduledCommandService.MarkAsExecutedAsync(id, false, "Server not connected"); return BadRequest(new { error = "Server is not connected" }); } try { - _logger.LogInformation($"[SchedulerController] User {User.GetEmail()} executing command {id} ({command.Name}) manually: {command.Command}"); + _logger.LogInformation("[SchedulerController] User {UserId} executing command {CommandId} ({CommandName}) manually: {Command}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, command.Name?.Replace("\r", "").Replace("\n", ""), command.Command?.Replace("\r", "").Replace("\n", "")); await client.SendCommandAsync(command.Command); - _logger.LogInformation($"[SchedulerController] Command {id} executed successfully"); + _logger.LogInformation("[SchedulerController] Command {CommandId} executed successfully", id); return Ok(new { message = "Command executed successfully" }); } catch (Exception sendEx) { - _logger.LogError(sendEx, $"[SchedulerController] Failed to send command {id} to RCON"); + _logger.LogError(sendEx, "[SchedulerController] Failed to send command {CommandId} to RCON", id); return BadRequest(new { error = $"Failed to execute command: {sendEx.Message}" }); } } catch (Exception ex) { - _logger.LogError(ex, $"[SchedulerController] Error executing command {id}"); + _logger.LogError(ex, "[SchedulerController] Error executing command {CommandId}", id); return StatusCode(500, new { error = "Failed to execute command" }); } } @@ -263,20 +263,20 @@ public async Task> ToggleActive(int id) if (!await User.HasServerAccess(_dbContext, command.RconServerId)) { - _logger.LogWarning($"[SchedulerController] User {User.GetEmail()} attempted to toggle command {id} for server {command.RconServerId} without authorization"); + _logger.LogWarning("[SchedulerController] User {UserId} attempted to toggle command {CommandId} for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, command.RconServerId); return Forbid(); } command.IsActive = !command.IsActive; var updated = await _scheduledCommandService.UpdateScheduledCommandAsync(command); - _logger.LogInformation($"[SchedulerController] User {User.GetEmail()} toggled active status of command {id} to {updated.IsActive}"); + _logger.LogInformation("[SchedulerController] User {UserId} toggled active status of command {CommandId} to {IsActive}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, updated.IsActive); return Ok(MapToDto(updated)); } catch (Exception ex) { - _logger.LogError(ex, $"[SchedulerController] Error toggling active status of command {id}"); + _logger.LogError(ex, "[SchedulerController] Error toggling active status of command {CommandId}", id); return StatusCode(500, new { error = "Failed to toggle active status" }); } } diff --git a/RustRconServerManager.Backend/Controllers/ServerWebhookController.cs b/RustRconServerManager.Backend/Controllers/ServerWebhookController.cs index f7c2f0f..6c47a67 100644 --- a/RustRconServerManager.Backend/Controllers/ServerWebhookController.cs +++ b/RustRconServerManager.Backend/Controllers/ServerWebhookController.cs @@ -3,9 +3,9 @@ using Microsoft.EntityFrameworkCore; using RustRconServerManager.Backend.Database; using RustRconServerManager.Backend.Extensions; +using RustRconServerManager.Backend.Helpers; using RustRconServerManager.Backend.Interfaces; using RustRconServerManager.Backend.Models; -using RustRconServerManager.Backend.Helpers; using RustRconServerManager.Shared.ServerWebhooks; namespace RustRconServerManager.Backend.Controllers; @@ -336,7 +336,7 @@ public async Task TestWebhook([FromBody] ServerWebhooks_TestWebho _logger.LogInformation("TestWebhook: EventType={EventType}, WebhookUrl={WebhookUrl}", request.EventType, - string.IsNullOrWhiteSpace(request.WebhookUrl) ? "(empty)" : request.WebhookUrl.Substring(0, Math.Min(50, request.WebhookUrl.Length))); + string.IsNullOrWhiteSpace(request.WebhookUrl) ? "(empty)" : request.WebhookUrl.Substring(0, Math.Min(50, request.WebhookUrl.Length)).Replace("\r", "").Replace("\n", "")); ApplicationUser user = await User.GetUser(_dbContext); _logger.LogInformation("TestWebhook: User retrieved: {UserId}", user.Id); @@ -380,15 +380,19 @@ public async Task TestWebhook([FromBody] ServerWebhooks_TestWebho _logger.LogInformation("TestWebhook: Validating URL format"); if (!Uri.TryCreate(request.WebhookUrl, UriKind.Absolute, out var uri)) { - _logger.LogWarning("TestWebhook: Invalid URL format: {WebhookUrl}", request.WebhookUrl); + _logger.LogWarning("TestWebhook: Invalid URL format: {WebhookUrl}", request.WebhookUrl?.Replace("\r", "").Replace("\n", "")); return BadRequest("Invalid webhook URL format"); } _logger.LogInformation("TestWebhook: Checking if URL is Discord webhook"); - if (!request.WebhookUrl.Contains("discord.com/api/webhooks/", StringComparison.OrdinalIgnoreCase) && - !request.WebhookUrl.Contains("discordapp.com/api/webhooks/", StringComparison.OrdinalIgnoreCase)) + bool isDiscordHost = uri.Host.Equals("discord.com", StringComparison.OrdinalIgnoreCase) || + uri.Host.Equals("discordapp.com", StringComparison.OrdinalIgnoreCase) || + uri.Host.EndsWith(".discord.com", StringComparison.OrdinalIgnoreCase) || + uri.Host.EndsWith(".discordapp.com", StringComparison.OrdinalIgnoreCase); + + if (!isDiscordHost || !uri.AbsolutePath.StartsWith("/api/webhooks/", StringComparison.OrdinalIgnoreCase)) { - _logger.LogWarning("TestWebhook: URL is not a Discord webhook: {WebhookUrl}", request.WebhookUrl); + _logger.LogWarning("TestWebhook: URL is not a Discord webhook: {WebhookUrl}", request.WebhookUrl?.Replace("\r", "").Replace("\n", "")); return BadRequest("URL must be a valid Discord webhook URL (discord.com or discordapp.com)"); } diff --git a/RustRconServerManager.Backend/Controllers/StatsController.cs b/RustRconServerManager.Backend/Controllers/StatsController.cs index 914ab29..3f315bd 100644 --- a/RustRconServerManager.Backend/Controllers/StatsController.cs +++ b/RustRconServerManager.Backend/Controllers/StatsController.cs @@ -104,7 +104,8 @@ public async Task GetStats(string statType, string timeRange) // Combine aggregated and recent stats stats.AddRange(recentStats); - _logger.LogInformation($"Retrieved {stats.Count} stats records for server {serverId}, stat type '{statType}', time range '{timeRange}'"); + _logger.LogInformation("Retrieved {StatsCount} stats records for server {ServerId}, stat type '{StatType}', time range '{TimeRange}'", + stats.Count, serverId, statType?.Replace("\r", "").Replace("\n", ""), timeRange?.Replace("\r", "").Replace("\n", "")); // For player count, round to whole numbers; for other stats, use decimals bool isPlayerCount = statType.ToLower() == "players"; @@ -238,7 +239,7 @@ public async Task GetStats(string statType, string timeRange) } catch (Exception ex) { - _logger.LogError(ex, "Error getting stats for {StatType} over {TimeRange}", statType, timeRange); + _logger.LogError(ex, "Error getting stats for {StatType} over {TimeRange}", statType?.Replace("\r", "").Replace("\n", ""), timeRange?.Replace("\r", "").Replace("\n", "")); return StatusCode(500, ApiErrorHelper.FormatError("Error getting stats", ex)); } } diff --git a/RustRconServerManager.Backend/Controllers/TriggersController.cs b/RustRconServerManager.Backend/Controllers/TriggersController.cs index b0dad3a..b5a11d7 100644 --- a/RustRconServerManager.Backend/Controllers/TriggersController.cs +++ b/RustRconServerManager.Backend/Controllers/TriggersController.cs @@ -4,6 +4,7 @@ using RustRconServerManager.Backend.Database; using RustRconServerManager.Backend.Extensions; using RustRconServerManager.Backend.Models; +using System.Security.Claims; namespace RustRconServerManager.Backend.Controllers { @@ -74,7 +75,7 @@ public async Task> GetTrigger(int id) // Verify user has access to this server if (!await User.HasServerAccess(_dbContext, trigger.RconServerId)) { - _logger.LogWarning($"User {User.GetEmail()} attempted to access trigger {id} for server {trigger.RconServerId} without authorization"); + _logger.LogWarning("User {UserId} attempted to access trigger {TriggerId} for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, trigger.RconServerId); return Forbid(); } @@ -82,7 +83,7 @@ public async Task> GetTrigger(int id) } catch (Exception ex) { - _logger.LogError(ex, $"Error getting trigger {id}"); + _logger.LogError(ex, "Error getting trigger {TriggerId}", id); return StatusCode(500, new { error = "Failed to retrieve trigger" }); } } @@ -106,7 +107,7 @@ public async Task> CreateTrigger([FromBody] CreateTriggerD // Verify user has access to this server if (!await User.HasServerAccess(_dbContext, serverId)) { - _logger.LogWarning($"User {User.GetEmail()} attempted to create trigger for server {serverId} without authorization"); + _logger.LogWarning("User {UserId} attempted to create trigger for server {ServerId} without authorization", user.Id, serverId); return Forbid(); } @@ -144,7 +145,7 @@ public async Task> CreateTrigger([FromBody] CreateTriggerD _dbContext.Triggers.Add(trigger); await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"User {User.GetEmail()} created trigger {trigger.Id} for server {serverId}"); + _logger.LogInformation("User {UserId} created trigger {TriggerId} for server {ServerId}", user.Id, trigger.Id, serverId); return CreatedAtAction(nameof(GetTrigger), new { id = trigger.Id }, trigger); } @@ -170,7 +171,7 @@ public async Task> UpdateTrigger(int id, [FromBody] Create // Verify user has access to this server if (!await User.HasServerAccess(_dbContext, existing.RconServerId)) { - _logger.LogWarning($"User {User.GetEmail()} attempted to update trigger {id} for server {existing.RconServerId} without authorization"); + _logger.LogWarning("User {UserId} attempted to update trigger {TriggerId} for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, existing.RconServerId); return Forbid(); } @@ -202,13 +203,13 @@ public async Task> UpdateTrigger(int id, [FromBody] Create await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"User {User.GetEmail()} updated trigger {id}"); + _logger.LogInformation("User {UserId} updated trigger {TriggerId}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id); return Ok(existing); } catch (Exception ex) { - _logger.LogError(ex, $"Error updating trigger {id}"); + _logger.LogError(ex, "Error updating trigger {TriggerId}", id); return StatusCode(500, new { error = "Failed to update trigger" }); } } @@ -228,20 +229,20 @@ public async Task DeleteTrigger(int id) // Verify user has access to this server if (!await User.HasServerAccess(_dbContext, trigger.RconServerId)) { - _logger.LogWarning($"User {User.GetEmail()} attempted to delete trigger {id} for server {trigger.RconServerId} without authorization"); + _logger.LogWarning("User {UserId} attempted to delete trigger {TriggerId} for server {ServerId} without authorization", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id, trigger.RconServerId); return Forbid(); } _dbContext.Triggers.Remove(trigger); await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"User {User.GetEmail()} deleted trigger {id}"); + _logger.LogInformation("User {UserId} deleted trigger {TriggerId}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value, id); return NoContent(); } catch (Exception ex) { - _logger.LogError(ex, $"Error deleting trigger {id}"); + _logger.LogError(ex, "Error deleting trigger {TriggerId}", id); return StatusCode(500, new { error = "Failed to delete trigger" }); } } diff --git a/RustRconServerManager.Backend/Services/AuditLogService.cs b/RustRconServerManager.Backend/Services/AuditLogService.cs index bdf4710..11c6d51 100644 --- a/RustRconServerManager.Backend/Services/AuditLogService.cs +++ b/RustRconServerManager.Backend/Services/AuditLogService.cs @@ -46,7 +46,7 @@ public async Task LogAsync(ApplicationUser user, int? serverId, string action, s catch (Exception ex) { // Audit logging must never break the actual action it's recording. - _logger.LogError(ex, "[AuditLogService] Failed to write audit log entry for action {Action}", action); + _logger.LogError(ex, "[AuditLogService] Failed to write audit log entry for action {Action}", action.Replace("\r", "").Replace("\n", "")); } } } diff --git a/RustRconServerManager.Backend/Services/DiscordWebhookService.cs b/RustRconServerManager.Backend/Services/DiscordWebhookService.cs index bed7d89..22ced84 100644 --- a/RustRconServerManager.Backend/Services/DiscordWebhookService.cs +++ b/RustRconServerManager.Backend/Services/DiscordWebhookService.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using RustRconServerManager.Backend.Database; using RustRconServerManager.Backend.Interfaces; @@ -84,7 +84,7 @@ public async Task SendPlayerConnectAsync(int serverId, string playerName, string } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending player connect webhook for {playerName}"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending player connect webhook for {PlayerName}", serverId, playerName?.Replace("\r", "").Replace("\n", "")); } } @@ -146,7 +146,7 @@ public async Task SendPlayerDisconnectAsync(int serverId, string playerName, str } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending player disconnect webhook for {playerName}"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending player disconnect webhook for {PlayerName}", serverId, playerName?.Replace("\r", "").Replace("\n", "")); } } @@ -215,7 +215,7 @@ public async Task SendPlayerKillAsync(int serverId, string killerName, string ki } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending player kill webhook"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending player kill webhook", serverId); } } @@ -283,7 +283,7 @@ public async Task SendPlayerBanAsync(int serverId, string playerName, string ste } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending player ban webhook for {playerName}"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending player ban webhook for {PlayerName}", serverId, playerName?.Replace("\r", "").Replace("\n", "")); } } @@ -347,7 +347,7 @@ public async Task SendPlayerKickAsync(int serverId, string playerName, string st } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending player kick webhook for {playerName}"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending player kick webhook for {PlayerName}", serverId, playerName?.Replace("\r", "").Replace("\n", "")); } } @@ -418,7 +418,7 @@ public async Task SendPlayerReportAsync(int serverId, string reporterName, strin } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending player report webhook"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending player report webhook", serverId); } } @@ -443,7 +443,7 @@ public async Task SendServerOfflineAsync(int serverId, string serverName) // Text message format - use custom text content if provided, otherwise default var textMessage = string.IsNullOrWhiteSpace(settings.ServerOfflineTextContent) - ? $"🔴 **{serverName}** is offline!" + ? $"🔴 **{serverName}** is offline!" : ReplaceVariables(settings.ServerOfflineTextContent, variables); // Default embed format @@ -453,13 +453,13 @@ public async Task SendServerOfflineAsync(int serverId, string serverName) { new { - title = "🔴 Server Offline", + title = "🔴 Server Offline", description = $"**{serverName}** is no longer reachable", color = 15158332, // Red fields = new[] { new { name = "Server", value = serverName, inline = true }, - new { name = "Status", value = "❌ Offline", inline = true }, + new { name = "Status", value = "❌ Offline", inline = true }, new { name = "Time", value = $"", inline = false } }, footer = new { text = "Server Status Monitor" }, @@ -481,7 +481,7 @@ public async Task SendServerOfflineAsync(int serverId, string serverName) } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending server offline webhook for {serverName}"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending server offline webhook for {ServerName}", serverId, serverName?.Replace("\r", "").Replace("\n", "")); } } @@ -528,7 +528,7 @@ public async Task SendServerOnlineAsync(int serverId, string serverName, int dow // Text message format - use custom text content if provided, otherwise default var textMessage = string.IsNullOrWhiteSpace(settings.ServerOnlineTextContent) - ? $"✅ **{serverName}** is back online! (Downtime: {downtimeText})" + ? $"✅ **{serverName}** is back online! (Downtime: {downtimeText})" : ReplaceVariables(settings.ServerOnlineTextContent, variables); // Default embed format @@ -538,13 +538,13 @@ public async Task SendServerOnlineAsync(int serverId, string serverName, int dow { new { - title = "✅ Server Online", + title = "✅ Server Online", description = $"**{serverName}** is back online!", color = 3066993, // Green fields = new[] { new { name = "Server", value = serverName, inline = true }, - new { name = "Status", value = "✅ Online", inline = true }, + new { name = "Status", value = "✅ Online", inline = true }, new { name = "Downtime", value = downtimeText, inline = false }, new { name = "Back Online At", value = $"", inline = false } }, @@ -567,7 +567,7 @@ public async Task SendServerOnlineAsync(int serverId, string serverName, int dow } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending server online webhook for {serverName}"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending server online webhook for {ServerName}", serverId, serverName?.Replace("\r", "").Replace("\n", "")); } } @@ -629,7 +629,7 @@ public async Task SendServerProtectionAsync(int serverId, string playerName, str } catch (Exception ex) { - _logger.LogError(ex, $"[SERVER {serverId}] Error sending server protection webhook for {playerName}"); + _logger.LogError(ex, "[SERVER {ServerId}] Error sending server protection webhook for {PlayerName}", serverId, playerName?.Replace("\r", "").Replace("\n", "")); } } @@ -856,13 +856,13 @@ public async Task SendTestWebhookAsync(int serverId, string webhookUrl, WebhookE { new { - title = "🔴 Server Offline (TEST)", + title = "🔴 Server Offline (TEST)", description = $"**{serverName}** is no longer reachable", color = 15158332, fields = new[] { new { name = "Server", value = serverName, inline = true }, - new { name = "Status", value = "❌ Offline", inline = true }, + new { name = "Status", value = "❌ Offline", inline = true }, new { name = "Time", value = $"", inline = false } }, footer = new { text = "Server Status Monitor" }, @@ -870,7 +870,7 @@ public async Task SendTestWebhookAsync(int serverId, string webhookUrl, WebhookE } } }, - $"🔴 **{serverName}** is offline!" + $"🔴 **{serverName}** is offline!" ), WebhookEventType.ServerOnline => ( @@ -889,13 +889,13 @@ public async Task SendTestWebhookAsync(int serverId, string webhookUrl, WebhookE { new { - title = "✅ Server Online (TEST)", + title = "✅ Server Online (TEST)", description = $"**{serverName}** is back online!", color = 3066993, fields = new[] { new { name = "Server", value = serverName, inline = true }, - new { name = "Status", value = "✅ Online", inline = true }, + new { name = "Status", value = "✅ Online", inline = true }, new { name = "Downtime", value = "15 minutes", inline = false }, new { name = "Back Online At", value = $"", inline = false } }, @@ -904,7 +904,7 @@ public async Task SendTestWebhookAsync(int serverId, string webhookUrl, WebhookE } } }, - $"✅ **{serverName}** is back online! (Downtime: 15 minutes)" + $"✅ **{serverName}** is back online! (Downtime: 15 minutes)" ), WebhookEventType.ServerProtection => ( @@ -951,7 +951,7 @@ public async Task SendTestWebhookAsync(int serverId, string webhookUrl, WebhookE } catch (Exception ex) { - _logger.LogError(ex, $"Error sending test webhook for {eventType}"); + _logger.LogError(ex, "Error sending test webhook for {EventType}", eventType); throw; } } @@ -968,7 +968,7 @@ public async Task SendTestWebhookAsync(int serverId, string webhookUrl, WebhookE } catch (Exception ex) { - _logger.LogError(ex, $"Error loading webhook settings for server {serverId}"); + _logger.LogError(ex, "Error loading webhook settings for server {ServerId}", serverId); return null; } } @@ -985,7 +985,7 @@ private async Task GetServerNameAsync(int serverId) } catch (Exception ex) { - _logger.LogError(ex, $"Error loading server name for server {serverId}"); + _logger.LogError(ex, "Error loading server name for server {ServerId}", serverId); return $"Server {serverId}"; } } @@ -1001,17 +1001,17 @@ private async Task SendWebhookAsync(string webhookUrl, object payload, string ev if (response.IsSuccessStatusCode) { - _logger.LogInformation($"Successfully sent {eventName} webhook"); + _logger.LogInformation("Successfully sent {EventName} webhook", eventName); } else { var responseBody = await response.Content.ReadAsStringAsync(); - _logger.LogWarning($"Failed to send {eventName} webhook. Status: {response.StatusCode}, Response: {responseBody}"); + _logger.LogWarning("Failed to send {EventName} webhook. Status: {StatusCode}, Response: {ResponseBody}", eventName, response.StatusCode, responseBody?.Replace("\r", "").Replace("\n", "")); } } catch (Exception ex) { - _logger.LogError(ex, $"Error sending {eventName} webhook to {webhookUrl}"); + _logger.LogError(ex, "Error sending {EventName} webhook to {WebhookUrl}", eventName, webhookUrl?.Replace("\r", "").Replace("\n", "")); } } @@ -1069,7 +1069,7 @@ private object ParseCustomContent(string customContent, Dictionary SendPasswordRecoveryEmailAsync(string toEmail, string co var host = _configuration["Smtp:Host"]; if (string.IsNullOrWhiteSpace(host)) { - _logger.LogWarning("SMTP is not configured (Smtp:Host is empty) - password recovery email was not sent to {Email}", toEmail); + _logger.LogWarning("SMTP is not configured (Smtp:Host is empty) - password recovery email was not sent"); return false; } @@ -53,7 +53,7 @@ public async Task SendPasswordRecoveryEmailAsync(string toEmail, string co } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to send password recovery email to {Email}", toEmail); + _logger.LogWarning(ex, "Failed to send password recovery email"); return false; } } diff --git a/RustRconServerManager.Backend/Services/MapStorageService.cs b/RustRconServerManager.Backend/Services/MapStorageService.cs index f9fbbc3..05da9d7 100644 --- a/RustRconServerManager.Backend/Services/MapStorageService.cs +++ b/RustRconServerManager.Backend/Services/MapStorageService.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using RustRconServerManager.Backend.Database; +using RustRconServerManager.Backend.Helpers; namespace RustRconServerManager.Backend.Services { @@ -188,11 +189,26 @@ private async Task RestoreServerImagesFromDatabase(CancellationToken cancellatio /// /// Gets the file path for a server's custom image. /// Path format: Storage/public/{instanceHash}/{serverId}/{filename} + /// Resolves the path and verifies it is still contained within the public images + /// base directory, since instanceHash is attacker-controlled on the anonymous + /// image-serving endpoint and naive character blacklisting isn't safe here + /// (legitimate hashes are Base64 and may contain '/' and '+'). /// public string GetServerImageFilePath(string instanceHash, int serverId, string filename) { - var directory = Path.Combine(_environment.ContentRootPath, "Storage", "public", instanceHash, serverId.ToString()); - return Path.Combine(directory, filename); + var baseDirectory = Path.Combine(_environment.ContentRootPath, "Storage", "public"); + var directory = Path.Combine(baseDirectory, instanceHash, serverId.ToString()); + var filePath = Path.Combine(directory, filename); + + var fullBase = Path.GetFullPath(baseDirectory) + Path.DirectorySeparatorChar; + var fullPath = Path.GetFullPath(filePath); + + if (!fullPath.StartsWith(fullBase, StringComparison.Ordinal)) + { + throw new ArgumentException("Invalid instanceHash, serverId, or filename: resolved path escapes the public images directory."); + } + + return fullPath; } /// @@ -200,14 +216,13 @@ public string GetServerImageFilePath(string instanceHash, int serverId, string f /// public async Task SaveServerImageToDisk(string instanceHash, int serverId, string filename, byte[] imageData) { - var directory = Path.Combine(_environment.ContentRootPath, "Storage", "public", instanceHash, serverId.ToString()); - Directory.CreateDirectory(directory); + var filePath = GetServerImageFilePath(instanceHash, serverId, filename); + Directory.CreateDirectory(Path.GetDirectoryName(filePath)!); - var filePath = Path.Combine(directory, filename); await File.WriteAllBytesAsync(filePath, imageData); _logger.LogInformation("MapStorageService: Saved server image {Filename} for server {ServerId} to disk ({Size} bytes)", - filename, serverId, imageData.Length); + filename.Replace("\r", "").Replace("\n", ""), serverId, imageData.Length); } /// @@ -220,7 +235,7 @@ public void DeleteServerImageFromDisk(string instanceHash, int serverId, string { File.Delete(filePath); _logger.LogInformation("MapStorageService: Deleted server image {Filename} for server {ServerId}", - filename, serverId); + filename.Replace("\r", "").Replace("\n", ""), serverId); } } } diff --git a/RustRconServerManager.Backend/Services/PluginVersionCheckService.cs b/RustRconServerManager.Backend/Services/PluginVersionCheckService.cs index c8cc327..b3ee64f 100644 --- a/RustRconServerManager.Backend/Services/PluginVersionCheckService.cs +++ b/RustRconServerManager.Backend/Services/PluginVersionCheckService.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using RustRconServerManager.Backend.Database; @@ -9,7 +9,7 @@ namespace RustRconServerManager.Backend.Services; /// /// Service for checking plugin versions against Codefling and Umod. -/// Uses a local DB cache (PluginVersionCache) — entries expire after 30 minutes. +/// Uses a local DB cache (PluginVersionCache) — entries expire after 30 minutes. /// public class PluginVersionCheckService { @@ -45,23 +45,23 @@ public PluginVersionCheckService( if (pluginSource == null) { - _logger.LogInformation($"[NO SOURCE] Plugin {pluginName} has no source configured for server {serverId} - skipping version check"); + _logger.LogInformation("[NO SOURCE] Plugin {PluginName} has no source configured for server {ServerId} - skipping version check", pluginName?.Replace("\r", "").Replace("\n", ""), serverId); return null; } if (pluginSource.Source == PluginSource.Custom) { - _logger.LogInformation($"[CUSTOM PLUGIN] Plugin {pluginName} is marked as custom for server {serverId} - skipping version check"); + _logger.LogInformation("[CUSTOM PLUGIN] Plugin {PluginName} is marked as custom for server {ServerId} - skipping version check", pluginName?.Replace("\r", "").Replace("\n", ""), serverId); return null; } - _logger.LogInformation($"[SOURCE CHECK] Plugin {pluginName} configured with source {pluginSource.Source} for server {serverId}"); + _logger.LogInformation("[SOURCE CHECK] Plugin {PluginName} configured with source {Source} for server {ServerId}", pluginName?.Replace("\r", "").Replace("\n", ""), pluginSource.Source, serverId); // Step 1: local cache var cached = await GetFromCacheAsync(pluginName); if (cached != null) { - _logger.LogInformation("[CACHE HIT] Plugin {PluginName} found in local cache", pluginName); + _logger.LogInformation("[CACHE HIT] Plugin {PluginName} found in local cache", pluginName?.Replace("\r", "").Replace("\n", "")); return new PluginVersionCheckResult { PluginName = pluginName, @@ -73,7 +73,7 @@ public PluginVersionCheckService( }; } - _logger.LogInformation($"[CACHE MISS] Plugin {pluginName} not in local cache, checking {pluginSource.Source} API..."); + _logger.LogInformation("[CACHE MISS] Plugin {PluginName} not in local cache, checking {Source} API...", pluginName?.Replace("\r", "").Replace("\n", ""), pluginSource.Source); // Step 2: configured source PluginVersionCheckResult? result = null; @@ -106,7 +106,7 @@ public PluginVersionCheckService( return result; } - _logger.LogWarning($"[NOT FOUND] Plugin {pluginName} not found on {pluginSource.Source}"); + _logger.LogWarning("[NOT FOUND] Plugin {PluginName} not found on {Source}", pluginName?.Replace("\r", "").Replace("\n", ""), pluginSource.Source); return new PluginVersionCheckResult { PluginName = pluginName, @@ -120,7 +120,7 @@ public PluginVersionCheckService( } catch (Exception ex) { - _logger.LogError(ex, $"Error checking version for plugin {pluginName}"); + _logger.LogError(ex, "Error checking version for plugin {PluginName}", pluginName?.Replace("\r", "").Replace("\n", "")); return new PluginVersionCheckResult { PluginName = pluginName, @@ -153,7 +153,7 @@ public PluginVersionCheckService( .Where(sps => sps.RustServerId == serverId && pluginNames.Contains(sps.PluginName)) .ToDictionaryAsync(sps => sps.PluginName, sps => sps.Source); - // Plugins without a source / Custom plugins → null result + // Plugins without a source / Custom plugins → null result foreach (var plugin in plugins) { if (!pluginSources.TryGetValue(plugin.PluginName, out var source) || source == PluginSource.Custom) @@ -234,7 +234,7 @@ private async Task SaveToCacheAsync(PluginVersionCheckResult result, int? umodRa existing.ExpiresAt = expiresAt; existing.UmodRateLimitRemaining = umodRateLimitRemaining; existing.UmodRateLimitTotal = umodRateLimitTotal; - _logger.LogInformation($"[CACHE UPDATE] {result.PluginName} (expires {expiresAt:O})"); + _logger.LogInformation("[CACHE UPDATE] {PluginName} (expires {ExpiresAt:O})", result.PluginName?.Replace("\r", "").Replace("\n", ""), expiresAt); } else { @@ -249,14 +249,14 @@ private async Task SaveToCacheAsync(PluginVersionCheckResult result, int? umodRa UmodRateLimitRemaining = umodRateLimitRemaining, UmodRateLimitTotal = umodRateLimitTotal }); - _logger.LogInformation($"[CACHE SAVE] {result.PluginName} (expires {expiresAt:O})"); + _logger.LogInformation("[CACHE SAVE] {PluginName} (expires {ExpiresAt:O})", result.PluginName?.Replace("\r", "").Replace("\n", ""), expiresAt); } await _dbContext.SaveChangesAsync(); } catch (Exception ex) { - _logger.LogError(ex, $"Error saving plugin {result.PluginName} to cache"); + _logger.LogError(ex, "Error saving plugin {PluginName} to cache", result.PluginName?.Replace("\r", "").Replace("\n", "")); } } @@ -265,14 +265,14 @@ private async Task SaveToCacheAsync(PluginVersionCheckResult result, int? umodRa try { var url = $"https://www.codefling.com/db/?category=all&filename={Uri.EscapeDataString(fileName)}"; - _logger.LogInformation($"[CODEFLING] Checking filename '{fileName}' -> URL: {url}"); + _logger.LogInformation("[CODEFLING] Checking filename '{FileName}' -> URL: {Url}", fileName?.Replace("\r", "").Replace("\n", ""), url?.Replace("\r", "").Replace("\n", "")); var response = await _httpClient.GetAsync(url); - _logger.LogInformation($"[CODEFLING] Response for '{fileName}': StatusCode={response.StatusCode}"); + _logger.LogInformation("[CODEFLING] Response for '{FileName}': StatusCode={StatusCode}", fileName?.Replace("\r", "").Replace("\n", ""), response.StatusCode); if (!response.IsSuccessStatusCode) { - _logger.LogWarning($"[CODEFLING] Failed to fetch '{fileName}': {response.StatusCode}"); + _logger.LogWarning("[CODEFLING] Failed to fetch '{FileName}': {StatusCode}", fileName?.Replace("\r", "").Replace("\n", ""), response.StatusCode); return null; } @@ -281,15 +281,15 @@ private async Task SaveToCacheAsync(PluginVersionCheckResult result, int? umodRa var plugin = plugins?.FirstOrDefault(); if (plugin != null) - _logger.LogInformation($"[CODEFLING] Found plugin '{plugin.Title}' v{plugin.Version}"); + _logger.LogInformation("[CODEFLING] Found plugin '{Title}' v{Version}", plugin.Title?.Replace("\r", "").Replace("\n", ""), plugin.Version?.Replace("\r", "").Replace("\n", "")); else - _logger.LogInformation($"[CODEFLING] No plugin found for '{fileName}'"); + _logger.LogInformation("[CODEFLING] No plugin found for '{FileName}'", fileName?.Replace("\r", "").Replace("\n", "")); return plugin; } catch (Exception ex) { - _logger.LogWarning(ex, $"[CODEFLING] Error checking {fileName}"); + _logger.LogWarning(ex, "[CODEFLING] Error checking {FileName}", fileName?.Replace("\r", "").Replace("\n", "")); return null; } } @@ -322,9 +322,9 @@ private async Task SaveToCacheAsync(PluginVersionCheckResult result, int? umodRa if (rateLimitRemaining.HasValue && rateLimitTotal.HasValue) { - _logger.LogInformation($"[UMOD RATE LIMIT] {rateLimitRemaining}/{rateLimitTotal} requests remaining"); + _logger.LogInformation("[UMOD RATE LIMIT] {RateLimitRemaining}/{RateLimitTotal} requests remaining", rateLimitRemaining, rateLimitTotal); if (rateLimitRemaining.Value < 5) - _logger.LogWarning($"⚠️ [UMOD RATE LIMIT] Only {rateLimitRemaining} requests remaining!"); + _logger.LogWarning("⚠️ [UMOD RATE LIMIT] Only {RateLimitRemaining} requests remaining!", rateLimitRemaining); } var content = await response.Content.ReadAsStringAsync(); @@ -343,7 +343,7 @@ private async Task SaveToCacheAsync(PluginVersionCheckResult result, int? umodRa } catch (Exception ex) { - _logger.LogWarning(ex, $"Error checking Umod for {pluginName}"); + _logger.LogWarning(ex, "Error checking Umod for {PluginName}", pluginName?.Replace("\r", "").Replace("\n", "")); return null; } } diff --git a/RustRconServerManager.Backend/Services/RconBackgroundService.Connections.cs b/RustRconServerManager.Backend/Services/RconBackgroundService.Connections.cs index 947a70c..c824c4c 100644 --- a/RustRconServerManager.Backend/Services/RconBackgroundService.Connections.cs +++ b/RustRconServerManager.Backend/Services/RconBackgroundService.Connections.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.SignalR; using RustRconServerManager.Backend.Database; using RustRconServerManager.Backend.SignalRHubs; @@ -32,13 +32,13 @@ private async Task StartConnectionWithServer(RconServer server) client.OnMessageReceived += async (sender, args) => { - _logger.LogInformation("[Server {0}] Message: {1}", args.ServerId, args.Message); + _logger.LogInformation("[Server {ServerId}] Message: {Message}", args.ServerId, args.Message?.Replace("\r", "").Replace("\n", "")); await ServerMessageReceived(args.ServerId, args.Message); }; client.OnCommandAnswerReceived += async (sender, args) => { - _logger.LogDebug("[Server {0}] Answer: {1}", args.ServerId, args.Message); + _logger.LogDebug("[Server {ServerId}] Answer: {Message}", args.ServerId, args.Message?.Replace("\r", "").Replace("\n", "")); await ServerCommandAnswerReceived(args.ServerId, args.Message, args.Command, args.Purpose); }; @@ -50,19 +50,19 @@ private async Task StartConnectionWithServer(RconServer server) client.OnChatMessageReceived += async (sender, args) => { - _logger.LogWarning("Global chat received: " + args.ChatMessage); + _logger.LogWarning("Global chat received: {ChatMessage}", args.ChatMessage?.Replace("\r", "").Replace("\n", "")); await OnChatReceived(args.ServerId, args.ChatMessage, args.PlayerId, args.PlayerName, args.Channel.ToString()); }; client.OnPlayerKill += async (sender, args) => { - _logger.LogWarning("Player killed: " + args.KillerName + " killed " + args.VictimName); + _logger.LogWarning("Player killed: {KillerName} killed {VictimName}", args.KillerName?.Replace("\r", "").Replace("\n", ""), args.VictimName?.Replace("\r", "").Replace("\n", "")); await PlayerKilled(args.ServerId, args.KillerName, args.KillerId, args.VictimName, args.VictimId, args.Position); }; client.OnPlayerConnected += async (sender, args) => { - _logger.LogWarning("Player connected: " + args.PlayerName); + _logger.LogWarning("Player connected: {PlayerName}", args.PlayerName?.Replace("\r", "").Replace("\n", "")); // Note: args.PlayerId contains the player NAME, args.PlayerName contains the SteamId await OnPlayerConnectedAsync(args.ServerId, args.PlayerName, args.PlayerId, args.PlayerEndpoint); }; @@ -70,14 +70,14 @@ private async Task StartConnectionWithServer(RconServer server) client.OnPlayerDisconnected += async (sender, args) => { _logger.LogInformation("Player disconnected: {PlayerName} ({PlayerId}) - Reason: {Reason}", - args.PlayerName, args.PlayerId, args.Reason); + args.PlayerName?.Replace("\r", "").Replace("\n", ""), args.PlayerId, args.Reason?.Replace("\r", "").Replace("\n", "")); await OnPlayerDisconnectedAsync(args.ServerId, args.PlayerId, args.PlayerName, args.Reason); }; client.OnPlayerReported += async (sender, args) => { _logger.LogInformation("Player reported: {Reporter} reported {Reported} for {Type}", - args.ReporterName, args.ReportedName, args.Type); + args.ReporterName?.Replace("\r", "").Replace("\n", ""), args.ReportedName?.Replace("\r", "").Replace("\n", ""), args.Type?.Replace("\r", "").Replace("\n", "")); await OnPlayerReportedAsync(args.ServerId, args.ReporterName, args.ReporterId, args.ReportedName, args.ReportedId, args.Subject, args.Message, args.Type); }; @@ -123,7 +123,7 @@ private async Task ServerMessageReceived(int serverId, string message) using var scope = _scopeFactory.CreateScope(); AppDbContext dbContext = scope.ServiceProvider.GetRequiredService(); - _logger.LogInformation("[RconBackgroundService] Message from Server {ServerId}: {Message}", serverId, message); + _logger.LogInformation("[RconBackgroundService] Message from Server {ServerId}: {Message}", serverId, message?.Replace("\r", "").Replace("\n", "")); RconLogEntry logEntry = new RconLogEntry(); logEntry.CreatedAt = DateTime.UtcNow;