Files
Nexus.Reader/src/NexusReader.Web/Services/ServerIdentityService.cs
T

151 lines
5.2 KiB
C#

using System.Security.Claims;
using FluentResults;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using NexusReader.Application.DTOs.User;
using NexusReader.Data.Persistence;
using NexusReader.Domain.Entities;
using NexusReader.Application.Queries.User;
using MediatR;
using NexusReader.Application.Constants;
using NexusReader.Application.Abstractions.Services;
using Microsoft.AspNetCore.Components.Authorization;
namespace NexusReader.Web.Services;
public class ServerIdentityService : IIdentityService
{
private readonly UserManager<NexusUser> _userManager;
private readonly SignInManager<NexusUser> _signInManager;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IMediator _mediator;
private readonly INativeStorageService _storageService;
private readonly AuthenticationStateProvider _authStateProvider;
public event Func<Task>? OnStateInvalidated;
public ServerIdentityService(
UserManager<NexusUser> userManager,
SignInManager<NexusUser> signInManager,
IHttpContextAccessor httpContextAccessor,
IMediator mediator,
INativeStorageService storageService,
AuthenticationStateProvider authStateProvider)
{
_userManager = userManager;
_signInManager = signInManager;
_httpContextAccessor = httpContextAccessor;
_mediator = mediator;
_storageService = storageService;
_authStateProvider = authStateProvider;
}
public async Task<Result> LoginAsync(string email, string password, bool rememberMe = false)
{
try
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null) return Result.Fail("Nieprawidłowy e-mail lub hasło.");
// Check if account is locked
if (await _userManager.IsLockedOutAsync(user)) return Result.Fail("Konto zostało zablokowane.");
// Check password
var isCorrect = await _userManager.CheckPasswordAsync(user, password);
if (!isCorrect)
{
await _userManager.AccessFailedAsync(user);
return Result.Fail("Nieprawidłowy e-mail lub hasło.");
}
// Reset access failed count on success
await _userManager.ResetAccessFailedCountAsync(user);
// In Blazor Interactive Server, we cannot use PasswordSignInAsync directly
// because headers are read-only once the circuit is established.
// We return success here to indicate credentials are valid.
// The UI will then perform a POST redirect to /account/login-form to set cookies.
return Result.Ok();
}
catch (Exception ex)
{
return Result.Fail(new Error($"Błąd podczas weryfikacji poświadczeń: {ex.Message}").CausedBy(ex));
}
}
public async Task<Result> LogoutAsync()
{
// Logout via SignalR is also problematic for cookie clearing.
// The UI should redirect to /account/logout-form
return Result.Ok();
}
public async Task<Result> RegisterAsync(string email, string password)
{
try
{
var user = new NexusUser
{
UserName = email,
Email = email,
SubscriptionPlanId = SubscriptionPlan.FreeId,
TenantId = "global"
};
var result = await _userManager.CreateAsync(user, password);
if (result.Succeeded)
{
// Similar to Login, we return success but don't sign in here.
return Result.Ok();
}
return Result.Fail(result.Errors.Select(e => e.Description).FirstOrDefault() ?? "Rejestracja nie powiodła się.");
}
catch (Exception ex)
{
return Result.Fail(new Error($"Błąd podczas rejestracji na serwerze: {ex.Message}").CausedBy(ex));
}
}
public Task<Result> RefreshTokenAsync() => Task.FromResult(Result.Ok());
public async Task<Result<UserProfileDto>> GetProfileAsync()
{
var authState = await _authStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user == null || !user.Identity?.IsAuthenticated == true)
{
user = _httpContextAccessor.HttpContext?.User;
}
if (user == null || !user.Identity?.IsAuthenticated == true) return Result.Fail("Not authenticated.");
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
if (userId == null) return Result.Fail("User ID not found.");
var result = await _mediator.Send(new GetUserProfileQuery(userId));
if (result.IsFailed) return Result.Fail(result.Errors);
return Result.Ok(result.Value);
}
public void ClearCache()
{
if (OnStateInvalidated != null)
{
_ = Task.Run(async () =>
{
try
{
await OnStateInvalidated.Invoke();
}
catch
{
// Ignore
}
});
}
}
}