Decouple ITokenService from UserAccount entity

Token generation only ever needed the id/username, not the full UserAccount.
This commit is contained in:
Aaron Po
2026-08-22 01:28:41 -04:00
parent 7793129284
commit 885b2a9570
8 changed files with 40 additions and 38 deletions

View File

@@ -92,13 +92,13 @@ public class RegisterUserHandlerTests
); );
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateAccessToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("access-token"); .Returns("access-token");
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateRefreshToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("refresh-token"); .Returns("refresh-token");
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateConfirmationToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateConfirmationToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("confirmation-token"); .Returns("confirmation-token");
RegistrationPayload result = await _handler.Handle(command, CancellationToken.None); RegistrationPayload result = await _handler.Handle(command, CancellationToken.None);
@@ -192,10 +192,10 @@ public class RegisterUserHandlerTests
.ReturnsAsync(new UserAccount { UserAccountId = Guid.NewGuid() }); .ReturnsAsync(new UserAccount { UserAccountId = Guid.NewGuid() });
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateAccessToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("access-token"); .Returns("access-token");
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateRefreshToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("refresh-token"); .Returns("refresh-token");
await _handler.Handle(command, CancellationToken.None); await _handler.Handle(command, CancellationToken.None);
@@ -241,10 +241,10 @@ public class RegisterUserHandlerTests
); );
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateAccessToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("access-token"); .Returns("access-token");
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateRefreshToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("refresh-token"); .Returns("refresh-token");
_mediatorMock _mediatorMock
.Setup(x => .Setup(x =>

View File

@@ -37,7 +37,7 @@ public class ResendConfirmationEmailHandlerTests
_authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync(user); _authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync(user);
_authRepositoryMock.Setup(x => x.IsUserVerifiedAsync(userId)).ReturnsAsync(false); _authRepositoryMock.Setup(x => x.IsUserVerifiedAsync(userId)).ReturnsAsync(false);
_tokenServiceMock.Setup(x => x.GenerateConfirmationToken(user)).Returns("fresh-token"); _tokenServiceMock.Setup(x => x.GenerateConfirmationToken(user.UserAccountId, user.Username)).Returns("fresh-token");
await _handler.Handle(new ResendConfirmationEmailCommand(userId), CancellationToken.None); await _handler.Handle(new ResendConfirmationEmailCommand(userId), CancellationToken.None);

View File

@@ -61,10 +61,10 @@ public class LoginHandlerTests
.Setup(x => x.Verify(It.IsAny<string>(), It.IsAny<string>())) .Setup(x => x.Verify(It.IsAny<string>(), It.IsAny<string>()))
.Returns(true); .Returns(true);
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateAccessToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("access-token"); .Returns("access-token");
_tokenServiceMock _tokenServiceMock
.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>())) .Setup(x => x.GenerateRefreshToken(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns("refresh-token"); .Returns("refresh-token");
LoginPayload result = await _handler.Handle( LoginPayload result = await _handler.Handle(

View File

@@ -35,8 +35,8 @@ public class LoginHandler(
if (!passwordInfrastructure.Verify(request.Password, activeCred.Hash)) if (!passwordInfrastructure.Verify(request.Password, activeCred.Hash))
throw new UnauthorizedException("Invalid username or password."); throw new UnauthorizedException("Invalid username or password.");
string accessToken = tokenService.GenerateAccessToken(user); string accessToken = tokenService.GenerateAccessToken(user.UserAccountId, user.Username);
string refreshToken = tokenService.GenerateRefreshToken(user); string refreshToken = tokenService.GenerateRefreshToken(user.UserAccountId, user.Username);
return new LoginPayload(user.UserAccountId, user.Username, refreshToken, accessToken); return new LoginPayload(user.UserAccountId, user.Username, refreshToken, accessToken);
} }

View File

@@ -48,9 +48,18 @@ public class RegisterUserHandler(
} }
); );
string accessToken = tokenService.GenerateAccessToken(createdUser); string accessToken = tokenService.GenerateAccessToken(
string refreshToken = tokenService.GenerateRefreshToken(createdUser); createdUser.UserAccountId,
string confirmationToken = tokenService.GenerateConfirmationToken(createdUser); createdUser.Username
);
string refreshToken = tokenService.GenerateRefreshToken(
createdUser.UserAccountId,
createdUser.Username
);
string confirmationToken = tokenService.GenerateConfirmationToken(
createdUser.UserAccountId,
createdUser.Username
);
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken)) if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken))
return new RegistrationPayload( return new RegistrationPayload(

View File

@@ -32,7 +32,10 @@ public class ResendConfirmationEmailHandler(
if (await authRepository.IsUserVerifiedAsync(request.UserId)) if (await authRepository.IsUserVerifiedAsync(request.UserId))
return; // Already confirmed, no-op return; // Already confirmed, no-op
string confirmationToken = tokenService.GenerateConfirmationToken(user); string confirmationToken = tokenService.GenerateConfirmationToken(
user.UserAccountId,
user.Username
);
await mediator.Send( await mediator.Send(
new SendResendConfirmationEmailCommand(user.FirstName, user.Email, confirmationToken), new SendResendConfirmationEmailCommand(user.FirstName, user.Email, confirmationToken),
cancellationToken cancellationToken

View File

@@ -48,19 +48,19 @@ public interface ITokenService
/// Generates a signed access token for the given user, expiring after /// Generates a signed access token for the given user, expiring after
/// <see cref="TokenServiceExpirationHours.AccessTokenHours" /> hours. /// <see cref="TokenServiceExpirationHours.AccessTokenHours" /> hours.
/// </summary> /// </summary>
string GenerateAccessToken(UserAccount user); string GenerateAccessToken(Guid userId, string username);
/// <summary> /// <summary>
/// Generates a signed refresh token for the given user, expiring after /// Generates a signed refresh token for the given user, expiring after
/// <see cref="TokenServiceExpirationHours.RefreshTokenHours" /> hours. /// <see cref="TokenServiceExpirationHours.RefreshTokenHours" /> hours.
/// </summary> /// </summary>
string GenerateRefreshToken(UserAccount user); string GenerateRefreshToken(Guid userId, string username);
/// <summary> /// <summary>
/// Generates a signed confirmation token for the given user, expiring after /// Generates a signed confirmation token for the given user, expiring after
/// <see cref="TokenServiceExpirationHours.ConfirmationTokenHours" /> hours. /// <see cref="TokenServiceExpirationHours.ConfirmationTokenHours" /> hours.
/// </summary> /// </summary>
string GenerateConfirmationToken(UserAccount user); string GenerateConfirmationToken(Guid userId, string username);
/// <summary>Validates an access token's signature and expiration and extracts the caller's identity.</summary> /// <summary>Validates an access token's signature and expiration and extracts the caller's identity.</summary>

View File

@@ -49,40 +49,30 @@ public class TokenService : ITokenService
} }
/// <inheritdoc /> /// <inheritdoc />
public string GenerateAccessToken(UserAccount user) public string GenerateAccessToken(Guid userId, string username)
{ {
DateTime expiresAt = DateTime.UtcNow.AddHours(TokenServiceExpirationHours.AccessTokenHours); DateTime expiresAt = DateTime.UtcNow.AddHours(TokenServiceExpirationHours.AccessTokenHours);
return _tokenInfrastructure.GenerateJwt( return _tokenInfrastructure.GenerateJwt(userId, username, expiresAt, _accessTokenSecret);
user.UserAccountId,
user.Username,
expiresAt,
_accessTokenSecret
);
} }
/// <inheritdoc /> /// <inheritdoc />
public string GenerateRefreshToken(UserAccount user) public string GenerateRefreshToken(Guid userId, string username)
{ {
DateTime expiresAt = DateTime.UtcNow.AddHours( DateTime expiresAt = DateTime.UtcNow.AddHours(
TokenServiceExpirationHours.RefreshTokenHours TokenServiceExpirationHours.RefreshTokenHours
); );
return _tokenInfrastructure.GenerateJwt( return _tokenInfrastructure.GenerateJwt(userId, username, expiresAt, _refreshTokenSecret);
user.UserAccountId,
user.Username,
expiresAt,
_refreshTokenSecret
);
} }
/// <inheritdoc /> /// <inheritdoc />
public string GenerateConfirmationToken(UserAccount user) public string GenerateConfirmationToken(Guid userId, string username)
{ {
DateTime expiresAt = DateTime.UtcNow.AddHours( DateTime expiresAt = DateTime.UtcNow.AddHours(
TokenServiceExpirationHours.ConfirmationTokenHours TokenServiceExpirationHours.ConfirmationTokenHours
); );
return _tokenInfrastructure.GenerateJwt( return _tokenInfrastructure.GenerateJwt(
user.UserAccountId, userId,
user.Username, username,
expiresAt, expiresAt,
_confirmationTokenSecret _confirmationTokenSecret
); );
@@ -114,8 +104,8 @@ public class TokenService : ITokenService
if (user == null) if (user == null)
throw new UnauthorizedException("User account not found"); throw new UnauthorizedException("User account not found");
string newAccess = GenerateAccessToken(user); string newAccess = GenerateAccessToken(user.UserAccountId, user.Username);
string newRefresh = GenerateRefreshToken(user); string newRefresh = GenerateRefreshToken(user.UserAccountId, user.Username);
return new RefreshTokenResult(user, newRefresh, newAccess); return new RefreshTokenResult(user, newRefresh, newAccess);
} }