using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using ROLAC.API.Data; using ROLAC.API.DTOs.Auth; using ROLAC.API.Entities; namespace ROLAC.API.Services; public class AuthService : IAuthService { private readonly UserManager _userManager; private readonly ITokenService _tokenService; private readonly AppDbContext _db; private readonly int _refreshTokenExpiryDays; public AuthService( UserManager userManager, ITokenService tokenService, AppDbContext db, IConfiguration config) { _userManager = userManager; _tokenService = tokenService; _db = db; _refreshTokenExpiryDays = int.Parse(config["Jwt:RefreshTokenExpiryDays"] ?? "30"); } // ------------------------------------------------------------------------- // Login // ------------------------------------------------------------------------- public async Task<(LoginResponse Response, string RawRefreshToken)> LoginAsync( LoginRequest request, string? ipAddress = null, string? deviceInfo = null) { var user = await _userManager.FindByEmailAsync(request.Email); if (user is null) throw new UnauthorizedAccessException("Invalid credentials."); if (!await _userManager.CheckPasswordAsync(user, request.Password)) throw new UnauthorizedAccessException("Invalid credentials."); if (!user.IsActive) throw new UnauthorizedAccessException("Account is inactive."); var roles = await _userManager.GetRolesAsync(user); var accessToken = _tokenService.GenerateAccessToken(user, roles); var rawRefresh = _tokenService.GenerateRefreshToken(); var tokenHash = _tokenService.HashToken(rawRefresh); _db.RefreshTokens.Add(new RefreshToken { UserId = user.Id, TokenHash = tokenHash, ExpiresAt = DateTime.UtcNow.AddDays(_refreshTokenExpiryDays), CreatedAt = DateTime.UtcNow, IpAddress = ipAddress, DeviceInfo = deviceInfo, }); user.LastLoginAt = DateTime.UtcNow; await _userManager.UpdateAsync(user); await _db.SaveChangesAsync(); return (BuildResponse(accessToken, user, roles), rawRefresh); } // ------------------------------------------------------------------------- // Refresh // ------------------------------------------------------------------------- public async Task<(LoginResponse Response, string RawRefreshToken)> RefreshAsync( string rawRefreshToken, string? ipAddress = null) { var hash = _tokenService.HashToken(rawRefreshToken); var token = await _db.RefreshTokens .FirstOrDefaultAsync(rt => rt.TokenHash == hash); if (token is null || !token.IsActive) throw new UnauthorizedAccessException("Invalid or expired refresh token."); var user = await _userManager.FindByIdAsync(token.UserId); if (user is null) throw new UnauthorizedAccessException("User not found."); var roles = await _userManager.GetRolesAsync(user); var newAccess = _tokenService.GenerateAccessToken(user, roles); var newRaw = _tokenService.GenerateRefreshToken(); var newHash = _tokenService.HashToken(newRaw); // Rotate: mark old token replaced+revoked, create new token token.RevokedAt = DateTime.UtcNow; token.ReplacedByHash = newHash; _db.RefreshTokens.Add(new RefreshToken { UserId = user.Id, TokenHash = newHash, ExpiresAt = DateTime.UtcNow.AddDays(_refreshTokenExpiryDays), CreatedAt = DateTime.UtcNow, IpAddress = ipAddress, DeviceInfo = token.DeviceInfo, }); await _db.SaveChangesAsync(); return (BuildResponse(newAccess, user, roles), newRaw); } // ------------------------------------------------------------------------- // Logout // ------------------------------------------------------------------------- public async Task LogoutAsync(string rawRefreshToken) { var hash = _tokenService.HashToken(rawRefreshToken); var token = await _db.RefreshTokens .FirstOrDefaultAsync(rt => rt.TokenHash == hash); if (token is not null && token.IsActive) { token.RevokedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); } } // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- private static LoginResponse BuildResponse( string accessToken, AppUser user, IList roles) => new() { AccessToken = accessToken, ExpiresIn = 15 * 60, User = new UserInfo { Id = user.Id, Email = user.Email!, Roles = roles, LanguagePreference = user.LanguagePreference, }, }; }