* Added Authentication

This commit is contained in:
2025-02-21 09:59:50 +01:00
parent ed5b5634fd
commit 6774311433
20 changed files with 468 additions and 30 deletions

View File

@@ -1,3 +1,4 @@
using FaKrosnoApi.Models;
using Microsoft.AspNetCore.Mvc;
using OrdersManagementDataModel.Dtos;
using OrdersManagementDataModel.Services;
@@ -29,6 +30,51 @@ public class UsersController(IUserService service) : Controller
return user != null ? Ok(user) : NotFound();
}
[HttpPost("authenticate")]
public async Task<IActionResult> 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<ActionResult<UserDto>> Add([FromBody] UserDto user)
{

View File

@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0" />
<PackageReference Include="Hangfire" Version="1.8.17" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />

View File

@@ -0,0 +1,7 @@
namespace FaKrosnoApi.Models;
public class AuthenticateRequestModel
{
public string Login { get; set; }
public string Password { get; set; }
}

View File

@@ -1,4 +1,5 @@
<!DOCTYPE html>
@using Microsoft.AspNetCore.Components.Authorization
<!DOCTYPE html>
<html lang="en">
<head>
@@ -17,12 +18,23 @@
</head>
<body>
@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");
}
}
<Routes @rendermode="@InteractiveServer" />
<script src="_framework/blazor.web.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
<!-- Add chart.js reference if chart components are used in your application. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.0.1/chart.umd.js" integrity="sha512-gQhCDsnnnUfaRzD8k1L5llCCV6O9HN09zClIzzeJ8OJ9MpGmIlCxm+pdCkqTwqJ4JcjbojFr79rl2F1mzcoLMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Add chartjs-plugin-datalabels.min.js reference if chart components with data label feature is used in your application. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js" integrity="sha512-JPcRR8yFa8mmCsfrw4TNte1ZvF1e3+1SdGMslZvmrzDYxS69J7J49vkFL8u6u8PlPJK+H3voElBtUCzaXj+6ig==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script src="_content/Blazor.Bootstrap/blazor.bootstrap.js"></script>

View File

@@ -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
<div class="h-100 d-flex flex-column">
<h5>Logowanie</h5>
@if (!string.IsNullOrEmpty(TempPassword))
{
<div class="alert alert-info">
Twoje tymczasowe hasło to: <strong>@TempPassword</strong>. Użyj go do pierwszego logowania.
</div>
}
<SfCard>
<CardHeader Title="Zaloguj się"/>
<CardContent>
<EditForm Model="@LoginModel" FormName="LoginForm" OnValidSubmit="@HandleLogin">
<DataAnnotationsValidator/>
<ValidationSummary/>
<div class="form-group">
<label for="login">Login</label>
<SfTextBox ID="login" @bind-Value="LoginModel.Login" Placeholder="Wprowadź login"
CssClass="e-outline"/>
<ValidationMessage For="@(() => LoginModel.Login)"/>
</div>
<div class="form-group">
<label for="password">Hasło</label>
<SfTextBox ID="password" Type="InputType.Password" @bind-Value="LoginModel.Password"
Placeholder="Wprowadź hasło" CssClass="e-outline"/>
<ValidationMessage For="@(() => LoginModel.Password)"/>
</div>
<div class="form-group mt-3">
<SfButton CssClass="e-primary" IsPrimary="true" Type="submit">Zaloguj</SfButton>
</div>
@if (!string.IsNullOrEmpty(ErrorMessage))
{
<div class="alert alert-danger mt-3">@ErrorMessage</div>
}
</EditForm>
@if (ShowChangePassword)
{
<hr/>
<h5>Zmień hasło</h5>
<EditForm Model="@ChangePasswordModel" FormName="ChangePasswordForm"
OnValidSubmit="@HandleChangePassword">
<DataAnnotationsValidator/>
<ValidationSummary/>
<div class="form-group">
<label for="newPassword">Nowe hasło</label>
<SfTextBox ID="newPassword" Type="InputType.Password"
@bind-Value="ChangePasswordModel.NewPassword" Placeholder="Wprowadź nowe hasło"
CssClass="e-outline"/>
<ValidationMessage For="@(() => ChangePasswordModel.NewPassword)"/>
</div>
<div class="form-group">
<label for="confirmPassword">Potwierdź hasło</label>
<SfTextBox ID="confirmPassword" Type="InputType.Password"
@bind-Value="ChangePasswordModel.ConfirmPassword" Placeholder="Potwierdź nowe hasło"
CssClass="e-outline"/>
<ValidationMessage For="@(() => ChangePasswordModel.ConfirmPassword)"/>
</div>
<div class="form-group mt-3">
<SfButton CssClass="e-success" Type="submit">Zmień hasło</SfButton>
</div>
</EditForm>
}
</CardContent>
</SfCard>
</div>
@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();
}
}
}

View File

