diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 8a20292..837ab0a 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -1,7 +1,7 @@ name: Build and run unit tests env: - DOTNET_VERSION: '9.x' + DOTNET_VERSION: '10.x' on: push: @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up .NET Core uses: actions/setup-dotnet@v5 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f92b85d..551b14c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: Release CI/CD env: AZURE_WEBAPP_NAME: app-rdmg AZURE_WEBAPP_PACKAGE_PATH: '.' - DOTNET_VERSION: '9.x' + DOTNET_VERSION: '10.x' on: push: @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up .NET Core uses: actions/setup-dotnet@v5 diff --git a/Directory.Build.props b/Directory.Build.props index 48051fb..c4d6973 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - net9.0 + net10.0 latest true enable diff --git a/Directory.Packages.props b/Directory.Packages.props index 27fb6cc..41179c3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,24 +7,23 @@ - - - - - - - - - + + + + + + + + + - - - + + - + diff --git a/README.md b/README.md index d7ba035..b8f3eb3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ I just wanted to see how is .NET under Linux so i ported my app to ASP.NET Core + Entity Framework Core. -You will need the .NET 9.0 SDK to build/run this project (simply run dotnet build/run in the terminal). +You will need the .NET 10.0 SDK to build/run this project (simply run dotnet build/run in the terminal). ## Usage diff --git a/src/RDMG.Core/Helpers/PasswordHelper.cs b/src/RDMG.Core/Helpers/PasswordHelper.cs index 266ac3c..240ca5a 100644 --- a/src/RDMG.Core/Helpers/PasswordHelper.cs +++ b/src/RDMG.Core/Helpers/PasswordHelper.cs @@ -4,16 +4,20 @@ namespace RDMG.Core.Helpers; public static class PasswordHelper { + private const int Iterations = 10000; + private const int HashLength = 20; + private const int SaltLength = 16; + private static readonly HashAlgorithmName HashAlgorithmName = HashAlgorithmName.SHA512; + public static string EncryptPassword(string password) { using var randomNumberGenerator = RandomNumberGenerator.Create(); - var salt = new byte[16]; + var salt = new byte[SaltLength]; randomNumberGenerator.GetBytes(salt); - using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA512); - var hash = pbkdf2.GetBytes(20); - var hashBytes = new byte[36]; - Array.Copy(salt, 0, hashBytes, 0, 16); - Array.Copy(hash, 0, hashBytes, 16, 20); + var hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, HashAlgorithmName, HashLength); + var hashBytes = new byte[SaltLength + HashLength]; + Array.Copy(salt, 0, hashBytes, 0, SaltLength); + Array.Copy(hash, 0, hashBytes, SaltLength, HashLength); return Convert.ToBase64String(hashBytes); } @@ -21,14 +25,13 @@ public static bool CheckPassword(string savedPasswordHash, string password) { var result = true; var hashBytes = Convert.FromBase64String(savedPasswordHash); - var salt = new byte[16]; - Array.Copy(hashBytes, 0, salt, 0, 16); - using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA512); - var hash = pbkdf2.GetBytes(20); + var salt = new byte[SaltLength]; + Array.Copy(hashBytes, 0, salt, 0, SaltLength); + var hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, HashAlgorithmName, HashLength); - for (var i = 0; i < 20; i++) + for (var i = 0; i < HashLength; i++) { - if (hashBytes[i + 16] == hash[i]) + if (hashBytes[i + SaltLength] == hash[i]) continue; result = false; break; diff --git a/src/RDMG.Core/RDMG.Core.csproj b/src/RDMG.Core/RDMG.Core.csproj index 3a097c4..abf136e 100644 --- a/src/RDMG.Core/RDMG.Core.csproj +++ b/src/RDMG.Core/RDMG.Core.csproj @@ -3,7 +3,6 @@ - diff --git a/src/RDMG.Web/Controllers/Web/DungeonController.cs b/src/RDMG.Web/Controllers/Web/DungeonController.cs index dc262f2..1bdb723 100644 --- a/src/RDMG.Web/Controllers/Web/DungeonController.cs +++ b/src/RDMG.Web/Controllers/Web/DungeonController.cs @@ -9,6 +9,7 @@ using System.Text.Json; using RDMG.Core.Abstractions.Services.Exceptions; using RDMG.Resources; +using RDMG.Web.Extensions; namespace RDMG.Web.Controllers.Web; @@ -68,7 +69,7 @@ public async Task Delete(int id, CancellationToken cancellationTo } catch (Exception ex) { - _logger.LogError(ex, "Error deleting dungeon."); + this.HandleException(ex, _logger, "Error deleting dungeon."); } return RedirectToAction("Index"); @@ -107,7 +108,7 @@ await _dungeonService.RenameDungeonAsync(model.Id, model.UserId, model.NewDungeo } catch (Exception ex) { - _logger.LogError(ex, "Error renaming dungeon."); + this.HandleException(ex, _logger, "Error renaming dungeon."); } return View(model); @@ -123,7 +124,7 @@ public async Task DeleteOption(int id, CancellationToken cancella } catch (Exception ex) { - _logger.LogError(ex, "Error deleting dungeon option."); + this.HandleException(ex, _logger, "Error deleting dungeon option."); } return RedirectToAction("Index"); @@ -166,7 +167,7 @@ public async Task Create(DungeonOptionCreateViewModel model, Canc } catch (Exception ex) { - _logger.LogError(ex, "Error creating dungeon."); + this.HandleException(ex, _logger, "Error creating dungeon."); } } diff --git a/src/RDMG.Web/Controllers/Web/ProfileController.cs b/src/RDMG.Web/Controllers/Web/ProfileController.cs index d8b1815..2087911 100644 --- a/src/RDMG.Web/Controllers/Web/ProfileController.cs +++ b/src/RDMG.Web/Controllers/Web/ProfileController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using RDMG.Core.Abstractions.Services; using RDMG.Core.Abstractions.Services.Models; +using RDMG.Web.Extensions; using RDMG.Web.Models.Profile; namespace RDMG.Web.Controllers.Web; @@ -44,8 +45,7 @@ public async Task ChangePassword(ProfileChangePasswordModel model } catch (Exception ex) { - _logger.LogError(ex, "Error changing password."); - ModelState.AddModelError("", ex.Message); + this.HandleException(ex, _logger, "Error changing password."); } } return View(model); diff --git a/src/RDMG.Web/Controllers/Web/UserController.cs b/src/RDMG.Web/Controllers/Web/UserController.cs index 54f0310..ed19d32 100644 --- a/src/RDMG.Web/Controllers/Web/UserController.cs +++ b/src/RDMG.Web/Controllers/Web/UserController.cs @@ -4,6 +4,7 @@ using RDMG.Core.Abstractions.Services; using RDMG.Core.Abstractions.Services.Models; using RDMG.Core.Domain; +using RDMG.Web.Extensions; using RDMG.Web.Models.User; namespace RDMG.Web.Controllers.Web; @@ -44,10 +45,10 @@ public async Task Create(UserCreateViewModel model) } catch (Exception ex) { - _logger.LogError(ex, "Error creating user."); - ModelState.AddModelError("", ex.Message); + this.HandleException(ex, _logger, "Error creating user."); } } + return View(model); } @@ -73,10 +74,10 @@ public async Task Edit(UserEditViewModel model) } catch (Exception ex) { - _logger.LogError(ex, "Error editing user."); - ModelState.AddModelError("", ex.Message); + this.HandleException(ex, _logger, "Error editing user."); } } + return View(model); } @@ -90,8 +91,9 @@ public async Task Delete(int id) } catch (Exception ex) { - _logger.LogError(ex, "Error deleting user."); + this.HandleException(ex, _logger, "Error deleting user."); } + return RedirectToAction("Index"); } @@ -105,8 +107,9 @@ public async Task Restore(int id) } catch (Exception ex) { - _logger.LogError(ex, "Error restoring user."); + this.HandleException(ex, _logger, "Error restoring user."); } + return RedirectToAction("Index"); } } \ No newline at end of file diff --git a/src/RDMG.Web/Dockerfile b/src/RDMG.Web/Dockerfile index 02f52c3..02d73db 100644 --- a/src/RDMG.Web/Dockerfile +++ b/src/RDMG.Web/Dockerfile @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /app COPY . ./ RUN dotnet restore ./src/RDMG.Web/RDMG.Web.csproj RUN dotnet publish "src/RDMG.Web/RDMG.Web.csproj" -c Release -o out -FROM mcr.microsoft.com/dotnet/aspnet:9.0 +FROM mcr.microsoft.com/dotnet/aspnet:10.0 WORKDIR /app COPY --from=build /app/out ./ diff --git a/src/RDMG.Web/Extensions/ControllerExtensions.cs b/src/RDMG.Web/Extensions/ControllerExtensions.cs new file mode 100644 index 0000000..2a449f1 --- /dev/null +++ b/src/RDMG.Web/Extensions/ControllerExtensions.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc; +using RDMG.Core.Abstractions.Services.Exceptions; + +namespace RDMG.Web.Extensions; + +public static class ControllerExtensions +{ + public static void HandleException(this Controller controller, Exception ex, ILogger logger, + string? defaultError = null) + { + switch (ex) + { + case ServiceAggregateException ae: + { + foreach (var exception in ae.GetInnerExceptions()) + { + controller.ModelState.AddModelError(string.Empty, exception.Message); + } + + break; + } + case ServiceException e: + controller.ModelState.AddModelError(string.Empty, e.Message); + break; + default: + logger.LogError(ex, "{GetDisplayUrl}: {DefaultError}", controller.Request.GetDisplayUrl(), + defaultError ?? ServiceException.GeneralError); + controller.ModelState.AddModelError(string.Empty, defaultError ?? ServiceException.GeneralError); + break; + } + } +} \ No newline at end of file