diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b421b34 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# top-most EditorConfig file +root = true + +# Core EditorConfig properties +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +csharp_prefer_braced_block = true:suggestion +csharp_preserve_single_line_blocks = true +csharp_style_expression_bodied_methods = when_on_single_line:suggestion diff --git a/MainProject.Tests/Controllers/AuthController_Tests.cs b/MainProject.Tests/Controllers/AuthController_Tests.cs index 2b58b55..971d602 100644 --- a/MainProject.Tests/Controllers/AuthController_Tests.cs +++ b/MainProject.Tests/Controllers/AuthController_Tests.cs @@ -61,12 +61,12 @@ public class AuthController_Tests ObjectResult response = (ObjectResult)(await controller.AuthenticateAsync(request)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, result.Status); Assert.IsInstanceOfType(result.Data, typeof(AuthenticatedUser)); } else @@ -81,42 +81,6 @@ public class AuthController_Tests } } - [TestMethod] - public async Task AuthenticateAsync_AuthenticateRequestDataNull() - { - IConfiguration configuration = TestUtils.CreateConfiguration(); - var authServiceMock = new Mock(); - var controller = new AuthController(configuration, authServiceMock.Object); - - var request = new AuthenticateRequest - { - Data = null - }; - AuthenticatedUser? authenticatedUser = null; - authServiceMock.Setup(s => s.AuthenticateAsync(It.IsAny())).ReturnsAsync(authenticatedUser); - ObjectResult response = (ObjectResult)(await controller.AuthenticateAsync(request)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response value is null"); - } - } - [TestMethod] public async Task AuthenticateAsync_NotFound() { @@ -140,46 +104,7 @@ public class AuthController_Tests if (response != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status404NotFound); - } - else - { - Assert.Fail($"Response is null"); - } - } - - [TestMethod] - public async Task AuthenticateAsync_ModelInvalid() - { - IConfiguration configuration = TestUtils.CreateConfiguration(); - var authServiceMock = new Mock(); - var controller = new AuthController(configuration, authServiceMock.Object); - - var request = new AuthenticateRequest - { - Data = null - }; - AuthenticatedUser? authenticatedUser = null; - authServiceMock.Setup(s => s.AuthenticateAsync(It.IsAny())).ReturnsAsync(authenticatedUser); - controller.ModelState.AddModelError("Data", "Invalid data"); - ObjectResult response = (ObjectResult)(await controller.AuthenticateAsync(request)); - - Assert.IsInstanceOfType(response, typeof(ObjectResult)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); } else { @@ -207,13 +132,13 @@ public class AuthController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status500InternalServerError); + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status500InternalServerError); - Assert.IsTrue(result.Message == "Something went wrong. Unexpected error"); + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); } else { diff --git a/MainProject.Tests/Controllers/RoleController_Tests.cs b/MainProject.Tests/Controllers/RoleController_Tests.cs index 5fcd784..417d7ac 100644 --- a/MainProject.Tests/Controllers/RoleController_Tests.cs +++ b/MainProject.Tests/Controllers/RoleController_Tests.cs @@ -70,12 +70,12 @@ public class RoleController_Tests ObjectResult response = (ObjectResult)(await _roleController.GetRoleByGuidAsync(guid)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, result.Status); Assert.IsInstanceOfType(result.Data, typeof(RoleDto)); } else @@ -89,41 +89,6 @@ public class RoleController_Tests } } - [TestMethod] - public async Task GetRoleByGuidAsync_GuidIsEmpty() - { - if (_roleController == null) - { - Assert.Fail($"_roleController is null"); - } - - var guid = String.Empty; - DatabaseSqlServer.Role? role = null; - - _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); - ObjectResult response = (ObjectResult)(await _roleController.GetRoleByGuidAsync(guid)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response value is null"); - } - } - [TestMethod] public async Task GetRoleByGuidAsync_NotFound() { @@ -141,44 +106,7 @@ public class RoleController_Tests if (response != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status404NotFound); - } - else - { - Assert.Fail($"Response is null"); - } - } - - [TestMethod] - public async Task GetRoleByGuidAsync_ModelInvalid() - { - if (_roleController == null) - { - Assert.Fail($"_roleController is null"); - } - - var guid = Guid.NewGuid().ToString(); - DatabaseSqlServer.Role? role = null; - _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); - _roleController.ModelState.AddModelError("Data", "Invalid data"); - ObjectResult response = (ObjectResult)(await _roleController.GetRoleByGuidAsync(guid)); - - Assert.IsInstanceOfType(response, typeof(ObjectResult)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); } else { @@ -202,13 +130,13 @@ public class RoleController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status500InternalServerError); + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status500InternalServerError); - Assert.IsTrue(result.Message == "Something went wrong. Unexpected error"); + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); } else { @@ -255,12 +183,12 @@ public class RoleController_Tests ObjectResult response = (ObjectResult)(await _roleController.CreateRoleAsync(request)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, result.Status); Assert.IsInstanceOfType(result.Data, typeof(RoleDto)); } else @@ -297,57 +225,13 @@ public class RoleController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); + Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Invalid name"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response value is null"); - } - } - - [TestMethod] - public async Task CreateRoleAsync_CreateRoleRequestDataNull() - { - if (_roleController == null) - { - Assert.Fail($"_roleController is null"); - } - - DatabaseSqlServer.Role role = ModelsInit.CreateRole(); - - CreateRoleRequest request = new CreateRoleRequest() - { - Data = null - }; - - _roleServiceMock?.Setup(s => s.CheckIfNameIsValid(It.IsAny(), It.IsAny())).ReturnsAsync(true); - - _roleServiceMock?.Setup(s => s.CreateRoleAsync( - It.IsAny() - )).ReturnsAsync(role); - - ObjectResult response = (ObjectResult)(await _roleController.CreateRoleAsync(request)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); + Assert.AreEqual(StatusCodes.Status400BadRequest, result.Status); + Assert.AreEqual("Invalid name", result.Message); } else { @@ -391,13 +275,13 @@ public class RoleController_Tests ObjectResult response = (ObjectResult)(await _roleController.CreateRoleAsync(request)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); + Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Not created"); + Assert.AreEqual(StatusCodes.Status400BadRequest, result.Status); + Assert.AreEqual("Not created", result.Message); } else { @@ -410,56 +294,6 @@ public class RoleController_Tests } } - [TestMethod] - public async Task CreateRoleAsync_ModelInvalid() - { - if (_roleController == null) - { - Assert.Fail($"_roleController is null"); - } - - DatabaseSqlServer.Role role = ModelsInit.CreateRole(); - - CreateRoleRequest request = new CreateRoleRequest() - { - Data = new CreateRoleRequestData() - { - Name = "RoleTest", - IsNotEditable = true - } - }; - - _roleServiceMock?.Setup(s => s.CheckIfNameIsValid(It.IsAny(), It.IsAny())).ReturnsAsync(true); - - _roleServiceMock?.Setup(s => s.CreateRoleAsync( - It.IsAny() - )).ReturnsAsync(role); - _roleController.ModelState.AddModelError("Data", "Invalid data"); - ObjectResult response = (ObjectResult)(await _roleController.CreateRoleAsync(request)); - - Assert.IsInstanceOfType(response, typeof(ObjectResult)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response is null"); - } - } - [TestMethod] public async Task CreateRoleAsync_Exception() { @@ -494,13 +328,13 @@ public class RoleController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status500InternalServerError); + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status500InternalServerError); - Assert.IsTrue(result.Message == "Something went wrong. Unexpected error"); + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); } else { @@ -531,42 +365,7 @@ public class RoleController_Tests ObjectResult response = (ObjectResult)(await _roleController.DeleteRoleByGuidAsync(guid)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status200OK); - } - else - { - Assert.Fail($"Response value is null"); - } - } - - [TestMethod] - public async Task DeleteRoleByGuidAsync_GuidIsEmpty() - { - if (_roleController == null) - { - Assert.Fail($"_roleController is null"); - } - - var guid = String.Empty; - DatabaseSqlServer.Role? role = null; - - _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); - ObjectResult response = (ObjectResult)(await _roleController.DeleteRoleByGuidAsync(guid)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); } else { @@ -591,44 +390,7 @@ public class RoleController_Tests if (response != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status404NotFound); - } - else - { - Assert.Fail($"Response is null"); - } - } - - [TestMethod] - public async Task DeleteRoleByGuidAsync_ModelInvalid() - { - if (_roleController == null) - { - Assert.Fail($"_roleController is null"); - } - - var guid = Guid.NewGuid().ToString(); - DatabaseSqlServer.Role? role = null; - _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); - _roleController.ModelState.AddModelError("Data", "Invalid data"); - ObjectResult response = (ObjectResult)(await _roleController.DeleteRoleByGuidAsync(guid)); - - Assert.IsInstanceOfType(response, typeof(ObjectResult)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); } else { @@ -652,13 +414,13 @@ public class RoleController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status500InternalServerError); + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status500InternalServerError); - Assert.IsTrue(result.Message == "Something went wrong. Unexpected error"); + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); } else { @@ -673,8 +435,6 @@ public class RoleController_Tests #endregion - - #region "UPDATE" [TestMethod] @@ -708,12 +468,12 @@ public class RoleController_Tests ObjectResult response = (ObjectResult)(await _roleController.UpdateRoleAsync(request, role.Guid)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, result.Status); Assert.IsInstanceOfType(result.Data, typeof(RoleDto)); } else @@ -752,7 +512,7 @@ public class RoleController_Tests if (response != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status404NotFound); + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); } else { @@ -786,13 +546,13 @@ public class RoleController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); + Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Invalid name"); + Assert.AreEqual(StatusCodes.Status400BadRequest, result.Status); + Assert.AreEqual("Invalid name", result.Message); } else { @@ -832,13 +592,13 @@ public class RoleController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); + Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "This role is not editable"); + Assert.AreEqual(StatusCodes.Status400BadRequest, result.Status); + Assert.AreEqual("This role is not editable", result.Message); } else { @@ -851,96 +611,6 @@ public class RoleController_Tests } } - [TestMethod] - public async Task UpdateRoleAsync_CreateRoleRequestDataNull() - { - if (_roleController == null) - { - Assert.Fail($"_roleController is null"); - } - - DatabaseSqlServer.Role role = ModelsInit.CreateRole(); - - CreateRoleRequest request = new CreateRoleRequest() - { - Data = null - }; - - _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); - _roleServiceMock?.Setup(s => s.CheckIfNameIsValid(It.IsAny(), It.IsAny())).ReturnsAsync(true); - _roleServiceMock?.Setup(s => s.UpdateRoleAsync(It.IsAny(), It.IsAny())).ReturnsAsync(role); - - ObjectResult response = (ObjectResult)(await _roleController.UpdateRoleAsync(request, role.Guid)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response value is null"); - } - } - - [TestMethod] - public async Task UpdateRoleAsync_ModelInvalid() - { - if (_roleController == null) - { - Assert.Fail($"_roleController is null"); - } - - DatabaseSqlServer.Role role = ModelsInit.CreateRole(); - - CreateRoleRequest request = new CreateRoleRequest() - { - Data = new CreateRoleRequestData() - { - Name = "RoleTest", - IsNotEditable = true - } - }; - - _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); - _roleServiceMock?.Setup(s => s.CheckIfNameIsValid(It.IsAny(), It.IsAny())).ReturnsAsync(true); - _roleServiceMock?.Setup(s => s.UpdateRoleAsync(It.IsAny(), It.IsAny())).ReturnsAsync(role); - _roleController.ModelState.AddModelError("Data", "Invalid data"); - ObjectResult response = (ObjectResult)(await _roleController.UpdateRoleAsync(request, role.Guid)); - - Assert.IsInstanceOfType(response, typeof(ObjectResult)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response is null"); - } - } - [TestMethod] public async Task UpdateRoleAsync_Exception() { @@ -971,18 +641,18 @@ public class RoleController_Tests It.IsAny(), It.IsAny() )).ThrowsAsync(new Exception("Unexpected error")); - ObjectResult response = (ObjectResult)(await _roleController.UpdateRoleAsync(request, role.Guid)); + ObjectResult response = (ObjectResult)await _roleController.UpdateRoleAsync(request, role.Guid); Assert.IsInstanceOfType(response, typeof(ObjectResult)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status500InternalServerError); + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status500InternalServerError); - Assert.IsTrue(result.Message == "Something went wrong. Unexpected error"); + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); } else { @@ -996,4 +666,5 @@ public class RoleController_Tests } #endregion + } diff --git a/MainProject.Tests/Controllers/UserController_Tests.cs b/MainProject.Tests/Controllers/UserController_Tests.cs index cc94f2b..44dbd35 100644 --- a/MainProject.Tests/Controllers/UserController_Tests.cs +++ b/MainProject.Tests/Controllers/UserController_Tests.cs @@ -66,6 +66,7 @@ public class UserController_Tests } +#region "GET" [TestMethod] public async Task GetUserByGuidAsync_Should_Return_200_When_Successful() { @@ -80,12 +81,12 @@ public class UserController_Tests ObjectResult response = (ObjectResult)(await _userController.GetUserByGuidAsync(guid)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, result.Status); Assert.IsInstanceOfType(result.Data, typeof(UserDto)); } else @@ -99,41 +100,6 @@ public class UserController_Tests } } - [TestMethod] - public async Task GetUserByGuidAsync_GuidIsEmpty() - { - if (_userController == null) - { - Assert.Fail($"_userController is null"); - } - - var guid = String.Empty; - DatabaseSqlServer.User? user = null; - - _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); - ObjectResult response = (ObjectResult)(await _userController.GetUserByGuidAsync(guid)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response value is null"); - } - } - [TestMethod] public async Task GetUserByGuidAsync_NotFound() { @@ -151,44 +117,7 @@ public class UserController_Tests if (response != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status404NotFound); - } - else - { - Assert.Fail($"Response is null"); - } - } - - [TestMethod] - public async Task GetUserByGuidAsync_ModelInvalid() - { - if (_userController == null) - { - Assert.Fail($"_userController is null"); - } - - var guid = Guid.NewGuid().ToString(); - DatabaseSqlServer.User? user = null; - _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); - _userController.ModelState.AddModelError("Data", "Invalid data"); - ObjectResult response = (ObjectResult)(await _userController.GetUserByGuidAsync(guid)); - - Assert.IsInstanceOfType(response, typeof(ObjectResult)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); } else { @@ -212,13 +141,13 @@ public class UserController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status500InternalServerError); + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status500InternalServerError); - Assert.IsTrue(result.Message == "Something went wrong. Unexpected error"); + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); } else { @@ -231,6 +160,10 @@ public class UserController_Tests } } +#endregion + + +#region "CREATE" [TestMethod] public async Task CreateUserAsync_Success() { @@ -265,12 +198,12 @@ public class UserController_Tests ObjectResult response = (ObjectResult)(await _userController.CreateUserAsync(request)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status200OK); + Assert.AreEqual(StatusCodes.Status200OK, result.Status); Assert.IsInstanceOfType(result.Data, typeof(UserDto)); } else @@ -311,13 +244,13 @@ public class UserController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); + Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Invalid email"); + Assert.AreEqual(StatusCodes.Status400BadRequest, result.Status); + Assert.AreEqual("Invalid email", result.Message); } else { @@ -362,58 +295,13 @@ public class UserController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); + Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Role not found"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response value is null"); - } - } - - [TestMethod] - public async Task CreateUserAsync_CreateUserRequestDataNull() - { - if (_userController == null) - { - Assert.Fail($"_userController is null"); - } - - DatabaseSqlServer.User user = ModelsInit.CreateUser(); - - CreateUserRequest request = new CreateUserRequest() - { - Data = null - }; - - _userServiceMock?.Setup(s => s.CheckIfEmailIsValid(It.IsAny(), It.IsAny())).ReturnsAsync(true); - - _userServiceMock?.Setup(s => s.CreateUserAsync( - It.IsAny(), - It.IsAny() - )).ReturnsAsync(user); - - ObjectResult response = (ObjectResult)(await _userController.CreateUserAsync(request)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); + Assert.AreEqual(StatusCodes.Status400BadRequest, result.Status); + Assert.AreEqual("Role not found", result.Message); } else { @@ -462,13 +350,13 @@ public class UserController_Tests ObjectResult response = (ObjectResult)(await _userController.CreateUserAsync(request)); if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); + Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Not created"); + Assert.AreEqual(StatusCodes.Status400BadRequest, result.Status); + Assert.AreEqual("Not created", result.Message); } else { @@ -481,59 +369,6 @@ public class UserController_Tests } } - [TestMethod] - public async Task CreateUserAsync_ModelInvalid() - { - if (_userController == null) - { - Assert.Fail($"_userController is null"); - } - - DatabaseSqlServer.User user = ModelsInit.CreateUser(); - - CreateUserRequest request = new CreateUserRequest() - { - Data = new CreateUserRequestData() - { - FirstName = user.FirstName, - LastName = user.LastName, - Email = user.Email, - Password = user.Password - } - }; - - _userServiceMock?.Setup(s => s.CheckIfEmailIsValid(It.IsAny(), It.IsAny())).ReturnsAsync(true); - - _userServiceMock?.Setup(s => s.CreateUserAsync( - It.IsAny(), - It.IsAny() - )).ReturnsAsync(user); - _userController.ModelState.AddModelError("Data", "Invalid data"); - ObjectResult response = (ObjectResult)(await _userController.CreateUserAsync(request)); - - Assert.IsInstanceOfType(response, typeof(ObjectResult)); - - if (response != null && response.Value != null) - { - Assert.IsTrue(response.StatusCode == StatusCodes.Status400BadRequest); - - var result = (BaseResponse)response.Value; - if (result != null) - { - Assert.IsTrue(result.Status == StatusCodes.Status400BadRequest); - Assert.IsTrue(result.Message == "Request is not well formed"); - } - else - { - Assert.Fail($"Result value is null"); - } - } - else - { - Assert.Fail($"Response is null"); - } - } - [TestMethod] public async Task CreateUserAsync_Exception() { @@ -576,13 +411,13 @@ public class UserController_Tests if (response != null && response.Value != null) { - Assert.IsTrue(response.StatusCode == StatusCodes.Status500InternalServerError); + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); var result = (BaseResponse)response.Value; if (result != null) { - Assert.IsTrue(result.Status == StatusCodes.Status500InternalServerError); - Assert.IsTrue(result.Message == "Something went wrong. Unexpected error"); + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); } else { @@ -594,4 +429,479 @@ public class UserController_Tests Assert.Fail($"Response is null"); } } + +#endregion + +#region "DELETE" + + [TestMethod] + public async Task DeleteRoleByGuidAsync_Success() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + var guid = Guid.NewGuid().ToString(); + DatabaseSqlServer.User user = ModelsInit.CreateUser(); + + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + ObjectResult response = (ObjectResult)await _userController.DeleteUserByGuidAsync(guid); + if (response != null && response.Value != null) + { + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); + } + else + { + Assert.Fail($"Response value is null"); + } + } + + [TestMethod] + public async Task DeleteRoleByGuidAsync_NotFound() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + var guid = Guid.NewGuid().ToString(); + DatabaseSqlServer.User? user = null; + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + NotFoundResult response = (NotFoundResult)await _userController.DeleteUserByGuidAsync(guid); + + Assert.IsInstanceOfType(response, typeof(NotFoundResult)); + + if (response != null) + { + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); + } + else + { + Assert.Fail($"Response is null"); + } + } + + [TestMethod] + public async Task DeleteRoleByGuidAsync_Exception() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + var guid = Guid.NewGuid().ToString(); + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ThrowsAsync(new Exception("Unexpected error")); + ObjectResult response = (ObjectResult)await _userController.DeleteUserByGuidAsync(guid); + + Assert.IsInstanceOfType(response, typeof(ObjectResult)); + + if (response != null && response.Value != null) + { + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); + + var result = (BaseResponse)response.Value; + if (result != null) + { + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); + } + else + { + Assert.Fail($"Result value is null"); + } + } + else + { + Assert.Fail($"Response is null"); + } + } + + #endregion + + #region "UPDATE" + + [TestMethod] + public async Task UpdateUserAsync_Should_Return_200_When_Successful() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User user = ModelsInit.CreateUser(); + + UpdateUserRequest request = new UpdateUserRequest() + { + Data = new UpdateUserRequestData() + { + FirstName = "NewFirstName", + LastName = "NewLastName" + } + }; + + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + _userServiceMock?.Setup(s => s.UpdateUserAsync(It.IsAny(), It.IsAny())).ReturnsAsync(user); + + ObjectResult response = (ObjectResult)await _userController.UpdateUserAsync(request, user.Guid); + if (response != null && response.Value != null) + { + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); + + var result = (BaseResponse)response.Value; + if (result != null) + { + Assert.AreEqual(StatusCodes.Status200OK, result.Status); + Assert.IsInstanceOfType(result.Data, typeof(UserDto)); + } + else + { + Assert.Fail($"Result value is null"); + } + } + else + { + Assert.Fail($"Response value is null"); + } + } + + [TestMethod] + public async Task UpdateUserAsync_UserNotFound() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User? user = null; + + UpdateUserRequest request = new UpdateUserRequest() + { + Data = new UpdateUserRequestData() + { + FirstName = "NewFirstName", + LastName = "NewLastName" + } + }; + + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + + NotFoundResult response = (NotFoundResult)await _userController.UpdateUserAsync(request, Guid.NewGuid().ToString()); + + if (response != null) + { + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); + } + else + { + Assert.Fail($"Response is null"); + } + } + + [TestMethod] + public async Task UpdateUserAsync_Exception() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User user = ModelsInit.CreateUser(); + + UpdateUserRequest request = new UpdateUserRequest() + { + Data = new UpdateUserRequestData() + { + FirstName = "NewFirstName", + LastName = "NewLastName" + } + }; + + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + _userServiceMock?.Setup(s => s.UpdateUserAsync( + It.IsAny(), It.IsAny() + )).ThrowsAsync(new Exception("Unexpected error")); + + ObjectResult response = (ObjectResult)await _userController.UpdateUserAsync(request, user.Guid); + Assert.IsInstanceOfType(response, typeof(ObjectResult)); + + if (response != null && response.Value != null) + { + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); + + var result = (BaseResponse)response.Value; + if (result != null) + { + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); + } + else + { + Assert.Fail($"Result value is null"); + } + } + else + { + Assert.Fail($"Response is null"); + } + } + + + + + [TestMethod] + public async Task UpdateUserPasswordAsync_Should_Return_200_When_Successful() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User user = ModelsInit.CreateUser(); + + string newPassword = "This!s4T3stP4ssw0rd#"; + + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + _userServiceMock?.Setup(s => s.UpdateUserPasswordAsync(It.IsAny(), newPassword)).ReturnsAsync(user); + + ObjectResult response = (ObjectResult)await _userController.UpdateUserPasswordAsync(user.Guid, newPassword); + if (response != null && response.Value != null) + { + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); + + var result = (BaseResponse)response.Value; + if (result != null) + { + Assert.AreEqual(StatusCodes.Status200OK, result.Status); + Assert.IsInstanceOfType(result.Data, typeof(UserDto)); + } + else + { + Assert.Fail($"Result value is null"); + } + } + else + { + Assert.Fail($"Response value is null"); + } + } + + [TestMethod] + public async Task UpdateUserPasswordAsync_UserNotFound() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User? user = null; + + string newPassword = "This!s4T3stP4ssw0rd#"; + + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + + NotFoundResult response = (NotFoundResult)await _userController.UpdateUserPasswordAsync(Guid.NewGuid().ToString(), newPassword); + + if (response != null) + { + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); + } + else + { + Assert.Fail($"Response is null"); + } + } + + [TestMethod] + public async Task UpdateUserPasswordAsync_Exception() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User user = ModelsInit.CreateUser(); + + string newPassword = "This!s4T3stP4ssw0rd#"; + + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + _userServiceMock?.Setup(s => s.UpdateUserPasswordAsync( + It.IsAny(), It.IsAny() + )).ThrowsAsync(new Exception("Unexpected error")); + + ObjectResult response = (ObjectResult)await _userController.UpdateUserPasswordAsync(user.Guid, newPassword); + Assert.IsInstanceOfType(response, typeof(ObjectResult)); + + if (response != null && response.Value != null) + { + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); + + var result = (BaseResponse)response.Value; + if (result != null) + { + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); + } + else + { + Assert.Fail($"Result value is null"); + } + } + else + { + Assert.Fail($"Response is null"); + } + } + + + + [TestMethod] + public async Task UpdateUserRoleAsync_Should_Return_200_When_Successful() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + if(_roleServiceMock == null) + { + Assert.Fail($"_roleServiceMock is null"); + } + + DatabaseSqlServer.User user = ModelsInit.CreateUser(); + DatabaseSqlServer.Role role = ModelsInit.CreateRole(); + + _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + _userServiceMock?.Setup(s => s.UpdateUserRoleAsync(It.IsAny(), It.IsAny())).ReturnsAsync(user); + + ObjectResult response = (ObjectResult)await _userController.UpdateUserRoleAsync(user.Guid, role.Guid); + + if (response != null && response.Value != null) + { + Assert.AreEqual(StatusCodes.Status200OK, response.StatusCode); + + var result = (BaseResponse)response.Value; + if (result != null) + { + Assert.AreEqual(StatusCodes.Status200OK, result.Status); + Assert.IsInstanceOfType(result.Data, typeof(UserDto)); + } + else + { + Assert.Fail($"Result value is null"); + } + } + else + { + Assert.Fail($"Response value is null"); + } + } + + [TestMethod] + public async Task UpdateUserPasswordAsync_RoleNotFound() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User user = ModelsInit.CreateUser(); + DatabaseSqlServer.Role? role = null; + + _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + ObjectResult response = (ObjectResult)await _userController.UpdateUserRoleAsync(user.Guid, Guid.NewGuid().ToString()); + if (response != null) + { + Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode); + Assert.IsNotNull(response.Value); + var result = (BaseResponse)response.Value; + if (result != null) + { + Assert.AreEqual(StatusCodes.Status400BadRequest, result.Status); + Assert.AreEqual("Role not found", result.Message); + } + else + { + Assert.Fail($"Result value is null"); + } + } + else + { + Assert.Fail($"Response is null"); + } + } + + [TestMethod] + public async Task UpdateUserRoleAsync_UserNotFound() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User? user = null; + DatabaseSqlServer.Role role = ModelsInit.CreateRole(); + + _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + _userServiceMock?.Setup(s => s.UpdateUserRoleAsync(It.IsAny(), It.IsAny())).ReturnsAsync(user); + + NotFoundResult response = (NotFoundResult)await _userController.UpdateUserRoleAsync(Guid.NewGuid().ToString(), role.Guid); + + if (response != null) + { + Assert.AreEqual(StatusCodes.Status404NotFound, response.StatusCode); + } + else + { + Assert.Fail($"Response is null"); + } + } + + [TestMethod] + public async Task UpdateUserRoleAsync_Exception() + { + if (_userController == null) + { + Assert.Fail($"_userController is null"); + } + + DatabaseSqlServer.User user = ModelsInit.CreateUser(); + DatabaseSqlServer.Role role = ModelsInit.CreateRole(); + + _roleServiceMock?.Setup(s => s.GetRoleByGuidAsync(It.IsAny())).ReturnsAsync(role); + _userServiceMock?.Setup(s => s.GetUserByGuidAsync(It.IsAny())).ReturnsAsync(user); + _userServiceMock?.Setup(s => s.UpdateUserRoleAsync( + It.IsAny(), It.IsAny() + )).ThrowsAsync(new Exception("Unexpected error")); + + ObjectResult response = (ObjectResult)await _userController.UpdateUserRoleAsync(user.Guid, role.Guid); + Assert.IsInstanceOfType(response, typeof(ObjectResult)); + + if (response != null && response.Value != null) + { + Assert.AreEqual(StatusCodes.Status500InternalServerError, response.StatusCode); + + var result = (BaseResponse)response.Value; + if (result != null) + { + Assert.AreEqual(StatusCodes.Status500InternalServerError, result.Status); + Assert.AreEqual("Something went wrong. Unexpected error", result.Message); + } + else + { + Assert.Fail($"Result value is null"); + } + } + else + { + Assert.Fail($"Response is null"); + } + } + + + #endregion + + } diff --git a/MainProject.Tests/Core/Attributes/JwtAuthorizationAttribute_Tests.cs b/MainProject.Tests/Core/Attributes/JwtAuthorizationAttribute_Tests.cs index 1523008..5e1c3fe 100644 --- a/MainProject.Tests/Core/Attributes/JwtAuthorizationAttribute_Tests.cs +++ b/MainProject.Tests/Core/Attributes/JwtAuthorizationAttribute_Tests.cs @@ -143,4 +143,4 @@ public class JwtAuthorizationAttribute_Tests Assert.IsNotInstanceOfType(context.Result, typeof(UnauthorizedResult)); } -} \ No newline at end of file +} diff --git a/MainProject.Tests/Core/Filters/ValidationActionFilter_Tests.cs b/MainProject.Tests/Core/Filters/ValidationActionFilter_Tests.cs new file mode 100644 index 0000000..a09f70a --- /dev/null +++ b/MainProject.Tests/Core/Filters/ValidationActionFilter_Tests.cs @@ -0,0 +1,135 @@ +using BasicDotnetTemplate.MainProject.Models.Api.Common.User; +using DatabaseSqlServer = BasicDotnetTemplate.MainProject.Models.Database.SqlServer; +using Microsoft.AspNetCore.Builder; +using BasicDotnetTemplate.MainProject.Models.Settings; +using BasicDotnetTemplate.MainProject.Utils; +using Microsoft.AspNetCore.Mvc.Filters; +using BasicDotnetTemplate.MainProject.Core.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using BasicDotnetTemplate.MainProject.Core.Filters; +using Newtonsoft.Json; +using BasicDotnetTemplate.MainProject.Models.Api.Base; + + +namespace BasicDotnetTemplate.MainProject.Tests; + +[TestClass] +public class ValidationActionFilter_Tests +{ + private readonly string _requestNotWellFormedMessage = "Request is not well formed"; + + private static ActionExecutingContext CreateContext(ModelStateDictionary modelState, object? requestBody = null) + { + var actionContext = new ActionContext( + new DefaultHttpContext(), + new Microsoft.AspNetCore.Routing.RouteData(), + new Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor(), + modelState + ); + var actionArguments = new Dictionary(); + + if (requestBody != null) + { + actionArguments.Add("request", requestBody); + } + return new ActionExecutingContext( + actionContext, + [], + actionArguments, + new Mock().Object + ); + } + + [TestMethod] + public void OnActionExecutionAsync_ModelStateInvalid_ReturnsBadRequestAndDoesNotCallNext() + { + // Arrange + var modelState = new ModelStateDictionary(); + modelState.AddModelError("MissingProperty", "MissingProperty is required"); + var context = CreateContext(modelState, new { SomeProp = "Value" }); + var nextCalled = false; + ActionExecutionDelegate next = () => { + nextCalled = true; + return Task.FromResult(new ActionExecutedContext(context, [], new Mock().Object)); + }; + var filter = new ValidationActionFilter(); + // Act + filter.OnActionExecutionAsync(context, next).GetAwaiter().GetResult(); + // Assert + Assert.IsNotNull(context.Result); + var badRequestResult = context.Result as BadRequestObjectResult; + Assert.IsNotNull(badRequestResult); + Assert.IsNotNull(badRequestResult!.Value); + + ValidationError validationError = (ValidationError)badRequestResult.Value; + Assert.AreEqual(_requestNotWellFormedMessage, validationError?.Message); + Assert.IsNotNull(validationError?.Errors); + Assert.IsFalse(modelState.IsValid); + Assert.IsFalse(nextCalled); + } + + [TestMethod] + public void OnActionExecutionAsync_ModelStateValid_RequestBodyNull_ReturnsBadRequestAndDoesNotCallNext() + { + + var modelState = new ModelStateDictionary(); + + var context = CreateContext(modelState, null); + var nextCalled = false; + ActionExecutionDelegate next = () => { + nextCalled = true; + return Task.FromResult(new ActionExecutedContext(context, [], new Mock().Object)); + }; + var filter = new ValidationActionFilter(); + // Act + filter.OnActionExecutionAsync(context, next).GetAwaiter().GetResult(); + // Assert + Assert.IsNotNull(context.Result); + var badRequestResult = context.Result as BadRequestObjectResult; + Assert.IsNotNull(badRequestResult); + Assert.IsNotNull(badRequestResult!.Value); + + ValidationError validationError = (ValidationError)badRequestResult.Value; + Assert.AreEqual(_requestNotWellFormedMessage, validationError?.Message); + Assert.IsNull(validationError?.Errors); + Assert.IsTrue(modelState.IsValid); + Assert.IsFalse(nextCalled); + } + + + [TestMethod] + public void OnActionExecutionAsync_ModelStateValid_RequestBodyValid_CallsNextAndDoesNotSetResult() + { + // Arrange + var modelState = new ModelStateDictionary(); + + var requestBody = new TestRequestBody { Value = "Test" }; + var context = CreateContext(modelState, requestBody); + var nextCalled = false; + ActionExecutionDelegate next = () => { + nextCalled = true; + return Task.FromResult(new ActionExecutedContext(context, [], new Mock().Object)); + }; + var filter = new ValidationActionFilter(); + // Act + filter.OnActionExecutionAsync(context, next).GetAwaiter().GetResult(); + // Assert + Assert.IsNull(context.Result); + Assert.IsTrue(nextCalled); + } + + + private class TestRequestBody + { + public string? Value { get; set; } + } + +} diff --git a/MainProject.Tests/JsonData/appsettings.json b/MainProject.Tests/JsonData/appsettings.json index a8fa93d..2580e5b 100644 --- a/MainProject.Tests/JsonData/appsettings.json +++ b/MainProject.Tests/JsonData/appsettings.json @@ -35,8 +35,9 @@ "ExpiredAfterMinsOfInactivity": 15 }, "EncryptionSettings": { - "Salt": "S7VIidfXQf1tOQYX", - "Pepper": "" + "SaltKey": "S7VIidfXQf1tOQYX", + "Salt": "u5CZAwq9vLGysC", + "Iterations": 10 } } diff --git a/MainProject.Tests/JsonData/invalidCryptAppsettings.json b/MainProject.Tests/JsonData/invalidCryptAppsettings.json index bb290e6..e7d6f9e 100644 --- a/MainProject.Tests/JsonData/invalidCryptAppsettings.json +++ b/MainProject.Tests/JsonData/invalidCryptAppsettings.json @@ -35,8 +35,9 @@ "ExpiredAfterMinsOfInactivity": 15 }, "EncryptionSettings": { - "Salt": "AAAAA", - "Pepper": "" + "SaltKey": "AAAAA", + "Salt": "", + "Iterations": 10 } } diff --git a/MainProject.Tests/MainProject.Tests.csproj b/MainProject.Tests/MainProject.Tests.csproj index 45f0343..975aa5a 100644 --- a/MainProject.Tests/MainProject.Tests.csproj +++ b/MainProject.Tests/MainProject.Tests.csproj @@ -10,12 +10,15 @@ - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + - - + + diff --git a/MainProject.Tests/Models/Api/Common/Role/UserRole_Tests.cs b/MainProject.Tests/Models/Api/Common/Role/UserRole_Tests.cs index aaa4fb6..91f2779 100644 --- a/MainProject.Tests/Models/Api/Common/Role/UserRole_Tests.cs +++ b/MainProject.Tests/Models/Api/Common/Role/UserRole_Tests.cs @@ -23,9 +23,9 @@ public class UserRole_Tests try { DatabaseSqlServer.Role role = ModelsInit.CreateRole(); - UserRole userRole = new UserRole(role); + UserRole userRole = new(role); - Assert.IsTrue(userRole.Name == role.Name); + Assert.AreEqual(role.Name, userRole.Name); } catch (Exception ex) { @@ -33,4 +33,4 @@ public class UserRole_Tests Assert.Fail($"An exception was thrown: {ex}"); } } -} \ No newline at end of file +} diff --git a/MainProject.Tests/Models/Api/Common/User/AuthenticatedUser_Tests.cs b/MainProject.Tests/Models/Api/Common/User/AuthenticatedUser_Tests.cs index 3d22813..448aed2 100644 --- a/MainProject.Tests/Models/Api/Common/User/AuthenticatedUser_Tests.cs +++ b/MainProject.Tests/Models/Api/Common/User/AuthenticatedUser_Tests.cs @@ -25,9 +25,9 @@ public class AuthenticatedUser_Tests DatabaseSqlServer.User user = ModelsInit.CreateUser(); AuthenticatedUser authenticatedUser = new AuthenticatedUser(user); - Assert.IsTrue(authenticatedUser.FirstName == user.FirstName); - Assert.IsTrue(authenticatedUser.LastName == user.LastName); - Assert.IsTrue(authenticatedUser.Email == user.Email); + Assert.AreEqual(user.FirstName, authenticatedUser.FirstName); + Assert.AreEqual(user.LastName, authenticatedUser.LastName); + Assert.AreEqual(user.Email, authenticatedUser.Email); Assert.IsInstanceOfType(authenticatedUser.Role, typeof(UserRole)); } catch (Exception ex) @@ -36,4 +36,4 @@ public class AuthenticatedUser_Tests Assert.Fail($"An exception was thrown: {ex}"); } } -} \ No newline at end of file +} diff --git a/MainProject.Tests/Services/UserService_Tests.cs b/MainProject.Tests/Services/UserService_Tests.cs index c3bc21f..910eec4 100644 --- a/MainProject.Tests/Services/UserService_Tests.cs +++ b/MainProject.Tests/Services/UserService_Tests.cs @@ -36,29 +36,6 @@ public class UserService_Tests } } - [TestMethod] - public async Task GetUserByUsernameAndPassword_Null() - { - try - { - var testString = "test"; - if (_userService != null) - { - var user = await _userService.GetUserByUsernameAndPassword(testString, testString); - Assert.IsTrue(user == null); - } - else - { - Assert.Fail($"UserService is null"); - } - } - catch (Exception ex) - { - Console.WriteLine(ex.InnerException); - Assert.Fail($"An exception was thrown: {ex}"); - } - } - [TestMethod] public async Task CheckIfEmailIsValid_EmailNotExists() { @@ -93,7 +70,8 @@ public class UserService_Tests { FirstName = expectedUser.FirstName ?? String.Empty, LastName = expectedUser.LastName ?? String.Empty, - Email = expectedUser.Email ?? String.Empty + Email = expectedUser.Email ?? String.Empty, + Password = "Password" }; Role role = new() @@ -106,10 +84,14 @@ public class UserService_Tests var user = await _userService.CreateUserAsync(data, role); Assert.IsInstanceOfType(user, typeof(User)); Assert.IsNotNull(user); - Assert.IsTrue(expectedUser.FirstName == user.FirstName); - Assert.IsTrue(expectedUser.LastName == user.LastName); - Assert.IsTrue(expectedUser.Email == user.Email); - Assert.IsTrue(expectedUser.Role?.Name == user.Role?.Name); + Assert.AreEqual(expectedUser.FirstName, user.FirstName); + Assert.AreEqual(expectedUser.LastName, user.LastName); + Assert.AreEqual(expectedUser.Email, user.Email); + Assert.AreEqual(expectedUser.Role?.Name, user.Role?.Name); + Assert.AreEqual(10, user.PasswordIterations); + Assert.IsNotNull(expectedUser.PasswordSalt); + Assert.IsNotNull(expectedUser.PasswordPepper); + Assert.IsNotNull(expectedUser.Password); _user = user; } @@ -120,6 +102,51 @@ public class UserService_Tests } } + [TestMethod] + public async Task GetUserByUsernameAndPassword_Null() + { + try + { + if (_userService != null) + { + var user = await _userService.GetUserByUsernameAndPassword(_user.Email, "WrongPassword"); + Assert.IsNull(user); + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex}"); + } + } + + [TestMethod] + public async Task GetUserByUsernameAndPassword_Success() + { + try + { + var password = "Password"; + if (_userService != null) + { + var user = await _userService.GetUserByUsernameAndPassword(_user.Email, password); + Assert.IsNotNull(user); + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex}"); + } + } + [TestMethod] public async Task CreateUserAsync_Exception() { @@ -131,7 +158,8 @@ public class UserService_Tests { FirstName = expectedUser.FirstName ?? String.Empty, LastName = expectedUser.LastName ?? String.Empty, - Email = expectedUser.Email ?? String.Empty + Email = expectedUser.Email ?? String.Empty, + Password = expectedUser.Password ?? String.Empty }; Role role = new() @@ -223,7 +251,7 @@ public class UserService_Tests { var user = await _userService.GetUserByIdAsync(_user.Id); Assert.IsNotNull(user); - Assert.IsTrue(user.Id == _user?.Id); + Assert.AreEqual(user.Id, _user?.Id); } else { @@ -247,7 +275,7 @@ public class UserService_Tests { var user = await _userService.GetUserByGuidAsync(_user.Guid ?? String.Empty); Assert.IsNotNull(user); - Assert.IsTrue(user.Guid == _user?.Guid); + Assert.AreEqual(user.Guid, _user?.Guid); } else { @@ -261,6 +289,186 @@ public class UserService_Tests } } + [TestMethod] + public async Task UpdateUserAsync_Success() + { + try + { + UpdateUserRequestData data = new UpdateUserRequestData() + { + FirstName = "ChangedUserFirstName", + LastName = "ChangedUserLastName" + }; + + if (_userService != null) + { + Assert.IsNotNull(_user); + var user = await _userService.UpdateUserAsync(data, _user!); + Assert.IsInstanceOfType(user, typeof(User)); + Assert.IsNotNull(user); + Assert.AreEqual(data.FirstName, user.FirstName); + Assert.AreEqual(data.LastName, user.LastName); + _user = user; + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex}"); + } + } + + [TestMethod] + public async Task UpdateUserAsync_Exception() + { + try + { + UpdateUserRequestData data = new UpdateUserRequestData() + { + FirstName = "ChangedUserFirstName", + LastName = "ChangedUserLastName" + }; + + var exceptionUserService = TestUtils.CreateUserServiceException(); + + if (exceptionUserService != null) + { + Assert.IsNotNull(_user); + var user = await exceptionUserService.UpdateUserAsync(data, _user!); + Assert.Fail($"Expected exception instead of response: {user?.Guid}"); + + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + } + } + + + [TestMethod] + public async Task UpdateUserPasswordAsync_Success() + { + try + { + if (_userService != null) + { + Assert.IsNotNull(_user); + var oldPassword = _user.Password; + var user = await _userService.UpdateUserPasswordAsync(_user!, "this-is-a-new-password"); + Assert.IsInstanceOfType(user, typeof(User)); + Assert.IsNotNull(user); + Assert.AreNotEqual(user.Password, oldPassword); + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex}"); + } + } + + [TestMethod] + public async Task UpdateUserPasswordAsync_Exception() + { + try + { + var exceptionUserService = TestUtils.CreateUserServiceException(); + + if (exceptionUserService != null) + { + Assert.IsNotNull(_user); + var user = await exceptionUserService.UpdateUserPasswordAsync(_user!, "this-is-a-new-password"); + Assert.Fail($"Expected exception instead of response: {user?.Guid}"); + + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + } + } + + [TestMethod] + public async Task UpdateUserRoleAsync_Success() + { + try + { + if (_userService != null) + { + Assert.IsNotNull(_user); + Role role = new() + { + Name = "NewRole", + IsNotEditable = false, + Guid = Guid.NewGuid().ToString() + }; + + var oldRole = _user.Role; + var user = await _userService.UpdateUserRoleAsync(_user!, role); + Assert.IsInstanceOfType(user, typeof(User)); + Assert.IsNotNull(user); + Assert.AreNotEqual(user.Role?.Id, oldRole?.Id); + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex}"); + } + } + + [TestMethod] + public async Task UpdateUserRoleAsync_Exception() + { + try + { + var exceptionUserService = TestUtils.CreateUserServiceException(); + + if (exceptionUserService != null) + { + Assert.IsNotNull(_user); + Role role = new() + { + Name = "NewRole", + IsNotEditable = false, + Guid = Guid.NewGuid().ToString() + }; + var user = await exceptionUserService.UpdateUserRoleAsync(_user!, role); + Assert.Fail($"Expected exception instead of response: {user?.Guid}"); + + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + } + } + [TestMethod] public async Task DeleteUser() { diff --git a/MainProject.Tests/TestsUtils/ExceptionSqlServerContext.cs b/MainProject.Tests/TestsUtils/ExceptionSqlServerContext.cs index 7587a75..465bcbd 100644 --- a/MainProject.Tests/TestsUtils/ExceptionSqlServerContext.cs +++ b/MainProject.Tests/TestsUtils/ExceptionSqlServerContext.cs @@ -21,4 +21,4 @@ public class ExceptionSqlServerContext : SqlServerContext } return base.SaveChangesAsync(cancellationToken); } -} \ No newline at end of file +} diff --git a/MainProject.Tests/TestsUtils/ModelsInit.cs b/MainProject.Tests/TestsUtils/ModelsInit.cs index 29541ff..bcbf9a9 100644 --- a/MainProject.Tests/TestsUtils/ModelsInit.cs +++ b/MainProject.Tests/TestsUtils/ModelsInit.cs @@ -13,8 +13,9 @@ public static class ModelsInit FirstName = "FirstName", LastName = "LastName", Email = "test-new@email.it", - PasswordHash = "PasswordHash", + PasswordPepper = "PasswordPepper", PasswordSalt = "PasswordSalt", + PasswordIterations = 0, Password = "Password", Role = CreateRole(), IsTestUser = true @@ -32,4 +33,4 @@ public static class ModelsInit }; return role; } -} \ No newline at end of file +} diff --git a/MainProject.Tests/Utils/CryptoUtils_Tests.cs b/MainProject.Tests/Utils/CryptUtils_Tests.cs similarity index 86% rename from MainProject.Tests/Utils/CryptoUtils_Tests.cs rename to MainProject.Tests/Utils/CryptUtils_Tests.cs index c054658..ad27193 100644 --- a/MainProject.Tests/Utils/CryptoUtils_Tests.cs +++ b/MainProject.Tests/Utils/CryptUtils_Tests.cs @@ -102,12 +102,12 @@ public class CryptoUtils_Tests } [TestMethod] - public void GenerateSalt() + public void GeneratePepper() { try { - var salt = CryptUtils.GenerateSalt(); - Assert.IsTrue(!String.IsNullOrEmpty(salt)); + var salt = CryptUtils.GeneratePepper(); + Assert.IsFalse(String.IsNullOrEmpty(salt)); } catch (Exception ex) { @@ -122,14 +122,14 @@ public class CryptoUtils_Tests try { var password = "P4ssw0rd@1!"; - var salt = CryptUtils.GenerateSalt(); - Assert.IsTrue(!String.IsNullOrEmpty(salt)); + var pepper = CryptUtils.GeneratePepper(); + Assert.IsFalse(String.IsNullOrEmpty(pepper)); WebApplicationBuilder builder = WebApplication.CreateBuilder(Array.Empty()); AppSettings appSettings = ProgramUtils.AddConfiguration(ref builder, System.AppDomain.CurrentDomain.BaseDirectory + "/JsonData"); - CryptUtils cryptoUtils = new CryptUtils(appSettings); - var encryptedPassword = cryptoUtils.GeneratePassword(password, salt, 0); - Assert.IsTrue(password != encryptedPassword); + var salt = appSettings?.EncryptionSettings?.Salt ?? String.Empty; + var encryptedPassword = CryptUtils.GeneratePassword(password, salt, 0, pepper); + Assert.AreNotEqual(encryptedPassword, password); } catch (Exception ex) { @@ -147,10 +147,7 @@ public class CryptoUtils_Tests var salt = "Afi7PQYgEL2sPbNyVzduvg=="; var hashedPassword = "2lMeySZ9ciH1KtSg1Z7oSJRmJEjHMeDvdaNRcJcGutM="; - WebApplicationBuilder builder = WebApplication.CreateBuilder(Array.Empty()); - AppSettings appSettings = ProgramUtils.AddConfiguration(ref builder, System.AppDomain.CurrentDomain.BaseDirectory + "/JsonData"); - CryptUtils cryptoUtils = new CryptUtils(appSettings); - var verified = cryptoUtils.VerifyPassword(password, salt, 0, hashedPassword); + var verified = CryptUtils.VerifyPassword(hashedPassword, password, salt, 0); Assert.IsTrue(verified); } catch (Exception ex) diff --git a/MainProject.Tests/Utils/PasswordUtils_Test.cs b/MainProject.Tests/Utils/PasswordUtils_Test.cs new file mode 100644 index 0000000..937a367 --- /dev/null +++ b/MainProject.Tests/Utils/PasswordUtils_Test.cs @@ -0,0 +1,62 @@ +using BasicDotnetTemplate.MainProject.Utils; +using BasicDotnetTemplate.MainProject.Models.Common; +using BasicDotnetTemplate.MainProject.Enum; + + +namespace BasicDotnetTemplate.MainProject.Tests; + +[TestClass] +public class PasswordUtils_Test +{ + [TestMethod] + public void PasswordValidation_Valid() + { + try + { + List errors = PasswordUtils.ValidatePassword("#aBcDeFgHi01245#"); + Assert.IsTrue(errors == null || errors.Count == 0); + } + catch (Exception exception) + { + Assert.Fail($"An exception was thrown: {exception}"); + } + } + + [TestMethod] + public void PasswordValidation_Invalid() + { + try + { + List errors = PasswordUtils.ValidatePassword("aAa1#"); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_LENGTH)); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_UPPER)); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_NUMBER)); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_SPECIAL)); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.IDENTICAL_CHARS)); + Assert.IsFalse(errors.Contains(PasswordValidationEnum.MIN_LOWER)); + } + catch (Exception exception) + { + Assert.Fail($"An exception was thrown: {exception}"); + } + } + + [TestMethod] + public void PasswordValidation_ToLowerInvalid() + { + try + { + List errors = PasswordUtils.ValidatePassword("AaBC0*TGH1#"); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_LOWER)); + } + catch (Exception exception) + { + Assert.Fail($"An exception was thrown: {exception}"); + } + } + +} + + + + diff --git a/MainProject/Controllers/AuthController.cs b/MainProject/Controllers/AuthController.cs index 6d9cb6f..fef8838 100644 --- a/MainProject/Controllers/AuthController.cs +++ b/MainProject/Controllers/AuthController.cs @@ -6,6 +6,7 @@ using BasicDotnetTemplate.MainProject.Models.Api.Request.Auth; using BasicDotnetTemplate.MainProject.Models.Api.Response; using BasicDotnetTemplate.MainProject.Models.Api.Response.Auth; using BasicDotnetTemplate.MainProject.Services; +using BasicDotnetTemplate.MainProject.Core.Filters; namespace BasicDotnetTemplate.MainProject.Controllers { @@ -21,30 +22,17 @@ namespace BasicDotnetTemplate.MainProject.Controllers this._authService = authService; } + [ModelStateValidationHandledByFilterAttribute] [HttpPost("authenticate")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType>(StatusCodes.Status404NotFound)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] [ProducesResponseType>(StatusCodes.Status500InternalServerError)] - public async Task AuthenticateAsync([FromBody] AuthenticateRequest request) + public async Task AuthenticateAsync([FromBody] AuthenticateRequest request) //NOSONAR { try { - if (!ModelState.IsValid) - { - return BadRequest(_requestNotWellFormed); - } - - if ( - request == null || - request.Data == null || - String.IsNullOrEmpty(request.Data.Email) || - String.IsNullOrEmpty(request.Data.Password) - ) - { - return BadRequest(_requestNotWellFormed); - } - var data = await this._authService.AuthenticateAsync(request.Data); + var data = await this._authService.AuthenticateAsync(request!.Data!); if (data == null) { @@ -55,14 +43,9 @@ namespace BasicDotnetTemplate.MainProject.Controllers } catch (Exception exception) { - var message = this._somethingWentWrong; - if (!String.IsNullOrEmpty(exception.Message)) - { - message += $". {exception.Message}"; - } - return InternalServerError(message); + return InternalServerError(exception); } } } -} \ No newline at end of file +} diff --git a/MainProject/Controllers/BaseController.cs b/MainProject/Controllers/BaseController.cs index ec2f51b..d217d39 100644 --- a/MainProject/Controllers/BaseController.cs +++ b/MainProject/Controllers/BaseController.cs @@ -12,7 +12,6 @@ namespace BasicDotnetTemplate.MainProject.Controllers protected readonly IMapper? _mapper; protected readonly IConfiguration _configuration; protected readonly AppSettings _appSettings; - protected readonly string _requestNotWellFormed = "Request is not well formed"; protected readonly string _somethingWentWrong = "Something went wrong"; protected BaseController( @@ -67,12 +66,16 @@ namespace BasicDotnetTemplate.MainProject.Controllers return StatusCode((int)HttpStatusCode.BadRequest, CreateResponse(HttpStatusCode.BadRequest, message, data)); } - protected IActionResult InternalServerError(string message) + protected IActionResult InternalServerError(Exception exception) { - message = String.IsNullOrEmpty(message) ? "Internal server error" : message; + var message = this._somethingWentWrong; + if (!String.IsNullOrEmpty(exception.Message)) + { + message += $". {exception.Message}"; + } return StatusCode((int)HttpStatusCode.InternalServerError, CreateResponse(HttpStatusCode.InternalServerError, message, new object())); } #nullable disable } -} \ No newline at end of file +} diff --git a/MainProject/Controllers/RoleController.cs b/MainProject/Controllers/RoleController.cs index 5b369a1..38ddc36 100644 --- a/MainProject/Controllers/RoleController.cs +++ b/MainProject/Controllers/RoleController.cs @@ -7,6 +7,7 @@ using BasicDotnetTemplate.MainProject.Models.Api.Response; using BasicDotnetTemplate.MainProject.Models.Api.Response.Role; using BasicDotnetTemplate.MainProject.Models.Database.SqlServer; using BasicDotnetTemplate.MainProject.Models.Api.Common.Role; +using BasicDotnetTemplate.MainProject.Core.Filters; namespace BasicDotnetTemplate.MainProject.Controllers { @@ -23,6 +24,7 @@ namespace BasicDotnetTemplate.MainProject.Controllers } [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] [HttpGet("get/{guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType>(StatusCodes.Status404NotFound)] @@ -32,15 +34,6 @@ namespace BasicDotnetTemplate.MainProject.Controllers { try { - if (!ModelState.IsValid) - { - return BadRequest(_requestNotWellFormed); - } - - if (String.IsNullOrEmpty(guid)) - { - return BadRequest(_requestNotWellFormed); - } var role = await this._roleService.GetRoleByGuidAsync(guid); if (role == null || String.IsNullOrEmpty(role.Guid)) @@ -54,39 +47,24 @@ namespace BasicDotnetTemplate.MainProject.Controllers } catch (Exception exception) { - var message = this._somethingWentWrong; - if (!String.IsNullOrEmpty(exception.Message)) - { - message += $". {exception.Message}"; - } - return InternalServerError(message); + return InternalServerError(exception); } } [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] [HttpPost("create")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] [ProducesResponseType>(StatusCodes.Status500InternalServerError)] - public async Task CreateRoleAsync([FromBody] CreateRoleRequest request) + public async Task CreateRoleAsync([FromBody] CreateRoleRequest request) //NOSONAR { try { - if (!ModelState.IsValid) + if (await this._roleService.CheckIfNameIsValid(request!.Data!.Name)) { - return BadRequest(_requestNotWellFormed); - } - - if (request == null || request.Data == null || String.IsNullOrEmpty(request.Data.Name) - ) - { - return BadRequest(_requestNotWellFormed); - } - - if (await this._roleService.CheckIfNameIsValid(request.Data.Name)) - { - var role = await this._roleService.CreateRoleAsync(request.Data); + var role = await this._roleService.CreateRoleAsync(request!.Data); if (role == null || String.IsNullOrEmpty(role.Guid)) { @@ -105,40 +83,21 @@ namespace BasicDotnetTemplate.MainProject.Controllers } catch (Exception exception) { - var message = this._somethingWentWrong; - if (!String.IsNullOrEmpty(exception.Message)) - { - message += $". {exception.Message}"; - } - return InternalServerError(message); + return InternalServerError(exception); } } [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] [HttpPut("update/{guid}")] - [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] [ProducesResponseType>(StatusCodes.Status500InternalServerError)] - public async Task UpdateRoleAsync([FromBody] CreateRoleRequest request, string guid) + public async Task UpdateRoleAsync([FromBody] CreateRoleRequest request, string guid) //NOSONAR { try { - if (!ModelState.IsValid) - { - return BadRequest(_requestNotWellFormed); - } - - if ( - request == null || - request.Data == null || - String.IsNullOrEmpty(request.Data.Name) || - String.IsNullOrEmpty(guid) - ) - { - return BadRequest(_requestNotWellFormed); - } - var role = await this._roleService.GetRoleByGuidAsync(guid); if (role == null || String.IsNullOrEmpty(role.Guid)) @@ -152,8 +111,8 @@ namespace BasicDotnetTemplate.MainProject.Controllers } if ( - await this._roleService.CheckIfNameIsValid(request.Data.Name) || - await this._roleService.CheckIfNameIsValid(request.Data.Name, guid) + await this._roleService.CheckIfNameIsValid(request!.Data!.Name) || + await this._roleService.CheckIfNameIsValid(request!.Data!.Name, guid) ) { role = await this._roleService.UpdateRoleAsync(request.Data, role); @@ -170,17 +129,13 @@ namespace BasicDotnetTemplate.MainProject.Controllers } catch (Exception exception) { - var message = this._somethingWentWrong; - if (!String.IsNullOrEmpty(exception.Message)) - { - message += $". {exception.Message}"; - } - return InternalServerError(message); + return InternalServerError(exception); } } [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] [HttpDelete("{guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType>(StatusCodes.Status404NotFound)] @@ -190,15 +145,6 @@ namespace BasicDotnetTemplate.MainProject.Controllers { try { - if (!ModelState.IsValid) - { - return BadRequest(_requestNotWellFormed); - } - - if (String.IsNullOrEmpty(guid)) - { - return BadRequest(_requestNotWellFormed); - } var role = await this._roleService.GetRoleByGuidAsync(guid); if (role == null || String.IsNullOrEmpty(role.Guid)) @@ -212,16 +158,11 @@ namespace BasicDotnetTemplate.MainProject.Controllers } catch (Exception exception) { - var message = this._somethingWentWrong; - if (!String.IsNullOrEmpty(exception.Message)) - { - message += $". {exception.Message}"; - } - return InternalServerError(message); + return InternalServerError(exception); } } } -} \ No newline at end of file +} diff --git a/MainProject/Controllers/RootController.cs b/MainProject/Controllers/RootController.cs index f48b1b0..9d38eca 100644 --- a/MainProject/Controllers/RootController.cs +++ b/MainProject/Controllers/RootController.cs @@ -15,4 +15,4 @@ namespace BasicDotnetTemplate.MainProject.Controllers return Ok(); } } -} \ No newline at end of file +} diff --git a/MainProject/Controllers/UserController.cs b/MainProject/Controllers/UserController.cs index cd9502b..48d8405 100644 --- a/MainProject/Controllers/UserController.cs +++ b/MainProject/Controllers/UserController.cs @@ -7,6 +7,7 @@ using BasicDotnetTemplate.MainProject.Models.Api.Response; using BasicDotnetTemplate.MainProject.Models.Api.Response.User; using BasicDotnetTemplate.MainProject.Models.Database.SqlServer; using BasicDotnetTemplate.MainProject.Models.Api.Common.User; +using BasicDotnetTemplate.MainProject.Core.Filters; namespace BasicDotnetTemplate.MainProject.Controllers { @@ -25,7 +26,9 @@ namespace BasicDotnetTemplate.MainProject.Controllers this._roleService = roleService; } + [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] [HttpGet("get/{guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType>(StatusCodes.Status404NotFound)] @@ -35,15 +38,6 @@ namespace BasicDotnetTemplate.MainProject.Controllers { try { - if (!ModelState.IsValid) - { - return BadRequest(_requestNotWellFormed); - } - - if (String.IsNullOrEmpty(guid)) - { - return BadRequest(_requestNotWellFormed); - } var user = await this._userService.GetUserByGuidAsync(guid); if (user == null || String.IsNullOrEmpty(user.Guid)) @@ -57,49 +51,30 @@ namespace BasicDotnetTemplate.MainProject.Controllers } catch (Exception exception) { - var message = this._somethingWentWrong; - if (!String.IsNullOrEmpty(exception.Message)) - { - message += $". {exception.Message}"; - } - return InternalServerError(message); + return InternalServerError(exception); } } [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] [HttpPost("create")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] [ProducesResponseType>(StatusCodes.Status500InternalServerError)] - public async Task CreateUserAsync([FromBody] CreateUserRequest request) + public async Task CreateUserAsync([FromBody] CreateUserRequest request) //NOSONAR { try { - if (!ModelState.IsValid) + if (await this._userService.CheckIfEmailIsValid(request!.Data!.Email)) { - return BadRequest(_requestNotWellFormed); - } - - if (request == null || request.Data == null || - String.IsNullOrEmpty(request.Data.FirstName) || - String.IsNullOrEmpty(request.Data.LastName) || - String.IsNullOrEmpty(request.Data.Email) || - String.IsNullOrEmpty(request.Data.Password) - ) - { - return BadRequest(_requestNotWellFormed); - } - - if (await this._userService.CheckIfEmailIsValid(request.Data.Email)) - { - var role = await this._roleService.GetRoleForUser(request.Data.RoleGuid); + var role = await this._roleService.GetRoleForUser(request!.Data!.RoleGuid); if (role == null) { return BadRequest("Role not found"); } - var user = await this._userService.CreateUserAsync(request.Data, role); + var user = await this._userService.CreateUserAsync(request!.Data, role); if (user == null || String.IsNullOrEmpty(user.Guid)) { @@ -118,17 +93,135 @@ namespace BasicDotnetTemplate.MainProject.Controllers } catch (Exception exception) { - var message = this._somethingWentWrong; - if (!String.IsNullOrEmpty(exception.Message)) - { - message += $". {exception.Message}"; - } - return InternalServerError(message); + return InternalServerError(exception); } } + [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] + [HttpPut("update/{guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType>(StatusCodes.Status400BadRequest)] + [ProducesResponseType>(StatusCodes.Status500InternalServerError)] + public async Task UpdateUserAsync([FromBody] UpdateUserRequest request, string guid) //NOSONAR + { + try + { + var user = await this._userService.GetUserByGuidAsync(guid); + if (user == null) + { + return NotFound(); + } + user = await this._userService.UpdateUserAsync(request!.Data!, user); + + var userDto = _mapper?.Map(user); + + return Success(String.Empty, userDto); + + } + catch (Exception exception) + { + return InternalServerError(exception); + } + + } + + [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] + [HttpPut("update/{guid}/password")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType>(StatusCodes.Status400BadRequest)] + [ProducesResponseType>(StatusCodes.Status500InternalServerError)] + public async Task UpdateUserPasswordAsync(string guid, string newPassword) + { + try + { + var user = await this._userService.GetUserByGuidAsync(guid); + if (user == null) + { + return NotFound(); + } + + user = await this._userService.UpdateUserPasswordAsync(user, newPassword); + + var userDto = _mapper?.Map(user); + + return Success(String.Empty, userDto); + + } + catch (Exception exception) + { + return InternalServerError(exception); + } + + } + + [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] + [HttpPut("update/{guid}/role")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType>(StatusCodes.Status400BadRequest)] + [ProducesResponseType>(StatusCodes.Status500InternalServerError)] + public async Task UpdateUserRoleAsync(string guid, string roleGuid) + { + try + { + var role = await this._roleService.GetRoleByGuidAsync(roleGuid); + if (role == null) + { + return BadRequest("Role not found"); + } + + var user = await this._userService.GetUserByGuidAsync(guid); + if (user == null) + { + return NotFound(); + } + + user = await this._userService.UpdateUserRoleAsync(user, role); + + var userDto = _mapper?.Map(user); + + return Success(String.Empty, userDto); + + } + catch (Exception exception) + { + return InternalServerError(exception); + } + + } + + [JwtAuthorization()] + [ModelStateValidationHandledByFilterAttribute] + [HttpDelete("{guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType>(StatusCodes.Status404NotFound)] + [ProducesResponseType>(StatusCodes.Status400BadRequest)] + [ProducesResponseType>(StatusCodes.Status500InternalServerError)] + public async Task DeleteUserByGuidAsync(string guid) + { + try + { + var user = await this._userService.GetUserByGuidAsync(guid); + + if (user == null || String.IsNullOrEmpty(user.Guid)) + { + return NotFound(); + } + + await this._userService.DeleteUserAsync(user); + + return Success(String.Empty); + } + catch (Exception exception) + { + return InternalServerError(exception); + } + + } } -} \ No newline at end of file +} diff --git a/MainProject/Controllers/VersionController.cs b/MainProject/Controllers/VersionController.cs index ee527ed..e818d01 100644 --- a/MainProject/Controllers/VersionController.cs +++ b/MainProject/Controllers/VersionController.cs @@ -11,9 +11,6 @@ namespace BasicDotnetTemplate.MainProject.Controllers ) : base(configuration) { } [HttpGet("get")] - public IActionResult GetVersion() - { - return Success(String.Empty, _appSettings?.Settings?.Version); - } + public IActionResult GetVersion() => Success(String.Empty, _appSettings?.Settings?.Version); } -} \ No newline at end of file +} diff --git a/MainProject/Core/Attributes/ValidateModelStateAutomaticallyAttribute.cs b/MainProject/Core/Attributes/ValidateModelStateAutomaticallyAttribute.cs new file mode 100644 index 0000000..ad8cd1b --- /dev/null +++ b/MainProject/Core/Attributes/ValidateModelStateAutomaticallyAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace BasicDotnetTemplate.MainProject.Core.Attributes +{ + /// + /// Indicates that ModelState validation is handled automatically by an Action Filter. + /// Used to suppress SonarCloud warnings about missing ModelState.IsValid checks. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class ModelStateValidationHandledByFilterAttribute : Attribute + { } +} diff --git a/MainProject/Core/Database/SqlServerContext.cs b/MainProject/Core/Database/SqlServerContext.cs index f36d364..16b0ae9 100644 --- a/MainProject/Core/Database/SqlServerContext.cs +++ b/MainProject/Core/Database/SqlServerContext.cs @@ -18,7 +18,7 @@ namespace BasicDotnetTemplate.MainProject.Core.Database public DbSet PermissionOperations { get; set; } public DbSet PermissionSystems { get; set; } public DbSet PermissionSystemModules { get; set; } - public DbSet PermissionSystemModuleOperations { get; set; } + public DbSet PermissionSystemModuleOperations { get; set; } public DbSet RolePermissionSystemModuleOperations { get; set; } public DbSet Roles { get; set; } public DbSet Users { get; set; } diff --git a/MainProject/Core/Filters/ValidationActionFilter.cs b/MainProject/Core/Filters/ValidationActionFilter.cs new file mode 100644 index 0000000..ef9ca29 --- /dev/null +++ b/MainProject/Core/Filters/ValidationActionFilter.cs @@ -0,0 +1,43 @@ +using BasicDotnetTemplate.MainProject.Models.Api.Base; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System.Threading.Tasks; + +namespace BasicDotnetTemplate.MainProject.Core.Filters +{ + public class ValidationActionFilter : IAsyncActionFilter + { + private readonly string _requestNotWellFormedMessage = "Request is not well formed"; + + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if (!context.ModelState.IsValid) + { + context.Result = new BadRequestObjectResult(new ValidationError + { + Message = _requestNotWellFormedMessage, + Errors = context.ModelState.Where(m => + m.Value != null && m.Value.Errors.Any()) + .ToDictionary( + m => m.Key, + m => m.Value!.Errors.Select(e => e.ErrorMessage).ToList() + ) + }); + return; + } + + var requestBody = context.ActionArguments.Values.FirstOrDefault(arg => arg != null && !arg.GetType().IsPrimitive && arg is not string); + + if (requestBody == null) + { + context.Result = new BadRequestObjectResult(new ValidationError + { + Message = _requestNotWellFormedMessage + }); + return; + } + + await next(); + } + } +} diff --git a/MainProject/Enum/PasswordValidationEnum.cs b/MainProject/Enum/PasswordValidationEnum.cs new file mode 100644 index 0000000..b821805 --- /dev/null +++ b/MainProject/Enum/PasswordValidationEnum.cs @@ -0,0 +1,10 @@ +namespace BasicDotnetTemplate.MainProject.Enum; +public static class PasswordValidationEnum +{ + public const string MIN_LENGTH = "Password must be at least 8 characters long"; + public const string MIN_UPPER = "Password must have at least 2 uppercase letters"; + public const string MIN_LOWER = "Password must have at least 2 lowercase letters"; + public const string MIN_NUMBER = "Password must be at least 2 numbers"; + public const string MIN_SPECIAL = "Password must be at least 2 special characters"; + public const string IDENTICAL_CHARS = "Password cannot have 3 or more consecutive identical characters"; +} diff --git a/MainProject/MainProject.csproj b/MainProject/MainProject.csproj index 24c681d..3fecdf7 100644 --- a/MainProject/MainProject.csproj +++ b/MainProject/MainProject.csproj @@ -15,36 +15,36 @@ all - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + - - + + - - - - - - - - + + + + + + + + diff --git a/MainProject/Migrations/20240904211920_InitialCreate.cs b/MainProject/Migrations/20240904211920_InitialCreate.cs index ed4ccbc..d7cf0c6 100644 --- a/MainProject/Migrations/20240904211920_InitialCreate.cs +++ b/MainProject/Migrations/20240904211920_InitialCreate.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MainProject/Migrations/20241129231345_Try.cs b/MainProject/Migrations/20241129231345_Try.cs index 46addf6..29573f2 100644 --- a/MainProject/Migrations/20241129231345_Try.cs +++ b/MainProject/Migrations/20241129231345_Try.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MainProject/Migrations/20250311195750_AlterTableUser.cs b/MainProject/Migrations/20250311195750_AlterTableUser.cs index 26df65b..b846699 100644 --- a/MainProject/Migrations/20250311195750_AlterTableUser.cs +++ b/MainProject/Migrations/20250311195750_AlterTableUser.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MainProject/Migrations/20250312234517_AlterTableUserMaxLengthIndexes.cs b/MainProject/Migrations/20250312234517_AlterTableUserMaxLengthIndexes.cs index 862a803..ca6e66d 100644 --- a/MainProject/Migrations/20250312234517_AlterTableUserMaxLengthIndexes.cs +++ b/MainProject/Migrations/20250312234517_AlterTableUserMaxLengthIndexes.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MainProject/Migrations/20250316014620_AlterTablesUsersAndRoles.cs b/MainProject/Migrations/20250316014620_AlterTablesUsersAndRoles.cs index b3192e0..bae0d5a 100644 --- a/MainProject/Migrations/20250316014620_AlterTablesUsersAndRoles.cs +++ b/MainProject/Migrations/20250316014620_AlterTablesUsersAndRoles.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MainProject/Migrations/20250316212343_AlterBaseUpdateTimeDeletionTimeNullable.cs b/MainProject/Migrations/20250316212343_AlterBaseUpdateTimeDeletionTimeNullable.cs index 5c7c4c9..1d789d6 100644 --- a/MainProject/Migrations/20250316212343_AlterBaseUpdateTimeDeletionTimeNullable.cs +++ b/MainProject/Migrations/20250316212343_AlterBaseUpdateTimeDeletionTimeNullable.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MainProject/Migrations/20250316212722_AlterTableRoleAddedIsNotEditable.cs b/MainProject/Migrations/20250316212722_AlterTableRoleAddedIsNotEditable.cs index 423f594..ecb0ae0 100644 --- a/MainProject/Migrations/20250316212722_AlterTableRoleAddedIsNotEditable.cs +++ b/MainProject/Migrations/20250316212722_AlterTableRoleAddedIsNotEditable.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MainProject/Migrations/20250426183010_AddingPermissionsTables.cs b/MainProject/Migrations/20250426183010_AddingPermissionsTables.cs index 3390e0d..7edef53 100644 --- a/MainProject/Migrations/20250426183010_AddingPermissionsTables.cs +++ b/MainProject/Migrations/20250426183010_AddingPermissionsTables.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MainProject/Migrations/20250617183212_AlterTableUsersForPasswordEncryption.Designer.cs b/MainProject/Migrations/20250617183212_AlterTableUsersForPasswordEncryption.Designer.cs new file mode 100644 index 0000000..53baad2 --- /dev/null +++ b/MainProject/Migrations/20250617183212_AlterTableUsersForPasswordEncryption.Designer.cs @@ -0,0 +1,542 @@ +// +using System; +using BasicDotnetTemplate.MainProject.Core.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace MainProject.Migrations +{ + [DbContext(typeof(SqlServerContext))] + [Migration("20250617183212_AlterTableUsersForPasswordEncryption")] + partial class AlterTableUsersForPasswordEncryption + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreationUserId") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DeletionUserId") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("Guid") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdateTime") + .HasColumnType("datetime2"); + + b.Property("UpdateUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Enabled" }, "IX_Enabled") + .HasFilter("[Enabled] = 1"); + + b.HasIndex(new[] { "IsDeleted" }, "IX_IsDeleted") + .HasFilter("[IsDeleted] = 0"); + + b.HasIndex(new[] { "IsDeleted", "Name", "Enabled" }, "IX_IsDeleted_Name_Enabled") + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("PermissionModules"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreationUserId") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DeletionUserId") + .HasColumnType("int"); + + b.Property("Guid") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdateTime") + .HasColumnType("datetime2"); + + b.Property("UpdateUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "IsDeleted", "Name" }, "IX_IsDeleted_Name"); + + b.ToTable("PermissionOperations"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionSystem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreationUserId") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DeletionUserId") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("Guid") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdateTime") + .HasColumnType("datetime2"); + + b.Property("UpdateUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Enabled" }, "IX_Enabled") + .HasFilter("[Enabled] = 1"); + + b.HasIndex(new[] { "IsDeleted" }, "IX_IsDeleted") + .HasFilter("[IsDeleted] = 0"); + + b.HasIndex(new[] { "IsDeleted", "Name", "Enabled" }, "IX_IsDeleted_Name_Enabled") + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("PermissionSystems"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionSystemModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreationUserId") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DeletionUserId") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("Guid") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("PermissionModuleId") + .HasColumnType("int"); + + b.Property("PermissionSystemId") + .HasColumnType("int"); + + b.Property("UpdateTime") + .HasColumnType("datetime2"); + + b.Property("UpdateUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PermissionModuleId"); + + b.HasIndex("PermissionSystemId"); + + b.ToTable("PermissionSystemModules"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionSystemModuleOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreationUserId") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DeletionUserId") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("Guid") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("PermissionOperationId") + .HasColumnType("int"); + + b.Property("PermissionSystemModuleId") + .HasColumnType("int"); + + b.Property("UpdateTime") + .HasColumnType("datetime2"); + + b.Property("UpdateUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PermissionOperationId"); + + b.HasIndex("PermissionSystemModuleId"); + + b.HasIndex(new[] { "IsDeleted", "Enabled", "Guid" }, "IX_IsDeleted_Enabled_Guid"); + + b.ToTable("PermissionSystemModuleOperations"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreationUserId") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DeletionUserId") + .HasColumnType("int"); + + b.Property("Guid") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsNotEditable") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdateTime") + .HasColumnType("datetime2"); + + b.Property("UpdateUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "IsDeleted", "Guid" }, "IX_IsDeleted_Guid") + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.RolePermissionSystemModuleOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreationUserId") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DeletionUserId") + .HasColumnType("int"); + + b.Property("Guid") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("PermissionSystemModuleOperationId") + .HasColumnType("int"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("UpdateTime") + .HasColumnType("datetime2"); + + b.Property("UpdateUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PermissionSystemModuleOperationId"); + + b.HasIndex("RoleId"); + + b.ToTable("RolePermissionSystemModuleOperations"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreationUserId") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DeletionUserId") + .HasColumnType("int"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Guid") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("nvarchar(45)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsTestUser") + .HasColumnType("bit"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordIterations") + .HasColumnType("int"); + + b.Property("PasswordPepper") + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("UpdateTime") + .HasColumnType("datetime2"); + + b.Property("UpdateUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.HasIndex(new[] { "Email" }, "IX_Email"); + + b.HasIndex(new[] { "IsDeleted", "Guid" }, "IX_IsDeleted_Guid") + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionSystemModule", b => + { + b.HasOne("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionModule", "PermissionModule") + .WithMany() + .HasForeignKey("PermissionModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionSystem", "PermissionSystem") + .WithMany() + .HasForeignKey("PermissionSystemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PermissionModule"); + + b.Navigation("PermissionSystem"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionSystemModuleOperation", b => + { + b.HasOne("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionOperation", "PermissionOperation") + .WithMany() + .HasForeignKey("PermissionOperationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionSystemModule", "PermissionSystemModule") + .WithMany() + .HasForeignKey("PermissionSystemModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PermissionOperation"); + + b.Navigation("PermissionSystemModule"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.RolePermissionSystemModuleOperation", b => + { + b.HasOne("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.PermissionSystemModuleOperation", "PermissionSystemModuleOperation") + .WithMany() + .HasForeignKey("PermissionSystemModuleOperationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PermissionSystemModuleOperation"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.User", b => + { + b.HasOne("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + + b.Navigation("Role"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MainProject/Migrations/20250617183212_AlterTableUsersForPasswordEncryption.cs b/MainProject/Migrations/20250617183212_AlterTableUsersForPasswordEncryption.cs new file mode 100644 index 0000000..585901a --- /dev/null +++ b/MainProject/Migrations/20250617183212_AlterTableUsersForPasswordEncryption.cs @@ -0,0 +1,109 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MainProject.Migrations +{ + /// + public partial class AlterTableUsersForPasswordEncryption : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Users_Roles_RoleId", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PasswordHash", + table: "Users"); + + migrationBuilder.AlterColumn( + name: "RoleId", + table: "Users", + type: "int", + nullable: true, + oldClrType: typeof(int), + oldType: "int"); + + migrationBuilder.AlterColumn( + name: "Password", + table: "Users", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AddColumn( + name: "PasswordIterations", + table: "Users", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "PasswordPepper", + table: "Users", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_Users_Roles_RoleId", + table: "Users", + column: "RoleId", + principalTable: "Roles", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Users_Roles_RoleId", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PasswordIterations", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PasswordPepper", + table: "Users"); + + migrationBuilder.AlterColumn( + name: "RoleId", + table: "Users", + type: "int", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "int", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Password", + table: "Users", + type: "nvarchar(max)", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "PasswordHash", + table: "Users", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddForeignKey( + name: "FK_Users_Roles_RoleId", + table: "Users", + column: "RoleId", + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/MainProject/Migrations/SqlServerContextModelSnapshot.cs b/MainProject/Migrations/SqlServerContextModelSnapshot.cs index d32f3cb..5d71ef6 100644 --- a/MainProject/Migrations/SqlServerContextModelSnapshot.cs +++ b/MainProject/Migrations/SqlServerContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace MainProject.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.2") + .HasAnnotation("ProductVersion", "9.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -435,18 +435,19 @@ namespace MainProject.Migrations .HasColumnType("nvarchar(200)"); b.Property("Password") - .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("PasswordHash") - .IsRequired() + b.Property("PasswordIterations") + .HasColumnType("int"); + + b.Property("PasswordPepper") .HasColumnType("nvarchar(max)"); b.Property("PasswordSalt") .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("RoleId") + b.Property("RoleId") .HasColumnType("int"); b.Property("UpdateTime") @@ -528,9 +529,7 @@ namespace MainProject.Migrations { b.HasOne("BasicDotnetTemplate.MainProject.Models.Database.SqlServer.Role", "Role") .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("RoleId"); b.Navigation("Role"); }); diff --git a/MainProject/Models/Api/Base/ValidationError.cs b/MainProject/Models/Api/Base/ValidationError.cs new file mode 100644 index 0000000..ab96f25 --- /dev/null +++ b/MainProject/Models/Api/Base/ValidationError.cs @@ -0,0 +1,9 @@ +using System; + +namespace BasicDotnetTemplate.MainProject.Models.Api.Base; + +public class ValidationError +{ + public string? Message { get; set; } + public Dictionary>? Errors { get; set; } +} diff --git a/MainProject/Models/Api/Common/Role/UserRole.cs b/MainProject/Models/Api/Common/Role/UserRole.cs index b568ca7..b5b6097 100644 --- a/MainProject/Models/Api/Common/Role/UserRole.cs +++ b/MainProject/Models/Api/Common/Role/UserRole.cs @@ -9,7 +9,7 @@ public class UserRole public string? Name { get; set; } #nullable disable - public UserRole() {} + public UserRole() { } public UserRole(DatabaseSqlServer.Role role) { diff --git a/MainProject/Models/Api/Common/User/AuthenticatedUser.cs b/MainProject/Models/Api/Common/User/AuthenticatedUser.cs index 1791717..e2b871a 100644 --- a/MainProject/Models/Api/Common/User/AuthenticatedUser.cs +++ b/MainProject/Models/Api/Common/User/AuthenticatedUser.cs @@ -19,7 +19,7 @@ public class AuthenticatedUser FirstName = user.FirstName; LastName = user.LastName; Email = user.Email; - Role = new UserRole(); + Role = new UserRole(user.Role!); } } diff --git a/MainProject/Models/Api/Data/Auth/AuthenticateRequestData.cs b/MainProject/Models/Api/Data/Auth/AuthenticateRequestData.cs index ef1c7c1..1e7801f 100644 --- a/MainProject/Models/Api/Data/Auth/AuthenticateRequestData.cs +++ b/MainProject/Models/Api/Data/Auth/AuthenticateRequestData.cs @@ -1,11 +1,13 @@ +using System.ComponentModel.DataAnnotations; + namespace BasicDotnetTemplate.MainProject.Models.Api.Data.Auth; public class AuthenticateRequestData { -#nullable enable - public string? Email { get; set; } - public string? Password { get; set; } -#nullable disable + [Required(ErrorMessage = "Email is required")] + public required string Email { get; set; } + [Required(ErrorMessage = "Password is required")] + public required string Password { get; set; } } diff --git a/MainProject/Models/Api/Data/Role/CreateRoleRequestData.cs b/MainProject/Models/Api/Data/Role/CreateRoleRequestData.cs index 6831cc0..0a6a1a9 100644 --- a/MainProject/Models/Api/Data/Role/CreateRoleRequestData.cs +++ b/MainProject/Models/Api/Data/Role/CreateRoleRequestData.cs @@ -1,8 +1,11 @@ +using System.ComponentModel.DataAnnotations; + namespace BasicDotnetTemplate.MainProject.Models.Api.Data.Role; public class CreateRoleRequestData { - public string Name { get; set; } = String.Empty; + [Required(ErrorMessage = "Name is required")] + public required string Name { get; set; } public required bool IsNotEditable { get; set; } -} \ No newline at end of file +} diff --git a/MainProject/Models/Api/Data/User/CreateUserRequestData.cs b/MainProject/Models/Api/Data/User/CreateUserRequestData.cs index 2202def..17971c2 100644 --- a/MainProject/Models/Api/Data/User/CreateUserRequestData.cs +++ b/MainProject/Models/Api/Data/User/CreateUserRequestData.cs @@ -1,11 +1,16 @@ +using System.ComponentModel.DataAnnotations; + namespace BasicDotnetTemplate.MainProject.Models.Api.Data.User; -public class CreateUserRequestData +public class CreateUserRequestData : UpdateUserRequestData { - public string FirstName { get; set; } = String.Empty; - public string LastName { get; set; } = String.Empty; - public string Email { get; set; } = String.Empty; - public string Password { get; set; } = String.Empty; + [Required(ErrorMessage = "Email is required")] + [StringLength(200, ErrorMessage = "Email's maxLength: 200")] + public required string Email { get; set; } + + [Required(ErrorMessage = "Password is required")] + public required string Password { get; set; } + public string? RoleGuid { get; set; } } diff --git a/MainProject/Models/Api/Data/User/UpdateUserRequestData.cs b/MainProject/Models/Api/Data/User/UpdateUserRequestData.cs new file mode 100644 index 0000000..aea9cd3 --- /dev/null +++ b/MainProject/Models/Api/Data/User/UpdateUserRequestData.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace BasicDotnetTemplate.MainProject.Models.Api.Data.User; + +public class UpdateUserRequestData +{ + [Required(ErrorMessage = "FirstName is required")] + [StringLength(200, ErrorMessage = "FirstName's maxLength: 200")] + public required string FirstName { get; set; } + + [Required(ErrorMessage = "LastName is required")] + [StringLength(200, ErrorMessage = "LastName's maxLength: 200")] + public required string LastName { get; set; } + +} + + + + diff --git a/MainProject/Models/Api/Request/Auth/AuthenticateRequest.cs b/MainProject/Models/Api/Request/Auth/AuthenticateRequest.cs index f7a8e52..ecd9624 100644 --- a/MainProject/Models/Api/Request/Auth/AuthenticateRequest.cs +++ b/MainProject/Models/Api/Request/Auth/AuthenticateRequest.cs @@ -1,12 +1,12 @@ +using System.ComponentModel.DataAnnotations; using BasicDotnetTemplate.MainProject.Models.Api.Data.Auth; namespace BasicDotnetTemplate.MainProject.Models.Api.Request.Auth; public class AuthenticateRequest { -#nullable enable - public AuthenticateRequestData? Data { get; set; } -#nullable disable + [Required(ErrorMessage = "Data is required")] + public required AuthenticateRequestData Data { get; set; } } diff --git a/MainProject/Models/Api/Request/Role/CreateRoleRequest.cs b/MainProject/Models/Api/Request/Role/CreateRoleRequest.cs index d83636a..79c2c72 100644 --- a/MainProject/Models/Api/Request/Role/CreateRoleRequest.cs +++ b/MainProject/Models/Api/Request/Role/CreateRoleRequest.cs @@ -1,10 +1,10 @@ +using System.ComponentModel.DataAnnotations; using BasicDotnetTemplate.MainProject.Models.Api.Data.Role; namespace BasicDotnetTemplate.MainProject.Models.Api.Request.Role; public class CreateRoleRequest { -#nullable enable - public CreateRoleRequestData? Data { get; set; } -#nullable disable -} \ No newline at end of file + [Required(ErrorMessage = "Data is required")] + public required CreateRoleRequestData? Data { get; set; } +} diff --git a/MainProject/Models/Api/Request/User/CreateUserRequest.cs b/MainProject/Models/Api/Request/User/CreateUserRequest.cs index 36ba57c..09dfb41 100644 --- a/MainProject/Models/Api/Request/User/CreateUserRequest.cs +++ b/MainProject/Models/Api/Request/User/CreateUserRequest.cs @@ -1,12 +1,12 @@ +using System.ComponentModel.DataAnnotations; using BasicDotnetTemplate.MainProject.Models.Api.Data.User; namespace BasicDotnetTemplate.MainProject.Models.Api.Request.User; public class CreateUserRequest { -#nullable enable - public CreateUserRequestData? Data { get; set; } -#nullable disable + [Required(ErrorMessage = "Data is required")] + public required CreateUserRequestData Data { get; set; } } diff --git a/MainProject/Models/Api/Request/User/UpdateUserRequest.cs b/MainProject/Models/Api/Request/User/UpdateUserRequest.cs new file mode 100644 index 0000000..7e9ee40 --- /dev/null +++ b/MainProject/Models/Api/Request/User/UpdateUserRequest.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using BasicDotnetTemplate.MainProject.Models.Api.Data.User; + +namespace BasicDotnetTemplate.MainProject.Models.Api.Request.User; + +public class UpdateUserRequest +{ + [Required(ErrorMessage = "Data is required")] + public required UpdateUserRequestData Data { get; set; } +} + + + + diff --git a/MainProject/Models/Api/Response/Auth/AuthenticateResponse.cs b/MainProject/Models/Api/Response/Auth/AuthenticateResponse.cs index 8d36f75..8827d00 100644 --- a/MainProject/Models/Api/Response/Auth/AuthenticateResponse.cs +++ b/MainProject/Models/Api/Response/Auth/AuthenticateResponse.cs @@ -5,4 +5,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Api.Response.Auth; public class AuthenticateResponse : BaseResponse { public AuthenticateResponse(int status, string? message, AuthenticatedUser? data) : base(status, message, data) { } -} \ No newline at end of file +} diff --git a/MainProject/Models/Api/Response/BaseResponse.cs b/MainProject/Models/Api/Response/BaseResponse.cs index 2b87876..70c7e99 100644 --- a/MainProject/Models/Api/Response/BaseResponse.cs +++ b/MainProject/Models/Api/Response/BaseResponse.cs @@ -16,4 +16,4 @@ public class BaseResponse public virtual dynamic? Data { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Api/Response/Role/GetRoleResponse.cs b/MainProject/Models/Api/Response/Role/GetRoleResponse.cs index 957646c..a061159 100644 --- a/MainProject/Models/Api/Response/Role/GetRoleResponse.cs +++ b/MainProject/Models/Api/Response/Role/GetRoleResponse.cs @@ -5,4 +5,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Api.Response.Role; public class GetRoleResponse : BaseResponse { public GetRoleResponse(int status, string? message, RoleDto? data) : base(status, message, data) { } -} \ No newline at end of file +} diff --git a/MainProject/Models/Api/Response/User/GetUserResponse.cs b/MainProject/Models/Api/Response/User/GetUserResponse.cs index b1979fa..b0989a6 100644 --- a/MainProject/Models/Api/Response/User/GetUserResponse.cs +++ b/MainProject/Models/Api/Response/User/GetUserResponse.cs @@ -5,4 +5,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Api.Response.User; public class GetUserResponse : BaseResponse { public GetUserResponse(int status, string? message, UserDto? data) : base(status, message, data) { } -} \ No newline at end of file +} diff --git a/MainProject/Models/Common/OperationInfo.cs b/MainProject/Models/Common/OperationInfo.cs index 7e9a9a8..eb3a0dd 100644 --- a/MainProject/Models/Common/OperationInfo.cs +++ b/MainProject/Models/Common/OperationInfo.cs @@ -4,6 +4,6 @@ public class OperationInfo { #nullable enable public string? Operation { get; set; } - public List? Roles {get; set; } + public List? Roles { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Common/PermissionInfo.cs b/MainProject/Models/Common/PermissionInfo.cs index 558fc5d..369f2d6 100644 --- a/MainProject/Models/Common/PermissionInfo.cs +++ b/MainProject/Models/Common/PermissionInfo.cs @@ -4,6 +4,6 @@ public class PermissionInfo { #nullable enable public string? System { get; set; } - public List? RolePermissionModuleOperations {get; set; } + public List? RolePermissionModuleOperations { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Common/PermissionsFile.cs b/MainProject/Models/Common/PermissionsFile.cs index f5a4d7b..c515d92 100644 --- a/MainProject/Models/Common/PermissionsFile.cs +++ b/MainProject/Models/Common/PermissionsFile.cs @@ -5,4 +5,4 @@ public class PermissionsFile #nullable enable public List? PermissionInfos { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Common/RolePermissionModuleOperation.cs b/MainProject/Models/Common/RolePermissionModuleOperation.cs index 6d132e5..3af3e33 100644 --- a/MainProject/Models/Common/RolePermissionModuleOperation.cs +++ b/MainProject/Models/Common/RolePermissionModuleOperation.cs @@ -6,4 +6,4 @@ public class RolePermissionModuleOperation public string? Module { get; set; } public List? Operations { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Database/Mongo/Log.cs b/MainProject/Models/Database/Mongo/Log.cs index f3d4e92..7694b26 100644 --- a/MainProject/Models/Database/Mongo/Log.cs +++ b/MainProject/Models/Database/Mongo/Log.cs @@ -8,4 +8,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Database.Mongo [BsonId] public ObjectId Id { get; set; } } -} \ No newline at end of file +} diff --git a/MainProject/Models/Database/SqlServer/PermissionModule.cs b/MainProject/Models/Database/SqlServer/PermissionModule.cs index dba3a92..72baa47 100644 --- a/MainProject/Models/Database/SqlServer/PermissionModule.cs +++ b/MainProject/Models/Database/SqlServer/PermissionModule.cs @@ -8,4 +8,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Database.SqlServer public required string Name { get; set; } public required bool Enabled { get; set; } } -} \ No newline at end of file +} diff --git a/MainProject/Models/Database/SqlServer/PermissionOperation.cs b/MainProject/Models/Database/SqlServer/PermissionOperation.cs index 4080277..7770331 100644 --- a/MainProject/Models/Database/SqlServer/PermissionOperation.cs +++ b/MainProject/Models/Database/SqlServer/PermissionOperation.cs @@ -7,4 +7,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Database.SqlServer [MaxLength(100)] public required string Name { get; set; } } -} \ No newline at end of file +} diff --git a/MainProject/Models/Database/SqlServer/PermissionSystem.cs b/MainProject/Models/Database/SqlServer/PermissionSystem.cs index 7b28d33..8f5e185 100644 --- a/MainProject/Models/Database/SqlServer/PermissionSystem.cs +++ b/MainProject/Models/Database/SqlServer/PermissionSystem.cs @@ -8,4 +8,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Database.SqlServer public required string Name { get; set; } public required bool Enabled { get; set; } } -} \ No newline at end of file +} diff --git a/MainProject/Models/Database/SqlServer/PermissionSystemModule.cs b/MainProject/Models/Database/SqlServer/PermissionSystemModule.cs index 84eef1f..2ce3eae 100644 --- a/MainProject/Models/Database/SqlServer/PermissionSystemModule.cs +++ b/MainProject/Models/Database/SqlServer/PermissionSystemModule.cs @@ -10,4 +10,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Database.SqlServer public required PermissionModule PermissionModule { get; set; } public required bool Enabled { get; set; } } -} \ No newline at end of file +} diff --git a/MainProject/Models/Database/SqlServer/PermissionSystemModuleOperation.cs b/MainProject/Models/Database/SqlServer/PermissionSystemModuleOperation.cs index deb5d8b..f716058 100644 --- a/MainProject/Models/Database/SqlServer/PermissionSystemModuleOperation.cs +++ b/MainProject/Models/Database/SqlServer/PermissionSystemModuleOperation.cs @@ -10,4 +10,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Database.SqlServer public required PermissionSystemModule PermissionSystemModule { get; set; } public required PermissionOperation PermissionOperation { get; set; } } -} \ No newline at end of file +} diff --git a/MainProject/Models/Database/SqlServer/RolePermissionSystemModuleOperation.cs b/MainProject/Models/Database/SqlServer/RolePermissionSystemModuleOperation.cs index d42be00..d1f437a 100644 --- a/MainProject/Models/Database/SqlServer/RolePermissionSystemModuleOperation.cs +++ b/MainProject/Models/Database/SqlServer/RolePermissionSystemModuleOperation.cs @@ -10,4 +10,4 @@ namespace BasicDotnetTemplate.MainProject.Models.Database.SqlServer public required Role Role { get; set; } public required PermissionSystemModuleOperation PermissionSystemModuleOperation { get; set; } } -} \ No newline at end of file +} diff --git a/MainProject/Models/Database/SqlServer/User.cs b/MainProject/Models/Database/SqlServer/User.cs index 3bb8f97..8d5fff6 100644 --- a/MainProject/Models/Database/SqlServer/User.cs +++ b/MainProject/Models/Database/SqlServer/User.cs @@ -13,7 +13,10 @@ public class User : Base [MaxLength(200)] public required string Email { get; set; } public required string PasswordSalt { get; set; } - public required string PasswordHash { get; set; } +#nullable enable + public string? PasswordPepper { get; set; } +#nullable disable + public required int PasswordIterations { get; set; } public required Role Role { get; set; } public required bool IsTestUser { get; set; } diff --git a/MainProject/Models/Settings/AppSettings.cs b/MainProject/Models/Settings/AppSettings.cs index 3df03e9..b337c1b 100644 --- a/MainProject/Models/Settings/AppSettings.cs +++ b/MainProject/Models/Settings/AppSettings.cs @@ -11,4 +11,4 @@ public class AppSettings public EncryptionSettings? EncryptionSettings { get; set; } public PermissionsSettings? PermissionsSettings { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/DatabaseConnection.cs b/MainProject/Models/Settings/DatabaseConnection.cs index e6d867d..09ba894 100644 --- a/MainProject/Models/Settings/DatabaseConnection.cs +++ b/MainProject/Models/Settings/DatabaseConnection.cs @@ -7,4 +7,4 @@ public class DatabaseConnection public string? Postgres { get; set; } public string? Mongodb { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/DatabaseSettings.cs b/MainProject/Models/Settings/DatabaseSettings.cs index 3ac3be2..ead53f7 100644 --- a/MainProject/Models/Settings/DatabaseSettings.cs +++ b/MainProject/Models/Settings/DatabaseSettings.cs @@ -7,4 +7,4 @@ public class DatabaseSettings public string? MongoDbConnectionString { get; set; } public string? PostgreSQLConnectionString { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/EncryptionSettings.cs b/MainProject/Models/Settings/EncryptionSettings.cs index 8d40260..910d911 100644 --- a/MainProject/Models/Settings/EncryptionSettings.cs +++ b/MainProject/Models/Settings/EncryptionSettings.cs @@ -3,7 +3,8 @@ namespace BasicDotnetTemplate.MainProject.Models.Settings; public class EncryptionSettings { #nullable enable + public string? SaltKey { get; set; } public string? Salt { get; set; } - public string? Pepper { get; set; } + public int? Iterations { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/JwtSettings.cs b/MainProject/Models/Settings/JwtSettings.cs index 23f3690..a578085 100644 --- a/MainProject/Models/Settings/JwtSettings.cs +++ b/MainProject/Models/Settings/JwtSettings.cs @@ -9,4 +9,4 @@ public class JwtSettings public int? ExpiredAfterMinsOfInactivity { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/OpenApiSettings.cs b/MainProject/Models/Settings/OpenApiSettings.cs index 35aa38d..6a909c8 100644 --- a/MainProject/Models/Settings/OpenApiSettings.cs +++ b/MainProject/Models/Settings/OpenApiSettings.cs @@ -7,4 +7,4 @@ public class OpenApiSettings public OpenApiSettingsDetails? OpenApiContact { get; set; } public OpenApiSettingsDetails? OpenApiLicense { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/OpenApiSettingsDetails.cs b/MainProject/Models/Settings/OpenApiSettingsDetails.cs index 08d5d97..dd2ea10 100644 --- a/MainProject/Models/Settings/OpenApiSettingsDetails.cs +++ b/MainProject/Models/Settings/OpenApiSettingsDetails.cs @@ -6,4 +6,4 @@ public class OpenApiSettingsDetails public string? Name { get; set; } public string? Url { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/PermissionsSettings.cs b/MainProject/Models/Settings/PermissionsSettings.cs index 495dc9a..82c485f 100644 --- a/MainProject/Models/Settings/PermissionsSettings.cs +++ b/MainProject/Models/Settings/PermissionsSettings.cs @@ -5,4 +5,4 @@ public class PermissionsSettings #nullable enable public string? FilePath { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/PrivateSettings.cs b/MainProject/Models/Settings/PrivateSettings.cs index 0ad3b8b..ffd9ebf 100644 --- a/MainProject/Models/Settings/PrivateSettings.cs +++ b/MainProject/Models/Settings/PrivateSettings.cs @@ -5,4 +5,4 @@ public class PrivateSettings #nullable enable public DatabaseConnection? DatabaseConnection { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Models/Settings/Settings.cs b/MainProject/Models/Settings/Settings.cs index 2932223..001b510 100644 --- a/MainProject/Models/Settings/Settings.cs +++ b/MainProject/Models/Settings/Settings.cs @@ -7,4 +7,4 @@ public class Settings public string? Version { get; set; } public string? Description { get; set; } #nullable disable -} \ No newline at end of file +} diff --git a/MainProject/Program.cs b/MainProject/Program.cs index e6213b2..b887e91 100644 --- a/MainProject/Program.cs +++ b/MainProject/Program.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using BasicDotnetTemplate.MainProject.Models.Settings; using System.Reflection; using BasicDotnetTemplate.MainProject.Utils; @@ -60,4 +60,4 @@ internal static class Program NLog.LogManager.Shutdown(); // Flush and close down internal threads and timers } -} \ No newline at end of file +} diff --git a/MainProject/Services/PermissionService.cs b/MainProject/Services/PermissionService.cs index 8ab2923..bc695b5 100644 --- a/MainProject/Services/PermissionService.cs +++ b/MainProject/Services/PermissionService.cs @@ -1275,4 +1275,4 @@ public class PermissionService : BaseService, IPermissionService -} \ No newline at end of file +} diff --git a/MainProject/Services/UserService.cs b/MainProject/Services/UserService.cs index 3fe1314..ac6f01b 100644 --- a/MainProject/Services/UserService.cs +++ b/MainProject/Services/UserService.cs @@ -4,6 +4,7 @@ using BasicDotnetTemplate.MainProject.Core.Database; using BasicDotnetTemplate.MainProject.Models.Api.Common.Exceptions; using BasicDotnetTemplate.MainProject.Models.Api.Data.User; using BasicDotnetTemplate.MainProject.Models.Database.SqlServer; +using BasicDotnetTemplate.MainProject.Utils; using Microsoft.EntityFrameworkCore; namespace BasicDotnetTemplate.MainProject.Services; @@ -15,7 +16,10 @@ public interface IUserService Task GetUserByUsernameAndPassword(string email, string password); Task CheckIfEmailIsValid(string email, string? guid = ""); Task CreateUserAsync(CreateUserRequestData data, Role role); + Task UpdateUserAsync(UpdateUserRequestData data, User user); Task DeleteUserAsync(User user); + Task UpdateUserPasswordAsync(User user, string newPassword); + Task UpdateUserRoleAsync(User user, Role newRole); } public class UserService : BaseService, IUserService @@ -42,6 +46,9 @@ public class UserService : BaseService, IUserService private User CreateUserData(CreateUserRequestData data, Role role) { + var salt = _appSettings.EncryptionSettings?.Salt ?? String.Empty; + var pepper = CryptUtils.GeneratePepper(); + var iterations = _appSettings.EncryptionSettings?.Iterations ?? 10; User user = new() { CreationTime = DateTime.UtcNow, @@ -51,9 +58,10 @@ public class UserService : BaseService, IUserService FirstName = data.FirstName, LastName = data.LastName, Email = data.Email, - PasswordSalt = "", - PasswordHash = "", - Password = "", + PasswordSalt = salt, + PasswordPepper = pepper, + PasswordIterations = iterations, + Password = CryptUtils.GeneratePassword(data.Password, salt, iterations, pepper), Role = role, IsTestUser = false }; @@ -77,7 +85,9 @@ public class UserService : BaseService, IUserService User? user = await this.GetUserByEmailQueryable(email).FirstOrDefaultAsync(); if (user != null) { - var encryptedPassword = user.PasswordHash; + var valid = CryptUtils.VerifyPassword(user.Password, password, user.PasswordSalt, user.PasswordIterations, user.PasswordPepper); + if (!valid) + user = null; } return user; @@ -127,6 +137,31 @@ public class UserService : BaseService, IUserService return user; } + public async Task UpdateUserAsync(UpdateUserRequestData data, User user) + { + using var transaction = await _sqlServerContext.Database.BeginTransactionAsync(); + + try + { + user.FirstName = data.FirstName ?? user.FirstName; + user.LastName = data.LastName ?? user.LastName; + user.UpdateTime = DateTime.UtcNow; + user.UpdateUserId = this.GetCurrentUserId(); + + _sqlServerContext.Users.Update(user); + await _sqlServerContext.SaveChangesAsync(); + await transaction.CommitAsync(); + } + catch (Exception exception) + { + Logger.Error(exception, $"[UserService][UpdateUserAsync] | {transaction.TransactionId}"); + await transaction.RollbackAsync(); + throw new UpdateException($"An error occurred while updating the user for transaction ID {transaction.TransactionId}.", exception); + } + + return user; + } + public async Task DeleteUserAsync(User user) { bool? deleted = false; @@ -144,6 +179,59 @@ public class UserService : BaseService, IUserService return deleted; } + public async Task UpdateUserPasswordAsync(User user, string newPassword) + { + using var transaction = await _sqlServerContext.Database.BeginTransactionAsync(); + try + { + var salt = _appSettings.EncryptionSettings?.Salt ?? String.Empty; + var pepper = CryptUtils.GeneratePepper(); + var iterations = _appSettings.EncryptionSettings?.Iterations ?? 10; + + user.PasswordSalt = salt; + user.PasswordPepper = pepper; + user.PasswordIterations = iterations; + user.Password = CryptUtils.GeneratePassword(newPassword, salt, iterations, pepper); + user.UpdateTime = DateTime.UtcNow; + user.UpdateUserId = this.GetCurrentUserId(); + + _sqlServerContext.Users.Update(user); + await _sqlServerContext.SaveChangesAsync(); + await transaction.CommitAsync(); + } + catch (Exception exception) + { + Logger.Error(exception, $"[UserService][UpdateUserPasswordAsync] | {transaction.TransactionId}"); + await transaction.RollbackAsync(); + throw new UpdateException($"An error occurred while updating the user for transaction ID {transaction.TransactionId}.", exception); + } + + return user; + } + + public async Task UpdateUserRoleAsync(User user, Role newRole) + { + using var transaction = await _sqlServerContext.Database.BeginTransactionAsync(); + + try + { + user.Role = newRole; + user.UpdateTime = DateTime.UtcNow; + user.UpdateUserId = this.GetCurrentUserId(); + + _sqlServerContext.Users.Update(user); + await _sqlServerContext.SaveChangesAsync(); + await transaction.CommitAsync(); + } + catch (Exception exception) + { + Logger.Error(exception, $"[UserService][UpdateUserRoleAsync] | {transaction.TransactionId}"); + await transaction.RollbackAsync(); + throw new UpdateException($"An error occurred while updating the user for transaction ID {transaction.TransactionId}.", exception); + } + + return user; + } } diff --git a/MainProject/Utils/CryptoUtils.cs b/MainProject/Utils/CryptUtils.cs similarity index 65% rename from MainProject/Utils/CryptoUtils.cs rename to MainProject/Utils/CryptUtils.cs index cb847fc..259f1e0 100644 --- a/MainProject/Utils/CryptoUtils.cs +++ b/MainProject/Utils/CryptUtils.cs @@ -4,24 +4,17 @@ using System.Text; using BasicDotnetTemplate.MainProject.Models.Settings; namespace BasicDotnetTemplate.MainProject.Utils; -public class CryptUtils +public class CryptUtils(AppSettings appSettings) { - private readonly string _secretKey; - private readonly string _pepper; + private readonly string _saltKey = appSettings.EncryptionSettings?.SaltKey ?? String.Empty; private const int _M = 16; private const int _N = 32; - public CryptUtils(AppSettings appSettings) - { - _secretKey = appSettings.EncryptionSettings?.Salt ?? String.Empty; - _pepper = appSettings.EncryptionSettings?.Pepper ?? String.Empty; - } - public string Decrypt(string encryptedData) { var decrypted = String.Empty; - if (String.IsNullOrEmpty(this._secretKey) || this._secretKey.Length < _M) + if (String.IsNullOrEmpty(this._saltKey) || this._saltKey.Length < _M) { throw new ArgumentException("Unable to proceed with decryption due to invalid settings"); } @@ -35,7 +28,7 @@ public class CryptUtils using (var aes = Aes.Create()) { - aes.Key = Encoding.UTF8.GetBytes(this._secretKey); + aes.Key = Encoding.UTF8.GetBytes(this._saltKey); aes.IV = Encoding.UTF8.GetBytes(iv); using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) @@ -57,21 +50,21 @@ public class CryptUtils return decrypted; } - public static string GenerateSalt() + public static string GeneratePepper() { using var rng = RandomNumberGenerator.Create(); - var byteSalt = new byte[16]; - rng.GetBytes(byteSalt); - var salt = Convert.ToBase64String(byteSalt); - return salt; + var bytePepper = new byte[16]; + rng.GetBytes(bytePepper); + var pepper = Convert.ToBase64String(bytePepper); + return pepper; } - public string GeneratePassword(string password, string salt, int iteration) + public static string GeneratePassword(string password, string salt, int iterations, string? pepper = "") { string hashedPassword = password; - for(var i = 0; i <= iteration; i++) + for (var i = 0; i <= iterations; i++) { - var passwordSaltPepper = $"{hashedPassword}{salt}{this._pepper}"; + var passwordSaltPepper = $"{hashedPassword}{salt}{pepper}"; var byteValue = Encoding.UTF8.GetBytes(passwordSaltPepper); var byteHash = SHA256.HashData(byteValue); hashedPassword = Convert.ToBase64String(byteHash); @@ -80,9 +73,9 @@ public class CryptUtils return hashedPassword; } - public bool VerifyPassword(string password, string salt, int iteration, string userPassword) + public static bool VerifyPassword(string userPassword, string password, string salt, int iterations, string? pepper = "") { - string hashedPassword = this.GeneratePassword(password, salt, iteration); + string hashedPassword = GeneratePassword(password, salt, iterations, pepper); return hashedPassword.Equals(userPassword, StringComparison.OrdinalIgnoreCase); } diff --git a/MainProject/Utils/FileUtils.cs b/MainProject/Utils/FileUtils.cs index 5c7be6e..6c84819 100644 --- a/MainProject/Utils/FileUtils.cs +++ b/MainProject/Utils/FileUtils.cs @@ -39,4 +39,4 @@ public static class FileUtils } } -} \ No newline at end of file +} diff --git a/MainProject/Utils/PasswordUtils.cs b/MainProject/Utils/PasswordUtils.cs new file mode 100644 index 0000000..f61ad80 --- /dev/null +++ b/MainProject/Utils/PasswordUtils.cs @@ -0,0 +1,64 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using BasicDotnetTemplate.MainProject.Enum; +using BasicDotnetTemplate.MainProject.Models.Settings; + +namespace BasicDotnetTemplate.MainProject.Utils; + +public static partial class PasswordUtils +{ + private const int MIN_LENGTH = 8; + private const int MIN_UPPER = 2; + private const int MIN_LOWER = 2; + private const int MIN_NUMBER = 2; + private const int MIN_SPECIAL = 2; + + [GeneratedRegex("[A-Z]")] + private static partial Regex RegexUpper(); + + [GeneratedRegex("[a-z]")] + private static partial Regex RegexLower(); + + [GeneratedRegex("[0-9]")] + private static partial Regex RegexNumber(); + + [GeneratedRegex("[^a-zA-Z0-9]")] + private static partial Regex RegexSpecial(); + + private static readonly Regex RegexIdenticalChars = new( + @"(\S)\1{2,}", + RegexOptions.IgnoreCase | RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100) + ); + + public static List ValidatePassword(string password) + { + List errors = []; + + if (password.Length < MIN_LENGTH) + errors.Add(PasswordValidationEnum.MIN_LENGTH); + + if (RegexUpper().Matches(password).Count < MIN_UPPER) + errors.Add(PasswordValidationEnum.MIN_UPPER); + + if (RegexLower().Matches(password).Count < MIN_LOWER) + errors.Add(PasswordValidationEnum.MIN_LOWER); + + if (RegexNumber().Matches(password).Count < MIN_NUMBER) + errors.Add(PasswordValidationEnum.MIN_NUMBER); + + if (RegexSpecial().Matches(password).Count < MIN_SPECIAL) + errors.Add(PasswordValidationEnum.MIN_SPECIAL); + + if (RegexIdenticalChars.IsMatch(password)) + errors.Add(PasswordValidationEnum.IDENTICAL_CHARS); + + return errors; + } + + +} + diff --git a/MainProject/Utils/ProgramUtils.cs b/MainProject/Utils/ProgramUtils.cs index eaa8c37..b2cd6a0 100644 --- a/MainProject/Utils/ProgramUtils.cs +++ b/MainProject/Utils/ProgramUtils.cs @@ -8,7 +8,7 @@ using BasicDotnetTemplate.MainProject.Models.Settings; using BasicDotnetTemplate.MainProject.Services; using BasicDotnetTemplate.MainProject.Models.Api.Data.Role; using BasicDotnetTemplate.MainProject.Models.Database.SqlServer; - +using BasicDotnetTemplate.MainProject.Core.Filters; namespace BasicDotnetTemplate.MainProject.Utils; @@ -140,7 +140,10 @@ public static class ProgramUtils builder.Services.AddAuthentication(); builder.Services.AddAuthorization(); - builder.Services.AddControllers(); + builder.Services.AddControllers(options => + { + options.Filters.Add(); + }); builder.Services.AddEndpointsApiExplorer(); Logger.Info("[ProgramUtils][AddServices] Done services"); @@ -292,4 +295,4 @@ public static class ProgramUtils } } -} \ No newline at end of file +} diff --git a/MainProject/appsettings.json b/MainProject/appsettings.json index 8893bf2..274bdd2 100644 --- a/MainProject/appsettings.json +++ b/MainProject/appsettings.json @@ -35,8 +35,9 @@ "ExpiredAfterMinsOfInactivity": 15 }, "EncryptionSettings": { - "Salt": "S7VIidfXQf1tOQYX", - "Pepper": "" + "SaltKey": "S7VIidfXQf1tOQYX", + "Salt": "u5CZAwq9vLGysC", + "Iterations": 10 }, "PermissionsSettings": { "FilePath": "Config/permissions.json"