Harden password reset and email send flows

This commit is contained in:
2026-03-28 14:17:12 +01:00
parent 25ae6b94e9
commit 9f949ee9df
15 changed files with 205 additions and 57 deletions
@@ -22,7 +22,7 @@ public sealed class AuthAndSystemControllerTests
userManager.Setup(x => x.GetUserAsync(It.IsAny<System.Security.Claims.ClaimsPrincipal>())).ReturnsAsync(user);
userManager.Setup(x => x.UpdateAsync(user)).ReturnsAsync(IdentityResult.Success);
var controller = new AuthController(BuildConfig(), userManager.Object, Mock.Of<ITokenService>(), Mock.Of<IAppEmailSender>(), Mock.Of<IGoogleTokenValidator>());
var controller = new AuthController(BuildConfig(), userManager.Object, Mock.Of<ITokenService>(), Mock.Of<IAppEmailSender>(), Mock.Of<IGoogleTokenValidator>(), NullLogger<AuthController>.Instance);
var result = await controller.UpdateProfile(new AuthController.UpdateProfileRequest(" new@example.com ", " newuser ", " Ada ", " Lovelace ", " Ada L. ", null, null));
@@ -34,6 +34,37 @@ public sealed class AuthAndSystemControllerTests
Assert.Equal("Ada L.", user.DisplayName);
}
[Fact]
public async Task Request_password_reset_returns_service_unavailable_when_email_send_fails()
{
var user = new ApplicationUser { Email = "person@example.com", UserName = "person@example.com" };
var userManager = CreateUserManager();
userManager.Setup(x => x.FindByEmailAsync("person@example.com")).ReturnsAsync(user);
userManager.Setup(x => x.GeneratePasswordResetTokenAsync(user)).ReturnsAsync("reset-token");
var emailSender = new Mock<IAppEmailSender>();
emailSender
.Setup(x => x.SendAsync(user.Email!, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException("SMTP unavailable"));
var controller = new AuthController(BuildConfig(), userManager.Object, Mock.Of<ITokenService>(), emailSender.Object, Mock.Of<IGoogleTokenValidator>(), NullLogger<AuthController>.Instance)
{
ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext()
}
};
controller.Request.Scheme = "https";
controller.Request.Host = new HostString("jobtracker.test");
var result = await controller.RequestPasswordReset(new AuthController.RequestPasswordResetRequest("person@example.com"), CancellationToken.None);
var problem = Assert.IsType<ObjectResult>(result);
Assert.Equal(StatusCodes.Status503ServiceUnavailable, problem.StatusCode);
var details = Assert.IsType<ProblemDetails>(problem.Value);
Assert.Equal("Email delivery unavailable", details.Title);
}
[Fact]
public void Me_result_includes_google_link_details_for_local_users()
{