151 lines
5.2 KiB
C#
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
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|