Migrate TokenService.RefreshTokenAsync to UserManager

Last remaining IAuthRepository call site; RefreshTokenResult now
carries a plain (Guid, string) instead of UserAccount.
This commit is contained in:
Aaron Po
2026-08-22 01:36:27 -04:00
parent 2e5f02fb38
commit 6b9c14c14b
6 changed files with 37 additions and 37 deletions

View File

@@ -1,4 +1,3 @@
using Domain.Entities;
using Features.Auth.Commands.RefreshToken; using Features.Auth.Commands.RefreshToken;
using Features.Auth.Dtos; using Features.Auth.Dtos;
using Features.Auth.Services; using Features.Auth.Services;
@@ -15,11 +14,12 @@ public class RefreshTokenHandlerTests
Mock<ITokenService> tokenServiceMock = new(); Mock<ITokenService> tokenServiceMock = new();
RefreshTokenHandler handler = new(tokenServiceMock.Object); RefreshTokenHandler handler = new(tokenServiceMock.Object);
Guid userId = Guid.NewGuid(); Guid userId = Guid.NewGuid();
UserAccount user = new() { UserAccountId = userId, Username = "testuser" };
tokenServiceMock tokenServiceMock
.Setup(x => x.RefreshTokenAsync("old-refresh-token")) .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( LoginPayload result = await handler.Handle(
new RefreshTokenCommand("old-refresh-token"), new RefreshTokenCommand("old-refresh-token"),

View File

@@ -1,25 +1,26 @@
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; using System.Security.Claims;
using Domain.Entities;
using Domain.Exceptions; using Domain.Exceptions;
using Features.Auth.Repository; using Features.Auth.Identity;
using Features.Auth.Services; using Features.Auth.Services;
using Features.Auth.Tests.TestSupport;
using FluentAssertions; using FluentAssertions;
using Infrastructure.Jwt; using Infrastructure.Jwt;
using Microsoft.AspNetCore.Identity;
using Moq; using Moq;
namespace Features.Auth.Tests.Services; namespace Features.Auth.Tests.Services;
public class TokenServiceRefreshTests public class TokenServiceRefreshTests
{ {
private readonly Mock<IAuthRepository> _authRepositoryMock;
private readonly Mock<ITokenInfrastructure> _tokenInfraMock; private readonly Mock<ITokenInfrastructure> _tokenInfraMock;
private readonly TokenService _tokenService; private readonly TokenService _tokenService;
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
public TokenServiceRefreshTests() public TokenServiceRefreshTests()
{ {
_tokenInfraMock = new Mock<ITokenInfrastructure>(); _tokenInfraMock = new Mock<ITokenInfrastructure>();
_authRepositoryMock = new Mock<IAuthRepository>(); _userManagerMock = UserManagerMockFactory.Create();
// Set environment variables for tokens // Set environment variables for tokens
Environment.SetEnvironmentVariable( Environment.SetEnvironmentVariable(
@@ -35,7 +36,7 @@ public class TokenServiceRefreshTests
"test-confirmation-secret-that-is-very-long-1234567890" "test-confirmation-secret-that-is-very-long-1234567890"
); );
_tokenService = new TokenService(_tokenInfraMock.Object, _authRepositoryMock.Object); _tokenService = new TokenService(_tokenInfraMock.Object, _userManagerMock.Object);
} }
[Fact] [Fact]
@@ -56,15 +57,7 @@ public class TokenServiceRefreshTests
ClaimsIdentity claimsIdentity = new(claims); ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity); ClaimsPrincipal principal = new(claimsIdentity);
UserAccount userAccount = new() ApplicationUser user = new() { Id = userId, UserName = username };
{
UserAccountId = userId,
Username = username,
FirstName = "Test",
LastName = "User",
Email = "test@example.com",
DateOfBirth = new DateTime(1990, 1, 1),
};
// Mock the validation of refresh token // Mock the validation of refresh token
_tokenInfraMock _tokenInfraMock
@@ -78,19 +71,19 @@ public class TokenServiceRefreshTests
(Guid _, string _, DateTime _, string _) => $"generated-token-{Guid.NewGuid()}" (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 // Act
RefreshTokenResult result = await _tokenService.RefreshTokenAsync(refreshToken); RefreshTokenResult result = await _tokenService.RefreshTokenAsync(refreshToken);
// Assert // Assert
result.Should().NotBeNull(); result.Should().NotBeNull();
result.UserAccount.UserAccountId.Should().Be(userId); result.UserId.Should().Be(userId);
result.UserAccount.Username.Should().Be(username); result.Username.Should().Be(username);
result.AccessToken.Should().NotBeEmpty(); result.AccessToken.Should().NotBeEmpty();
result.RefreshToken.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) // Verify tokens were generated (called twice - once for access, once for refresh)
_tokenInfraMock.Verify( _tokenInfraMock.Verify(
@@ -161,7 +154,9 @@ public class TokenServiceRefreshTests
.Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny<string>())) .Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny<string>()))
.ReturnsAsync(principal); .ReturnsAsync(principal);
_authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync((UserAccount?)null); _userManagerMock
.Setup(x => x.FindByIdAsync(userId.ToString()))
.ReturnsAsync((ApplicationUser?)null);
// Act & Assert // Act & Assert
await FluentActions await FluentActions

View File

@@ -1,8 +1,8 @@
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; using System.Security.Claims;
using Domain.Exceptions; using Domain.Exceptions;
using Features.Auth.Repository;
using Features.Auth.Services; using Features.Auth.Services;
using Features.Auth.Tests.TestSupport;
using FluentAssertions; using FluentAssertions;
using Infrastructure.Jwt; using Infrastructure.Jwt;
using Moq; using Moq;
@@ -32,7 +32,10 @@ public class TokenServiceValidationTests
"test-confirmation-secret-that-is-very-long-1234567890" "test-confirmation-secret-that-is-very-long-1234567890"
); );
_tokenService = new TokenService(_tokenInfraMock.Object, new Mock<IAuthRepository>().Object); _tokenService = new TokenService(
_tokenInfraMock.Object,
UserManagerMockFactory.Create().Object
);
} }
[Fact] [Fact]

