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