From 67743114333825d5ff5c6307d82f7977c2bc1afa Mon Sep 17 00:00:00 2001 From: Piotr Kus Date: Fri, 21 Feb 2025 09:59:50 +0100 Subject: [PATCH] * Added Authentication --- FaKrosnoApi/Controllers/UsersController.cs | 46 +++++ FaKrosnoApi/FaKrosnoApi.csproj | 1 + .../Models/AuthenticateRequestModel.cs | 7 + OrdersManagement/Components/App.razor | 20 ++- .../Pages/Admin/Users/LoginModule.razor | 165 ++++++++++++++++++ .../Admin/Users/RegistrationModule.razor | 87 +++++++++ .../Admin/{ => Users}/UsersManager.razor | 4 +- .../Components/Pages/EdiCustomerOrders.razor | 15 +- .../Components/Pages/ScheduleOrders.razor | 7 +- .../CustomAuthenticationStateProvider.cs | 29 +++ .../Models/ChangePasswordModel.cs | 14 ++ OrdersManagement/Models/LoginModel.cs | 12 ++ OrdersManagement/Models/RegisterModel.cs | 21 +++ .../ResponseModel.cs} | 4 +- OrdersManagement/OrdersManagement.csproj | 1 + OrdersManagement/Program.cs | 32 +++- .../Services/EdiCustomerOrderService.cs | 8 +- OrdersManagement/Services/UserService.cs | 10 +- .../Services/IUserService.cs | 1 + .../Services/UserService.cs | 14 ++ 20 files changed, 468 insertions(+), 30 deletions(-) create mode 100644 FaKrosnoApi/Models/AuthenticateRequestModel.cs create mode 100644 OrdersManagement/Components/Pages/Admin/Users/LoginModule.razor create mode 100644 OrdersManagement/Components/Pages/Admin/Users/RegistrationModule.razor rename OrdersManagement/Components/Pages/Admin/{ => Users}/UsersManager.razor (98%) create mode 100644 OrdersManagement/CustomAuthenticationStateProvider.cs create mode 100644 OrdersManagement/Models/ChangePasswordModel.cs create mode 100644 OrdersManagement/Models/LoginModel.cs create mode 100644 OrdersManagement/Models/RegisterModel.cs rename OrdersManagement/{Dtos/ResponseDto.cs => Models/ResponseModel.cs} (64%) diff --git a/FaKrosnoApi/Controllers/UsersController.cs b/FaKrosnoApi/Controllers/UsersController.cs index 9283ebb..1c152fd 100644 --- a/FaKrosnoApi/Controllers/UsersController.cs +++ b/FaKrosnoApi/Controllers/UsersController.cs @@ -1,3 +1,4 @@ +using FaKrosnoApi.Models; using Microsoft.AspNetCore.Mvc; using OrdersManagementDataModel.Dtos; using OrdersManagementDataModel.Services; @@ -28,6 +29,51 @@ public class UsersController(IUserService service) : Controller UserDto? user = await service.GetByUsername(username); return user != null ? Ok(user) : NotFound(); } + + [HttpPost("authenticate")] + public async Task Authenticate([FromBody] AuthenticateRequestModel? request) + { + if (request == null || string.IsNullOrEmpty(request.Login) || string.IsNullOrEmpty(request.Password)) + { + return BadRequest(new { message = "Login i hasło są wymagane" }); + } + + var user = await service.GetByUsername(request.Login); + + var x = BCrypt.Net.BCrypt.Verify(request.Password, user?.PasswordHash); + + if (user == null || !BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash)) + { + return Unauthorized(new { message = "Nieprawidłowy login lub hasło" }); + } + + var userDto = new UserDto + { + Id = user.Id, + Login = user.Login, + IsTemporaryPassword = user.IsTemporaryPassword, + IsActive = user.IsActive, + ActiveFrom = user.ActiveFrom, + ActiveTo = user.ActiveTo, + Email = user.Email, + FirstName = user.FirstName, + LastName = user.LastName, + CreatedDate = user.CreatedDate, + LastLoginDate = user.LastLoginDate, + FailedLoginAttempts = user.FailedLoginAttempts, + IsLocked = user.IsLocked, + LockoutEndDate = user.LockoutEndDate, + RowPointer = user.RowPointer + }; + + user.LastLoginDate = DateTime.Now; + user.FailedLoginAttempts = 0; + await service.Login(user); + + return Ok(userDto); + } + + [HttpPost] public async Task> Add([FromBody] UserDto user) diff --git a/FaKrosnoApi/FaKrosnoApi.csproj b/FaKrosnoApi/FaKrosnoApi.csproj index 418c3ca..595a074 100644 --- a/FaKrosnoApi/FaKrosnoApi.csproj +++ b/FaKrosnoApi/FaKrosnoApi.csproj @@ -7,6 +7,7 @@ + diff --git a/FaKrosnoApi/Models/AuthenticateRequestModel.cs b/FaKrosnoApi/Models/AuthenticateRequestModel.cs new file mode 100644 index 0000000..7b0cc38 --- /dev/null +++ b/FaKrosnoApi/Models/AuthenticateRequestModel.cs @@ -0,0 +1,7 @@ +namespace FaKrosnoApi.Models; + +public class AuthenticateRequestModel +{ + public string Login { get; set; } + public string Password { get; set; } +} \ No newline at end of file diff --git a/OrdersManagement/Components/App.razor b/OrdersManagement/Components/App.razor index bf51599..290d777 100644 --- a/OrdersManagement/Components/App.razor +++ b/OrdersManagement/Components/App.razor @@ -1,4 +1,5 @@ - +@using Microsoft.AspNetCore.Components.Authorization + @@ -17,16 +18,27 @@ + @inject AuthenticationStateProvider AuthenticationStateProvider + @inject NavigationManager NavigationManager + + @{ + var authState = AuthenticationStateProvider.GetAuthenticationStateAsync().Result; + var user = authState.User; + + if (!(user.Identity is { IsAuthenticated: true }) && NavigationManager.Uri != NavigationManager.BaseUri + "login") + { + NavigationManager.NavigateTo("/login"); + } + } + - - - + \ No newline at end of file diff --git a/OrdersManagement/Components/Pages/Admin/Users/LoginModule.razor b/OrdersManagement/Components/Pages/Admin/Users/LoginModule.razor new file mode 100644 index 0000000..a78e87c --- /dev/null +++ b/OrdersManagement/Components/Pages/Admin/Users/LoginModule.razor @@ -0,0 +1,165 @@ +@page "/login" +@using Microsoft.AspNetCore.Components.Authorization +@using OrdersManagement.Models +@using Syncfusion.Blazor.Inputs +@using Syncfusion.Blazor.Buttons +@using Syncfusion.Blazor.Cards +@inject UserService UserService +@inject NavigationManager NavigationManager +@inject AuthenticationStateProvider AuthenticationStateProvider + +
+
Logowanie
+ + @if (!string.IsNullOrEmpty(TempPassword)) + { +
+ Twoje tymczasowe hasło to: @TempPassword. Użyj go do pierwszego logowania. +
+ } + + + + + + + + +
+ + + +
+ +
+ + + +
+ +
+ Zaloguj +
+ + @if (!string.IsNullOrEmpty(ErrorMessage)) + { +
@ErrorMessage
+ } +
+ + @if (ShowChangePassword) + { +
+
Zmień hasło
+ + + + +
+ + + +
+ +
+ + + +
+ +
+ Zmień hasło +
+
+ } +
+
+ +
+ +@code { + private LoginModel LoginModel { get; set; } = new(); + private ChangePasswordModel ChangePasswordModel { get; set; } = new(); + private bool ShowChangePassword { get; set; } + private string TempPassword { get; set; } + private string ErrorMessage { get; set; } + + protected override void OnInitialized() + { + TempPassword = NavigationManager.Uri.Split('?').Length > 1 ? Uri.UnescapeDataString(NavigationManager.Uri.Split('=')[1]) : null; + } + + private async Task HandleLogin() + { + try + { + Console.WriteLine($"Próba logowania dla: {LoginModel.Login}"); + var user = await UserService.AuthenticateUserAsync(LoginModel.Login, LoginModel.Password); + if (user != null) + { + Console.WriteLine($"Użytkownik {user.Login} znaleziony."); + if (user.IsTemporaryPassword) + { + ShowChangePassword = true; + StateHasChanged(); // Wymagane, aby odświeżyć UI + } + else + { + await ((CustomAuthenticationStateProvider)AuthenticationStateProvider).MarkUserAsAuthenticated(user); + NavigationManager.NavigateTo("/"); + } + } + else + { + ErrorMessage = "Nieprawidłowy login lub hasło"; + Console.WriteLine(ErrorMessage); + StateHasChanged(); + } + } + catch (Exception ex) + { + ErrorMessage = $"Błąd logowania: {ex.Message}"; + Console.WriteLine(ErrorMessage); + StateHasChanged(); + } + } + + private async Task HandleChangePassword() + { + try + { + if (ChangePasswordModel.NewPassword == ChangePasswordModel.ConfirmPassword) + { + var user = await UserService.GetUserByUsernameAsync(LoginModel.Login); + if (user != null) + { + user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(ChangePasswordModel.NewPassword); + user.IsTemporaryPassword = false; + await UserService.UpdateUserAsync(user); + ShowChangePassword = false; + LoginModel = new LoginModel(); + StateHasChanged(); + NavigationManager.NavigateTo("/login"); + } + } + else + { + ErrorMessage = "Hasła nie są zgodne"; + StateHasChanged(); + } + } + catch (Exception ex) + { + ErrorMessage = $"Błąd zmiany hasła: {ex.Message}"; + StateHasChanged(); + } + } + +} diff --git a/OrdersManagement/Components/Pages/Admin/Users/RegistrationModule.razor b/OrdersManagement/Components/Pages/Admin/Users/RegistrationModule.razor new file mode 100644 index 0000000..56cd434 --- /dev/null +++ b/OrdersManagement/Components/Pages/Admin/Users/RegistrationModule.razor @@ -0,0 +1,87 @@ +@page "/register" +@using OrdersManagement.Models +@using OrdersManagementDataModel.Dtos +@using Syncfusion.Blazor.Inputs +@using Syncfusion.Blazor.Buttons +@using Syncfusion.Blazor.Cards + +@inject UserService UserService +@inject NavigationManager NavigationManager + +
+
Rejestracja
+
+ + + + + + + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ Zarejestruj +
+
+
+
+
+ +@code { + private RegisterModel RegisterModel { get; set; } = new(); + + private async Task HandleRegister() + { + var temporaryPassword = Guid.NewGuid().ToString().Substring(0, 8); + var passwordHash = BCrypt.Net.BCrypt.HashPassword(temporaryPassword); + + var user = new UserDto + { + Login = RegisterModel.Login, + PasswordHash = passwordHash, + IsTemporaryPassword = true, + IsActive = true, + ActiveFrom = DateTime.Now, + Email = RegisterModel.Email, + FirstName = RegisterModel.FirstName, + LastName = RegisterModel.LastName, + CreatedDate = DateTime.Now, + RowPointer = Guid.NewGuid() + }; + + var result = await UserService.AddUserAsync(user); + + if (result.IsSuccessStatusCode) + { + NavigationManager.NavigateTo($"/login?tempPassword={Uri.EscapeDataString(temporaryPassword)}"); + } + } + +} \ No newline at end of file diff --git a/OrdersManagement/Components/Pages/Admin/UsersManager.razor b/OrdersManagement/Components/Pages/Admin/Users/UsersManager.razor similarity index 98% rename from OrdersManagement/Components/Pages/Admin/UsersManager.razor rename to OrdersManagement/Components/Pages/Admin/Users/UsersManager.razor index 8e4c520..24ed19e 100644 --- a/OrdersManagement/Components/Pages/Admin/UsersManager.razor +++ b/OrdersManagement/Components/Pages/Admin/Users/UsersManager.razor @@ -21,8 +21,7 @@ - + §
@@ -169,5 +168,4 @@ break; } } - } \ No newline at end of file diff --git a/OrdersManagement/Components/Pages/EdiCustomerOrders.razor b/OrdersManagement/Components/Pages/EdiCustomerOrders.razor index 52aaea3..4633c99 100644 --- a/OrdersManagement/Components/Pages/EdiCustomerOrders.razor +++ b/OrdersManagement/Components/Pages/EdiCustomerOrders.razor @@ -1,8 +1,11 @@ @page "/EdiCustomerOrders" +@attribute [Authorize] + @inject EdiCustomerOrderService EdiCustomerOrderService @inject NavigationManager NavigationManager -@using OrdersManagement.Dtos +@using Microsoft.AspNetCore.Authorization +@using OrdersManagement.Models @using SytelineSaAppEfDataModel.Dtos @using Syncfusion.Blazor.Grids @using Syncfusion.Blazor.Cards @@ -105,7 +108,7 @@ @if (_responses.Any(x => x.Status == 1)) { - foreach (ResponseDto response in _responses.Where(x => x.Status == 1)) + foreach (ResponseModel response in _responses.Where(x => x.Status == 1)) {

Zamówienie EDI @response.Identifier zostało poprawnie zaksięgowane w Zamówieniach klienta pod numerem '@response.ExternalIdentifier'

@@ -113,7 +116,7 @@ } @if (_responses.Any(x => x.Status == 0)) { - foreach (ResponseDto response in _responses.Where(x => x.Status == 0)) + foreach (ResponseModel response in _responses.Where(x => x.Status == 0)) {

Błąd: Zamówienie EDI @response.Identifier nie zostało poprawnie zaksięgowane w Zamówieniach klienta.
Lista błędów:
@response.Message

@@ -135,7 +138,7 @@ private IEnumerable _ediCustomerOrders = []; private List _selectedEdiCustomerOrders = new(); - private List _responses = new(); + private List _responses = new(); private bool _isVisible; private bool? _filter = false; @@ -158,11 +161,11 @@ { if (!_selectedEdiCustomerOrders.Any()) return false; - _responses = new List(); + _responses = new List(); foreach (EdiCustomerOrderDto selectedEdiCustomerOrder in _selectedEdiCustomerOrders) { - ResponseDto response = await EdiCustomerOrderService.SendOrderToSyteline(selectedEdiCustomerOrder.RowPointer, selectedEdiCustomerOrder.CustomerOrderNumber); + ResponseModel response = await EdiCustomerOrderService.SendOrderToSyteline(selectedEdiCustomerOrder.RowPointer, selectedEdiCustomerOrder.CustomerOrderNumber); if (response.Status == 1) { diff --git a/OrdersManagement/Components/Pages/ScheduleOrders.razor b/OrdersManagement/Components/Pages/ScheduleOrders.razor index 293fdb2..a628b44 100644 --- a/OrdersManagement/Components/Pages/ScheduleOrders.razor +++ b/OrdersManagement/Components/Pages/ScheduleOrders.razor @@ -1,9 +1,14 @@ @page "/" +@attribute [Authorize] + +@inherits LayoutComponentBase + +@using Microsoft.AspNetCore.Authorization @using OrdersManagement.Components.Pages.Shared @using Syncfusion.Blazor.Grids + @inject ScheduleOrderService ScheduleOrderService -@inherits LayoutComponentBase
Zamówienia DELFOR
diff --git a/OrdersManagement/CustomAuthenticationStateProvider.cs b/OrdersManagement/CustomAuthenticationStateProvider.cs new file mode 100644 index 0000000..e50c952 --- /dev/null +++ b/OrdersManagement/CustomAuthenticationStateProvider.cs @@ -0,0 +1,29 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; +using OrdersManagementDataModel.Dtos; + +namespace OrdersManagement; + +public class CustomAuthenticationStateProvider : AuthenticationStateProvider +{ + private UserDto? _currentUser; + + public override Task GetAuthenticationStateAsync() + { + var identity = _currentUser != null ? new ClaimsIdentity([new Claim(ClaimTypes.Name, _currentUser.Login)], "CustomAuth") : new ClaimsIdentity(); + return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(identity))); + } + + public Task MarkUserAsAuthenticated(UserDto? user) + { + _currentUser = user; + NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); + return Task.CompletedTask; + } + + public void MarkUserAsLoggedOut() + { + _currentUser = null; + NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); + } +} diff --git a/OrdersManagement/Models/ChangePasswordModel.cs b/OrdersManagement/Models/ChangePasswordModel.cs new file mode 100644 index 0000000..302ab93 --- /dev/null +++ b/OrdersManagement/Models/ChangePasswordModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace OrdersManagement.Models; + +public class ChangePasswordModel +{ + [Required(ErrorMessage = "Nowe hasło jest wymagane")] + [StringLength(100, MinimumLength = 6, ErrorMessage = "Hasło musi mieć od 6 do 100 znaków")] + public string NewPassword { get; set; } + + [Required(ErrorMessage = "Potwierdzenie hasła jest wymagane")] + [Compare("NewPassword", ErrorMessage = "Hasła muszą się zgadzać")] + public string ConfirmPassword { get; set; } +} \ No newline at end of file diff --git a/OrdersManagement/Models/LoginModel.cs b/OrdersManagement/Models/LoginModel.cs new file mode 100644 index 0000000..995f690 --- /dev/null +++ b/OrdersManagement/Models/LoginModel.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace OrdersManagement.Models; + +public class LoginModel +{ + [Required(ErrorMessage = "Login jest wymagany")] + public string Login { get; set; } + + [Required(ErrorMessage = "Hasło jest wymagane")] + public string Password { get; set; } +} diff --git a/OrdersManagement/Models/RegisterModel.cs b/OrdersManagement/Models/RegisterModel.cs new file mode 100644 index 0000000..4568c1b --- /dev/null +++ b/OrdersManagement/Models/RegisterModel.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace OrdersManagement.Models; + +public class RegisterModel +{ + [Required(ErrorMessage = "Login jest wymagany")] + [StringLength(50, ErrorMessage = "Login może mieć maksymalnie 50 znaków")] + public string Login { get; set; } + + [Required(ErrorMessage = "Email jest wymagany")] + [EmailAddress(ErrorMessage = "Nieprawidłowy format email")] + [StringLength(100, ErrorMessage = "Email może mieć maksymalnie 100 znaków")] + public string Email { get; set; } + + [StringLength(50, ErrorMessage = "Imię może mieć maksymalnie 50 znaków")] + public string FirstName { get; set; } + + [StringLength(50, ErrorMessage = "Nazwisko może mieć maksymalnie 50 znaków")] + public string LastName { get; set; } +} \ No newline at end of file diff --git a/OrdersManagement/Dtos/ResponseDto.cs b/OrdersManagement/Models/ResponseModel.cs similarity index 64% rename from OrdersManagement/Dtos/ResponseDto.cs rename to OrdersManagement/Models/ResponseModel.cs index b756a20..5cf0c83 100644 --- a/OrdersManagement/Dtos/ResponseDto.cs +++ b/OrdersManagement/Models/ResponseModel.cs @@ -1,6 +1,6 @@ -namespace OrdersManagement.Dtos +namespace OrdersManagement.Models { - public class ResponseDto(int status, string identifier, string? message, string? externalIdentifier) + public class ResponseModel(int status, string identifier, string? message, string? externalIdentifier) { public int Status { get; set; } = status; public string Identifier { get; set; } = identifier; diff --git a/OrdersManagement/OrdersManagement.csproj b/OrdersManagement/OrdersManagement.csproj index 234e103..44aa5a3 100644 --- a/OrdersManagement/OrdersManagement.csproj +++ b/OrdersManagement/OrdersManagement.csproj @@ -7,6 +7,7 @@ + diff --git a/OrdersManagement/Program.cs b/OrdersManagement/Program.cs index 3427f39..ea58e38 100644 --- a/OrdersManagement/Program.cs +++ b/OrdersManagement/Program.cs @@ -1,26 +1,38 @@ -using Microsoft.AspNetCore.Routing.Constraints; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Components.Authorization; +using OrdersManagement; using OrdersManagement.Components; using OrdersManagement.Services; using Syncfusion.Blazor; var builder = WebApplication.CreateBuilder(args); +string faKrosnoApiUrl = builder.Configuration["FaKrosnoApiUrl"] ?? "http://localhost:5001"; + + builder.Services.AddSyncfusionBlazor(); builder.Services.AddBlazorBootstrap(); -string faKrosnoApiUrl = builder.Configuration["FaKrosnoApiUrl"] ?? "http://localhost:5001"; +builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.LoginPath = "/login"; + options.LogoutPath = "/login"; + }); + +builder.Services.AddAuthorizationCore(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(faKrosnoApiUrl) }); -// Add services to the container. -builder.Services.AddRazorComponents() - .AddInteractiveServerComponents(); +builder.Services.AddRazorComponents().AddInteractiveServerComponents(); +// Usługi aplikacji builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -30,13 +42,17 @@ var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); - app.UseStaticFiles(); +app.UseRouting(); + +// Włącz autentykację i autoryzację w pipeline +app.UseAuthentication(); +app.UseAuthorization(); + app.UseAntiforgery(); app.MapRazorComponents() diff --git a/OrdersManagement/Services/EdiCustomerOrderService.cs b/OrdersManagement/Services/EdiCustomerOrderService.cs index a0f5e21..60811ab 100644 --- a/OrdersManagement/Services/EdiCustomerOrderService.cs +++ b/OrdersManagement/Services/EdiCustomerOrderService.cs @@ -1,4 +1,4 @@ -using OrdersManagement.Dtos; +using OrdersManagement.Models; using SytelineSaAppEfDataModel.Dtos; namespace OrdersManagement.Services @@ -16,14 +16,14 @@ namespace OrdersManagement.Services $"api/EdiCustomerOrders/by-order-number/?customerOrderNumber={customerOrderNumber}"); } - public async Task SendOrderToSyteline(Guid customerOrderNumber, string orderNumber) + public async Task SendOrderToSyteline(Guid customerOrderNumber, string orderNumber) { HttpResponseMessage responseMessage = await httpClient.PostAsync( $"api/EdiCustomerOrders/send-to-syteline?customerOrderNumber={customerOrderNumber}", null); if (responseMessage.IsSuccessStatusCode) { - return new ResponseDto(1, orderNumber, null, null); + return new ResponseModel(1, orderNumber, null, null); } string? errorMessage = null; @@ -35,7 +35,7 @@ namespace OrdersManagement.Services errorMessage = string.Join("\r\n", logs.Select(x => x.ErrMsg)); } - return new ResponseDto(0, orderNumber, errorMessage, null); + return new ResponseModel(0, orderNumber, errorMessage, null); } } } diff --git a/OrdersManagement/Services/UserService.cs b/OrdersManagement/Services/UserService.cs index b54412c..1f4c341 100644 --- a/OrdersManagement/Services/UserService.cs +++ b/OrdersManagement/Services/UserService.cs @@ -8,6 +8,12 @@ public class UserService(HttpClient httpClient) { return await httpClient.GetFromJsonAsync>("api/Users"); } + + public async Task AuthenticateUserAsync(string login, string password) + { + var response = await httpClient.PostAsJsonAsync("api/users/authenticate", new { Login = login, Password = password }); + return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; + } public async Task GetUserAsync(Guid userId) { @@ -19,9 +25,9 @@ public class UserService(HttpClient httpClient) return await httpClient.GetFromJsonAsync($"api/Users/by-username/?username={username}"); } - public async Task AddUserAsync(UserDto user) + public async Task AddUserAsync(UserDto user) { - await httpClient.PostAsJsonAsync("api/Users", user); + return await httpClient.PostAsJsonAsync("api/Users", user); } public async Task UpdateUserAsync(UserDto user) diff --git a/OrdersManagementDataModel/Services/IUserService.cs b/OrdersManagementDataModel/Services/IUserService.cs index 7a0bf7d..d9b92bf 100644 --- a/OrdersManagementDataModel/Services/IUserService.cs +++ b/OrdersManagementDataModel/Services/IUserService.cs @@ -9,6 +9,7 @@ public interface IUserService Task GetByUsername(string username); Task Add(UserDto userDto); Task Update(UserDto userDto); + Task Login(UserDto userDto); Task Delete(Guid id); Task> GetUserRoles(Guid userId); Task GetUserByLoginAndPassword(string login, string password); diff --git a/OrdersManagementDataModel/Services/UserService.cs b/OrdersManagementDataModel/Services/UserService.cs index 131216c..3cff033 100644 --- a/OrdersManagementDataModel/Services/UserService.cs +++ b/OrdersManagementDataModel/Services/UserService.cs @@ -56,6 +56,20 @@ public class UserService(OrdersManagementDbContext context, IMapper mapper) : IU context.Users.Update(user); return await context.SaveChangesAsync(); } + + public async Task Login(UserDto userDto) + { + User? user = context.Users.FirstOrDefault(x => x.Id == userDto.Id); + + if (user is null) return 0; + + user.LastLoginDate = DateTime.Now; + user.FailedLoginAttempts = 0; + + context.Users.Update(user); + + return await context.SaveChangesAsync(); + } public async Task Delete(Guid id) {