diff --git a/Govor.API.Tests/Govor.API.Tests.csproj b/Govor.API.Tests/Govor.API.Tests.csproj index 50de971..3a4ca0b 100644 --- a/Govor.API.Tests/Govor.API.Tests.csproj +++ b/Govor.API.Tests/Govor.API.Tests.csproj @@ -26,10 +26,10 @@ - + diff --git a/Govor.API.Tests/UnitTests/Services/PasswordHasherTests.cs b/Govor.API.Tests/UnitTests/Services/PasswordHasherTests.cs new file mode 100644 index 0000000..4326528 --- /dev/null +++ b/Govor.API.Tests/UnitTests/Services/PasswordHasherTests.cs @@ -0,0 +1,51 @@ +using AutoFixture; +using Govor.API.Services; +using Govor.Core.Infrastructure.Extensions; + +namespace Govor.API.Tests.UnitTests.Services; + +[TestFixture] +public class PasswordHasherTests +{ + private IPasswordHasher _passwordHasher; + private Fixture _fixture; + + [SetUp] + public void StarUp() + { + _fixture = new Fixture(); + _passwordHasher = new PasswordHasher(); + } + + [Test] + public void Given_Password_When_Hash_Then_Hash_And_Verify_Then_Result_Should_Be_True() + { + // Arrange + string password = _fixture.Create(); + + // Act + string hash = _passwordHasher.Hash(password); + + var result = _passwordHasher.Verify(password, hash); + + // Assert + Assert.That(hash, Is.Not.EqualTo(password)); + Assert.That(result, Is.True); + } + + [Test] + public void Given_Password_NotPassword_When_Hash_Should_Not_Be_True_Then_Result_Should_Be_False() + { + // Arrange + string password = _fixture.Create(); + string notPassword = _fixture.Create(); + + // Act + string hash = _passwordHasher.Hash(password); + + var result = _passwordHasher.Verify(notPassword, hash); + + // Assert + Assert.That(result, Is.False); + } +} \ No newline at end of file diff --git a/Govor.API/Controllers/AuthController.cs b/Govor.API/Controllers/AuthController.cs new file mode 100644 index 0000000..a72284e --- /dev/null +++ b/Govor.API/Controllers/AuthController.cs @@ -0,0 +1,72 @@ +using Govor.API.Services.Authentication; +using Govor.Core.DTOs; +using Govor.Core.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Govor.API.Controllers; + +[ApiController] +[Route("[controller]")] +public class AuthController : Controller +{ + private IAccountService _accountService; + private ILogger _logger; + + public AuthController(IAccountService accountService, ILogger logger) + { + _accountService = accountService; + _logger = logger; + } + + [HttpPost("register")] + [RequireHttps] + public async Task Register([FromBody] UserDto userDto) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + await _accountService.RegistrationAsync(userDto.Name, userDto.Password); + return Ok(new { Message = "User registered successfully" }); + } + catch (UserAlreadyExistException ex) + { + _logger.LogWarning(ex, $"Registration failed for user {userDto.Name}"); + return BadRequest("Registration failed: user already exists."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error during registration for user {Name}", userDto.Name); + return StatusCode(500, "An unexpected error occurred. Please try again later."); + } + } + + [HttpPost("login")] + [RequireHttps] + public async Task Login([FromBody] UserDto userDto) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + await _accountService.LoginAsync(userDto.Name, userDto.Password); + return Ok(new { Message = "User login successfully" }); + } + catch (UserNotRegisteredException ex) + { + _logger.LogWarning(ex, "Login failed for user {Name}", userDto.Name); + return BadRequest("Login failed: user does not exist."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error during login for user {Name}", userDto.Name); + return StatusCode(500, "An unexpected error occurred. Please try again later."); + } + } +} \ No newline at end of file diff --git a/Govor.API/Controllers/InviteController.cs b/Govor.API/Controllers/InviteController.cs new file mode 100644 index 0000000..f56d75e --- /dev/null +++ b/Govor.API/Controllers/InviteController.cs @@ -0,0 +1,31 @@ +using Govor.Core.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Govor.API.Controllers; + +[ApiController] +[Route("invite")] +public class InviteController : ControllerBase +{ + private readonly IGroupService _groupService; + + public InviteController(IGroupService groupService) + { + _groupService = groupService; + } + + [HttpGet("{code}")] + public IActionResult JoinGroup(string code) + { + var group = _groupService.GetGroupByInvite(code); + if (group == null) return NotFound(); + + // Вернуть инфу, которая откроется в MAUI-клиенте + return Ok(new + { + groupId = group.Id, + name = group.Name, + isChannel = group.IsChannel + }); + } +} diff --git a/Govor.API/Govor.API.csproj b/Govor.API/Govor.API.csproj index 9c0771d..96dd6da 100644 --- a/Govor.API/Govor.API.csproj +++ b/Govor.API/Govor.API.csproj @@ -7,6 +7,7 @@ + @@ -21,9 +22,4 @@ - - - - - diff --git a/Govor.API/Services/Authentication/AuthService.cs b/Govor.API/Services/Authentication/AuthService.cs new file mode 100644 index 0000000..ea7bdf6 --- /dev/null +++ b/Govor.API/Services/Authentication/AuthService.cs @@ -0,0 +1,33 @@ +using Govor.Core.Infrastructure.Extensions; +using Govor.Core.Repositories; +using Govor.Core.Services; + +namespace Govor.API.Services.Authentication; + +public class AuthService : IAccountService +{ + private readonly IPasswordHasher _passwordHasher; + private readonly IUsersRepository _usersRepository; + + public AuthService(IUsersRepository usersRepository, IPasswordHasher passwordHasher) + { + _usersRepository = usersRepository; + _passwordHasher = passwordHasher; + } + public Task RegistrationAsync(string name, string password) + { + throw new NotImplementedException(); + } + + public async Task LoginAsync(string name, string password) + { + if (await _usersRepository.ExistsUsernameAsync(name) == false) + throw new UserNotRegisteredException(name); + + throw new NotImplementedException(); + } +} + +public class UserAlreadyExistException(string username) : Exception($"{username} is already exists!") { } + +public class UserNotRegisteredException(string username) : Exception($"{username} is not registered!") { } \ No newline at end of file diff --git a/Govor.API/Services/PasswordHasher.cs b/Govor.API/Services/PasswordHasher.cs new file mode 100644 index 0000000..4976236 --- /dev/null +++ b/Govor.API/Services/PasswordHasher.cs @@ -0,0 +1,16 @@ +using Govor.Core.Infrastructure.Extensions; + +namespace Govor.API.Services; + +public class PasswordHasher : IPasswordHasher +{ + public string Hash(string password) + { + return BCrypt.Net.BCrypt.HashPassword(password); + } + + public bool Verify(string hashedPassword, string providedPassword) + { + return BCrypt.Net.BCrypt.Verify(hashedPassword, providedPassword); + } +} \ No newline at end of file diff --git a/Govor.Core/DTOs/UserDto.cs b/Govor.Core/DTOs/UserDto.cs new file mode 100644 index 0000000..7a5032d --- /dev/null +++ b/Govor.Core/DTOs/UserDto.cs @@ -0,0 +1,3 @@ +namespace Govor.Core.DTOs; + +public record UserDto(string Password, string Name); \ No newline at end of file diff --git a/Govor.Core/Govor.Core.csproj b/Govor.Core/Govor.Core.csproj index 71fa877..1f027dd 100644 --- a/Govor.Core/Govor.Core.csproj +++ b/Govor.Core/Govor.Core.csproj @@ -6,10 +6,6 @@ enable - - - - diff --git a/Govor.Core/Infrastructure/Extensions/IPasswordHasher.cs b/Govor.Core/Infrastructure/Extensions/IPasswordHasher.cs new file mode 100644 index 0000000..c6253c9 --- /dev/null +++ b/Govor.Core/Infrastructure/Extensions/IPasswordHasher.cs @@ -0,0 +1,7 @@ +namespace Govor.Core.Infrastructure.Extensions; + +public interface IPasswordHasher +{ + string Hash(string password); + bool Verify(string hashedPassword, string providedPassword); +} \ No newline at end of file diff --git a/Govor.Core/Services/IAuthService.cs b/Govor.Core/Services/IAuthService.cs new file mode 100644 index 0000000..25d3697 --- /dev/null +++ b/Govor.Core/Services/IAuthService.cs @@ -0,0 +1,7 @@ +namespace Govor.Core.Services; + +public interface IAccountService +{ + public Task RegistrationAsync(string name, string password); + public Task LoginAsync(string name, string password); +} \ No newline at end of file diff --git a/Govor.Core/Services/IGroupService.cs b/Govor.Core/Services/IGroupService.cs new file mode 100644 index 0000000..d3a5d42 --- /dev/null +++ b/Govor.Core/Services/IGroupService.cs @@ -0,0 +1,8 @@ +using Govor.Core.Models; + +namespace Govor.Core.Services; + +public interface IGroupService +{ + ChatGroup GetGroupByInvite(string code); +} \ No newline at end of file