View File

@@ -22,8 +22,8 @@ public class RefreshTokenHandler(ITokenService tokenService)
{ {
RefreshTokenResult result = await tokenService.RefreshTokenAsync(request.RefreshToken); RefreshTokenResult result = await tokenService.RefreshTokenAsync(request.RefreshToken);
return new LoginPayload( return new LoginPayload(
result.UserAccount.UserAccountId, result.UserId,
result.UserAccount.Username, result.Username,
result.RefreshToken, result.RefreshToken,
result.AccessToken result.AccessToken
); );

View File

@@ -1,5 +1,4 @@
using System.Security.Claims; using System.Security.Claims;
using Domain.Entities;
namespace Features.Auth.Services; namespace Features.Auth.Services;
@@ -22,7 +21,7 @@ public enum TokenType
public record ValidatedToken(Guid UserId, string Username, ClaimsPrincipal Principal); public record ValidatedToken(Guid UserId, string Username, ClaimsPrincipal Principal);
/// <summary>Represents the result of refreshing a user's session.</summary> /// <summary>Represents the result of refreshing a user's session.</summary>
public record RefreshTokenResult(UserAccount UserAccount, string RefreshToken, string AccessToken); public record RefreshTokenResult(Guid UserId, string Username, string RefreshToken, string AccessToken);
/// <summary> /// <summary>
/// Defines the expiration windows, in hours, for each type of token issued by <see cref="ITokenService" />. /// Defines the expiration windows, in hours, for each type of token issued by <see cref="ITokenService" />.

View File

@@ -1,9 +1,9 @@
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; using System.Security.Claims;
using Domain.Entities;
using Domain.Exceptions; using Domain.Exceptions;
using Features.Auth.Repository; using Features.Auth.Identity;
using Infrastructure.Jwt; using Infrastructure.Jwt;
using Microsoft.AspNetCore.Identity;
namespace Features.Auth.Services; namespace Features.Auth.Services;
@@ -14,20 +14,23 @@ namespace Features.Auth.Services;
public class TokenService : ITokenService public class TokenService : ITokenService
{ {
private readonly string _accessTokenSecret; private readonly string _accessTokenSecret;
private readonly IAuthRepository _authRepository;
private readonly string _confirmationTokenSecret; private readonly string _confirmationTokenSecret;
private readonly string _refreshTokenSecret; private readonly string _refreshTokenSecret;
private readonly ITokenInfrastructure _tokenInfrastructure; private readonly ITokenInfrastructure _tokenInfrastructure;
private readonly UserManager<ApplicationUser> _userManager;
/// <summary>Initializes a new instance of the <see cref="TokenService" /> class.</summary> /// <summary>Initializes a new instance of the <see cref="TokenService" /> class.</summary>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// Thrown when any of the <c>ACCESS_TOKEN_SECRET</c>, <c>REFRESH_TOKEN_SECRET</c>, or /// Thrown when any of the <c>ACCESS_TOKEN_SECRET</c>, <c>REFRESH_TOKEN_SECRET</c>, or
/// <c>CONFIRMATION_TOKEN_SECRET</c> environment variables are not set. /// <c>CONFIRMATION_TOKEN_SECRET</c> environment variables are not set.
/// </exception> /// </exception>
public TokenService(ITokenInfrastructure tokenInfrastructure, IAuthRepository authRepository) public TokenService(
ITokenInfrastructure tokenInfrastructure,
UserManager<ApplicationUser> userManager
)
{ {
_tokenInfrastructure = tokenInfrastructure; _tokenInfrastructure = tokenInfrastructure;
_authRepository = authRepository; _userManager = userManager;
_accessTokenSecret = _accessTokenSecret =
Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET") Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET")
@@ -100,14 +103,14 @@ public class TokenService : ITokenService
public async Task<RefreshTokenResult> RefreshTokenAsync(string refreshTokenString) public async Task<RefreshTokenResult> RefreshTokenAsync(string refreshTokenString)
{ {
ValidatedToken validated = await ValidateRefreshTokenAsync(refreshTokenString); ValidatedToken validated = await ValidateRefreshTokenAsync(refreshTokenString);
UserAccount? user = await _authRepository.GetUserByIdAsync(validated.UserId); ApplicationUser? user = await _userManager.FindByIdAsync(validated.UserId.ToString());
if (user == null) if (user == null)
throw new UnauthorizedException("User account not found"); throw new UnauthorizedException("User account not found");
string newAccess = GenerateAccessToken(user.UserAccountId, user.Username); string newAccess = GenerateAccessToken(user.Id, user.UserName);
string newRefresh = GenerateRefreshToken(user.UserAccountId, user.Username); string newRefresh = GenerateRefreshToken(user.Id, user.UserName);
return new RefreshTokenResult(user, newRefresh, newAccess); return new RefreshTokenResult(user.Id, user.UserName, newRefresh, newAccess);
} }
/// <summary> /// <summary>