@@ -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
<div class="h-100 d-flex flex-column">
<h5>Rejestracja</h5>
<br/>
<SfCard>
<CardHeader Title="Zarejestruj się"/>
<CardContent>
<EditForm Model="@RegisterModel" OnValidSubmit="HandleRegister">
<DataAnnotationsValidator/>
<ValidationSummary/>
<div class="form-group">
<label for="login">Login</label>
<SfTextBox ID="login" @bind-Value="RegisterModel.Login" Placeholder="Wprowadź login"
CssClass="e-outline"/>
<ValidationMessage For="@(() => RegisterModel.Login)"/>
</div>
<div class="form-group">
<label for="email">Email</label>
<SfTextBox ID="email" @bind-Value="RegisterModel.Email" Placeholder="Wprowadź email"
CssClass="e-outline"/>
<ValidationMessage For="@(() => RegisterModel.Email)"/>
</div>
<div class="form-group">
<label for="firstName">Imię</label>
<SfTextBox ID="firstName" @bind-Value="RegisterModel.FirstName" Placeholder="Wprowadź imię"
CssClass="e-outline"/>
<ValidationMessage For="@(() => RegisterModel.FirstName)"/>
</div>
<div class="form-group">
<label for="lastName">Nazwisko</label>
<SfTextBox ID="lastName" @bind-Value="RegisterModel.LastName" Placeholder="Wprowadź nazwisko"
CssClass="e-outline"/>
<ValidationMessage For="@(() => RegisterModel.LastName)"/>
</div>
<div class="form-group mt-3">
<SfButton CssClass="e-primary" IsPrimary="true" Type="submit">Zarejestruj</SfButton>
</div>
</EditForm>
</CardContent>
</SfCard>
</div>
@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)}");
}
}
}

View File

@@ -21,8 +21,7 @@
<GridColumn Field="@nameof(UserDto.IsActive)" HeaderText="Aktywny" Width="80"></GridColumn>
<GridColumn Field="@nameof(UserDto.CreatedDate)" HeaderText="Utworzono" Format="d" Width="120"></GridColumn>
</GridColumns>
<GridEditSettings AllowDeleting="true" ShowDeleteConfirmDialog="true" AllowAdding="true" AllowEditing="true"
Mode="EditMode.Normal"></GridEditSettings>
<GridEditSettings AllowDeleting="true" ShowDeleteConfirmDialog="true" AllowAdding="true" NewRowPosition="NewRowPosition.Bottom" AllowEditing="true"></GridEditSettings>
<GridEvents OnActionBegin="UserActionBegin" OnActionComplete="UserActionComplete" TValue="UserDto"></GridEvents>
</SfGrid>§
<br/>
@@ -169,5 +168,4 @@
break;
}
}
}

View File

@@ -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 @@
<Content>
@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))
{
<p>Zamówienie EDI @response.Identifier zostało poprawnie zaksięgowane w Zamówieniach klienta pod
numerem '@response.ExternalIdentifier'</p>
@@ -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))
{
<p>Błąd: Zamówienie EDI @response.Identifier nie zostało poprawnie zaksięgowane w Zamówieniach
klienta.<br/>Lista błędów:<br/>@response.Message</p>
@@ -135,7 +138,7 @@
private IEnumerable<EdiCustomerOrderDto> _ediCustomerOrders = [];
private List<EdiCustomerOrderDto> _selectedEdiCustomerOrders = new();
private List<ResponseDto> _responses = new();
private List<ResponseModel> _responses = new();
private bool _isVisible;
private bool? _filter = false;
@@ -158,11 +161,11 @@
{
if (!_selectedEdiCustomerOrders.Any()) return false;
_responses = new List<ResponseDto>();
_responses = new List<ResponseModel>();
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)
{

View File

@@ -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
<div class="h-100 d-flex flex-column">
<h5>Zamówienia DELFOR</h5>

View File

@@ -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<AuthenticationState> 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());
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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;

View File

@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0" />
<PackageReference Include="Blazor.Bootstrap" Version="3.2.0" />
<PackageReference Include="Syncfusion.Blazor.Buttons" Version="28.2.3" />
<PackageReference Include="Syncfusion.Blazor.Cards" Version="28.2.3" />

View File

@@ -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<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddScoped<UserService>();
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<ScheduleOrderService>();
builder.Services.AddScoped<ScheduleOrderDetailsService>();
builder.Services.AddScoped<EdiCustomerOrderService>();
builder.Services.AddScoped<CustomerOrderService>();
builder.Services.AddScoped<HangfireService>();
builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<RoleService>();
builder.Services.AddScoped<FunctionService>();
@@ -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<App>()

View File

@@ -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<ResponseDto> SendOrderToSyteline(Guid customerOrderNumber, string orderNumber)
public async Task<ResponseModel> 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);
}
}
}

View File

@@ -9,6 +9,12 @@ public class UserService(HttpClient httpClient)
return await httpClient.GetFromJsonAsync<IEnumerable<UserDto>>("api/Users");
}
public async Task<UserDto?> 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<UserDto>() : null;
}
public async Task<UserDto?> GetUserAsync(Guid userId)
{
return await httpClient.GetFromJsonAsync<UserDto>($"api/Users/by-id/?id={userId}");
@@ -19,9 +25,9 @@ public class UserService(HttpClient httpClient)
return await httpClient.GetFromJsonAsync<UserDto>($"api/Users/by-username/?username={username}");
}
public async Task AddUserAsync(UserDto user)
public async Task<HttpResponseMessage> AddUserAsync(UserDto user)
{
await httpClient.PostAsJsonAsync("api/Users", user);
return await httpClient.PostAsJsonAsync("api/Users", user);
}
public async Task UpdateUserAsync(UserDto user)

View File

@@ -9,6 +9,7 @@ public interface IUserService
Task<UserDto?> GetByUsername(string username);
Task<int> Add(UserDto userDto);
Task<int> Update(UserDto userDto);
Task<int> Login(UserDto userDto);
Task<int> Delete(Guid id);
Task<IList<UserRoleDto>> GetUserRoles(Guid userId);
Task<UserDto?> GetUserByLoginAndPassword(string login, string password);

View File

@@ -57,6 +57,20 @@ public class UserService(OrdersManagementDbContext context, IMapper mapper) : IU
return await context.SaveChangesAsync();
}
public async Task<int> 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<int> Delete(Guid id)
{
User? user = await context.Users.Where(x => x.RowPointer == id).FirstOrDefaultAsync() ?? null;