143 lines
4.8 KiB
C#
143 lines
4.8 KiB
C#
using System.Security.Cryptography;
|
|
using LiveRecorder.Application.Abstractions.Auth;
|
|
using LiveRecorder.Application.Abstractions.Persistence;
|
|
using LiveRecorder.Application.Common;
|
|
using LiveRecorder.Application.Models.Auth;
|
|
using LiveRecorder.Domain.Entities;
|
|
|
|
namespace LiveRecorder.Application.Services;
|
|
|
|
public sealed class AuthService : IAuthService
|
|
{
|
|
private readonly IUserAccountRepository _userAccountRepository;
|
|
private readonly IUserSessionRepository _userSessionRepository;
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
|
|
public AuthService(
|
|
IUserAccountRepository userAccountRepository,
|
|
IUserSessionRepository userSessionRepository,
|
|
IUnitOfWork unitOfWork)
|
|
{
|
|
_userAccountRepository = userAccountRepository;
|
|
_userSessionRepository = userSessionRepository;
|
|
_unitOfWork = unitOfWork;
|
|
}
|
|
|
|
public async Task<LoginResponse> LoginAsync(LoginRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
var username = request.Username.Trim();
|
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(request.Password))
|
|
{
|
|
throw new InvalidOperationException("用户名和密码不能为空。");
|
|
}
|
|
|
|
var user = await _userAccountRepository.GetByUsernameAsync(username, cancellationToken)
|
|
?? throw new InvalidOperationException("用户名或密码错误。");
|
|
|
|
if (!user.IsActive || !PasswordHasher.Verify(request.Password, user.PasswordHash))
|
|
{
|
|
throw new InvalidOperationException("用户名或密码错误。");
|
|
}
|
|
|
|
var now = DateTimeOffset.UtcNow;
|
|
var token = Convert.ToHexString(RandomNumberGenerator.GetBytes(32));
|
|
var expiresAt = now.AddHours(12);
|
|
var session = new UserSession(user.Id, token, expiresAt, now);
|
|
|
|
await _userSessionRepository.AddAsync(session, cancellationToken);
|
|
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
|
|
|
var authenticatedUser = new AuthenticatedUser
|
|
{
|
|
UserId = user.Id,
|
|
Username = user.Username,
|
|
DisplayName = user.DisplayName,
|
|
Token = token,
|
|
ExpiresAt = expiresAt
|
|
};
|
|
|
|
return new LoginResponse
|
|
{
|
|
Token = token,
|
|
ExpiresAt = expiresAt,
|
|
User = authenticatedUser
|
|
};
|
|
}
|
|
|
|
public async Task LogoutAsync(string token, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(token))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var session = await _userSessionRepository.GetByTokenAsync(token, cancellationToken);
|
|
if (session is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
session.Revoke(DateTimeOffset.UtcNow);
|
|
_userSessionRepository.Update(session);
|
|
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<AuthenticatedUser?> ValidateTokenAsync(string token, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(token))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var session = await _userSessionRepository.GetByTokenAsync(token, cancellationToken);
|
|
if (session is null || !session.IsValid(DateTimeOffset.UtcNow))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var user = await _userAccountRepository.GetByIdAsync(session.UserAccountId, cancellationToken);
|
|
if (user is null || !user.IsActive)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new AuthenticatedUser
|
|
{
|
|
UserId = user.Id,
|
|
Username = user.Username,
|
|
DisplayName = user.DisplayName,
|
|
Token = session.Token,
|
|
ExpiresAt = session.ExpiresAt
|
|
};
|
|
}
|
|
|
|
public async Task ChangePasswordAsync(Guid userId, ChangePasswordRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
if (string.IsNullOrWhiteSpace(request.CurrentPassword) || string.IsNullOrWhiteSpace(request.NewPassword))
|
|
{
|
|
throw new InvalidOperationException("当前密码和新密码不能为空。");
|
|
}
|
|
|
|
if (request.NewPassword.Length < 6)
|
|
{
|
|
throw new InvalidOperationException("新密码长度不能少于 6 位。");
|
|
}
|
|
|
|
var user = await _userAccountRepository.GetByIdAsync(userId, cancellationToken)
|
|
?? throw new InvalidOperationException("用户不存在。");
|
|
|
|
if (!PasswordHasher.Verify(request.CurrentPassword, user.PasswordHash))
|
|
{
|
|
throw new InvalidOperationException("当前密码不正确。");
|
|
}
|
|
|
|
user.UpdatePassword(PasswordHasher.Hash(request.NewPassword));
|
|
_userAccountRepository.Update(user);
|
|
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|