From fe83c47e3745075f04a65138d9399bc1f533d8d6 Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Sat, 22 Aug 2026 01:36:27 -0400 Subject: [PATCH] Migrate TokenService.RefreshTokenAsync to UserManager Last remaining IAuthRepository call site; RefreshTokenResult now carries a plain (Guid, string) instead of UserAccount. --- .../Commands/RefreshTokenHandlerTests.cs | 6 ++-- .../Services/TokenServiceRefreshTests.cs | 33 ++++++++----------- .../Services/TokenServiceValidationTests.cs | 7 ++-- .../RefreshToken/RefreshTokenHandler.cs | 4 +-- .../Features.Auth/Services/ITokenService.cs | 3 +- .../Features.Auth/Services/TokenService.cs | 21 +++++++----- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/web/backend/Features/Features.Auth.Tests/Commands/RefreshTokenHandlerTests.cs b/web/backend/Features/Features.Auth.Tests/Commands/RefreshTokenHandlerTests.cs index 8eb5b16c..bb006d4b 100644 --- a/web/backend/Features/Features.Auth.Tests/Commands/RefreshTokenHandlerTests.cs +++ b/web/backend/Features/Features.Auth.Tests/Commands/RefreshTokenHandlerTests.cs @@ -1,4 +1,3 @@ -using Domain.Entities; using Features.Auth.Commands.RefreshToken; using Features.Auth.Dtos; using Features.Auth.Services; @@ -15,11 +14,12 @@ public class RefreshTokenHandlerTests Mock tokenServiceMock = new(); RefreshTokenHandler handler = new(tokenServiceMock.Object); Guid userId = Guid.NewGuid(); - UserAccount user = new() { UserAccountId = userId, Username = "testuser" }; tokenServiceMock .Setup(x => x.RefreshTokenAsync("old-refresh-token")) - .ReturnsAsync(new RefreshTokenResult(user, "new-refresh-token", "new-access-token")); + .ReturnsAsync( + new RefreshTokenResult(userId, "testuser", "new-refresh-token", "new-access-token") + ); LoginPayload result = await handler.Handle( new RefreshTokenCommand("old-refresh-token"), diff --git a/web/backend/Features/Features.Auth.Tests/Services/TokenServiceRefreshTests.cs b/web/backend/Features/Features.Auth.Tests/Services/TokenServiceRefreshTests.cs index 76c49ab9..9e9215d2 100644 --- a/web/backend/Features/Features.Auth.Tests/Services/TokenServiceRefreshTests.cs +++ b/web/backend/Features/Features.Auth.Tests/Services/TokenServiceRefreshTests.cs @@ -1,25 +1,26 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; -using Domain.Entities; using Domain.Exceptions; -using Features.Auth.Repository; +using Features.Auth.Identity; using Features.Auth.Services; +using Features.Auth.Tests.TestSupport; using FluentAssertions; using Infrastructure.Jwt; +using Microsoft.AspNetCore.Identity; using Moq; namespace Features.Auth.Tests.Services; public class TokenServiceRefreshTests { - private readonly Mock _authRepositoryMock; private readonly Mock _tokenInfraMock; private readonly TokenService _tokenService; + private readonly Mock> _userManagerMock; public TokenServiceRefreshTests() { _tokenInfraMock = new Mock(); - _authRepositoryMock = new Mock(); + _userManagerMock = UserManagerMockFactory.Create(); // Set environment variables for tokens Environment.SetEnvironmentVariable( @@ -35,7 +36,7 @@ public class TokenServiceRefreshTests "test-confirmation-secret-that-is-very-long-1234567890" ); - _tokenService = new TokenService(_tokenInfraMock.Object, _authRepositoryMock.Object); + _tokenService = new TokenService(_tokenInfraMock.Object, _userManagerMock.Object); } [Fact] @@ -56,15 +57,7 @@ public class TokenServiceRefreshTests ClaimsIdentity claimsIdentity = new(claims); ClaimsPrincipal principal = new(claimsIdentity); - UserAccount userAccount = new() - { - UserAccountId = userId, - Username = username, - FirstName = "Test", - LastName = "User", - Email = "test@example.com", - DateOfBirth = new DateTime(1990, 1, 1), - }; + ApplicationUser user = new() { Id = userId, UserName = username }; // Mock the validation of refresh token _tokenInfraMock @@ -78,19 +71,19 @@ public class TokenServiceRefreshTests (Guid _, string _, DateTime _, string _) => $"generated-token-{Guid.NewGuid()}" ); - _authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync(userAccount); + _userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user); // Act RefreshTokenResult result = await _tokenService.RefreshTokenAsync(refreshToken); // Assert result.Should().NotBeNull(); - result.UserAccount.UserAccountId.Should().Be(userId); - result.UserAccount.Username.Should().Be(username); + result.UserId.Should().Be(userId); + result.Username.Should().Be(username); result.AccessToken.Should().NotBeEmpty(); result.RefreshToken.Should().NotBeEmpty(); - _authRepositoryMock.Verify(x => x.GetUserByIdAsync(userId), Times.Once); + _userManagerMock.Verify(x => x.FindByIdAsync(userId.ToString()), Times.Once); // Verify tokens were generated (called twice - once for access, once for refresh) _tokenInfraMock.Verify( @@ -161,7 +154,9 @@ public class TokenServiceRefreshTests .Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny())) .ReturnsAsync(principal); - _authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync((UserAccount?)null); + _userManagerMock + .Setup(x => x.FindByIdAsync(userId.ToString())) + .ReturnsAsync((ApplicationUser?)null); // Act & Assert await FluentActions diff --git a/web/backend/Features/Features.Auth.Tests/Services/TokenServiceValidationTests.cs b/web/backend/Features/Features.Auth.Tests/Services/TokenServiceValidationTests.cs index 7aa1a6ab..213ef7fd 100644 --- a/web/backend/Features/Features.Auth.Tests/Services/TokenServiceValidationTests.cs +++ b/web/backend/Features/Features.Auth.Tests/Services/TokenServiceValidationTests.cs @@ -1,8 +1,8 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Domain.Exceptions; -using Features.Auth.Repository; using Features.Auth.Services; +using Features.Auth.Tests.TestSupport; using FluentAssertions; using Infrastructure.Jwt; using Moq; @@ -32,7 +32,10 @@ public class TokenServiceValidationTests "test-confirmation-secret-that-is-very-long-1234567890" ); - _tokenService = new TokenService(_tokenInfraMock.Object, new Mock().Object); + _tokenService = new TokenService( + _tokenInfraMock.Object, + UserManagerMockFactory.Create().Object + ); } [Fact] diff --git a/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenHandler.cs b/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenHandler.cs index 6f0a520b..386ae836 100644 --- a/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenHandler.cs +++ b/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenHandler.cs @@ -22,8 +22,8 @@ public class RefreshTokenHandler(ITokenService tokenService) { RefreshTokenResult result = await tokenService.RefreshTokenAsync(request.RefreshToken); return new LoginPayload( - result.UserAccount.UserAccountId, - result.UserAccount.Username, + result.UserId, + result.Username, result.RefreshToken, result.AccessToken ); diff --git a/web/backend/Features/Features.Auth/Services/ITokenService.cs b/web/backend/Features/Features.Auth/Services/ITokenService.cs index b9fdb2c1..de303f6e 100644 --- a/web/backend/Features/Features.Auth/Services/ITokenService.cs +++ b/web/backend/Features/Features.Auth/Services/ITokenService.cs @@ -1,5 +1,4 @@ using System.Security.Claims; -using Domain.Entities; namespace Features.Auth.Services; @@ -22,7 +21,7 @@ public enum TokenType public record ValidatedToken(Guid UserId, string Username, ClaimsPrincipal Principal); /// Represents the result of refreshing a user's session. -public record RefreshTokenResult(UserAccount UserAccount, string RefreshToken, string AccessToken); +public record RefreshTokenResult(Guid UserId, string Username, string RefreshToken, string AccessToken); /// /// Defines the expiration windows, in hours, for each type of token issued by . diff --git a/web/backend/Features/Features.Auth/Services/TokenService.cs b/web/backend/Features/Features.Auth/Services/TokenService.cs index 6f6729c4..1f6e4d4c 100644 --- a/web/backend/Features/Features.Auth/Services/TokenService.cs +++ b/web/backend/Features/Features.Auth/Services/TokenService.cs @@ -1,9 +1,9 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; -using Domain.Entities; using Domain.Exceptions; -using Features.Auth.Repository; +using Features.Auth.Identity; using Infrastructure.Jwt; +using Microsoft.AspNetCore.Identity; namespace Features.Auth.Services; @@ -14,20 +14,23 @@ namespace Features.Auth.Services; public class TokenService : ITokenService { private readonly string _accessTokenSecret; - private readonly IAuthRepository _authRepository; private readonly string _confirmationTokenSecret; private readonly string _refreshTokenSecret; private readonly ITokenInfrastructure _tokenInfrastructure; + private readonly UserManager _userManager; /// Initializes a new instance of the class. /// /// Thrown when any of the ACCESS_TOKEN_SECRET, REFRESH_TOKEN_SECRET, or /// CONFIRMATION_TOKEN_SECRET environment variables are not set. /// - public TokenService(ITokenInfrastructure tokenInfrastructure, IAuthRepository authRepository) + public TokenService( + ITokenInfrastructure tokenInfrastructure, + UserManager userManager + ) { _tokenInfrastructure = tokenInfrastructure; - _authRepository = authRepository; + _userManager = userManager; _accessTokenSecret = Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET") @@ -100,14 +103,14 @@ public class TokenService : ITokenService public async Task RefreshTokenAsync(string refreshTokenString) { ValidatedToken validated = await ValidateRefreshTokenAsync(refreshTokenString); - UserAccount? user = await _authRepository.GetUserByIdAsync(validated.UserId); + ApplicationUser? user = await _userManager.FindByIdAsync(validated.UserId.ToString()); if (user == null) throw new UnauthorizedException("User account not found"); - string newAccess = GenerateAccessToken(user.UserAccountId, user.Username); - string newRefresh = GenerateRefreshToken(user.UserAccountId, user.Username); + string newAccess = GenerateAccessToken(user.Id, user.UserName); + string newRefresh = GenerateRefreshToken(user.Id, user.UserName); - return new RefreshTokenResult(user, newRefresh, newAccess); + return new RefreshTokenResult(user.Id, user.UserName, newRefresh, newAccess); } ///