1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00

feat(prelogin): [Auth/PM-27062] Prelogin New Response (#6577)

* feat(prelogin): [PM-27062] Prelogin New Response - Initial changes to support new data coming back from prelogin.

* test(prelogin): [PM-27062] Prelogin New Response - Added tests.
This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-11-14 14:33:31 -05:00
committed by GitHub
parent b4c7ab8773
commit 92e511284b
6 changed files with 175 additions and 47 deletions

View File

@@ -75,7 +75,7 @@ public class AccountsControllerTests : IDisposable
}
[Fact]
public async Task PostPrelogin_WhenUserExists_ShouldReturnUserKdfInfo()
public async Task PostPasswordPrelogin_WhenUserExists_ShouldReturnUserKdfInfo()
{
var userKdfInfo = new UserKdfInformation
{
@@ -84,30 +84,113 @@ public class AccountsControllerTests : IDisposable
};
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(userKdfInfo);
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
var response = await _sut.PostPasswordPrelogin(new PasswordPreloginRequestModel { Email = "user@example.com" });
Assert.Equal(userKdfInfo.Kdf, response.Kdf);
Assert.Equal(userKdfInfo.KdfIterations, response.KdfIterations);
}
[Fact]
public async Task PostPrelogin_WhenUserDoesNotExistAndNoDefaultKdfHmacKeySet_ShouldDefaultToPBKDF()
public async Task PostPrelogin_And_PostPasswordPrelogin_ShouldUseSamePreloginLogic()
{
// Arrange: No user exists and no default HMAC key to force default path
var email = "same-user@example.com";
SetDefaultKdfHmacKey(null);
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null));
// Act
var legacyResponse = await _sut.PostPrelogin(new PasswordPreloginRequestModel { Email = email });
var newResponse = await _sut.PostPasswordPrelogin(new PasswordPreloginRequestModel { Email = email });
// Assert: Both endpoints yield identical results, implying shared logic path
Assert.Equal(legacyResponse.Kdf, newResponse.Kdf);
Assert.Equal(legacyResponse.KdfIterations, newResponse.KdfIterations);
Assert.Equal(legacyResponse.KdfMemory, newResponse.KdfMemory);
Assert.Equal(legacyResponse.KdfParallelism, newResponse.KdfParallelism);
Assert.Equal(legacyResponse.Salt, newResponse.Salt);
Assert.NotNull(legacyResponse.KdfSettings);
Assert.NotNull(newResponse.KdfSettings);
Assert.Equal(legacyResponse.KdfSettings!.KdfType, newResponse.KdfSettings!.KdfType);
Assert.Equal(legacyResponse.KdfSettings!.Iterations, newResponse.KdfSettings!.Iterations);
Assert.Equal(legacyResponse.KdfSettings!.Memory, newResponse.KdfSettings!.Memory);
Assert.Equal(legacyResponse.KdfSettings!.Parallelism, newResponse.KdfSettings!.Parallelism);
// Both methods should consult the repository once each with the same email
await _userRepository.Received(2).GetKdfInformationByEmailAsync(Arg.Is<string>(e => e == email));
}
[Fact]
public async Task PostPasswordPrelogin_WhenUserExists_ReturnsNewFieldsAlignedWithLegacy_Argon2()
{
var email = "user@example.com";
var userKdfInfo = new UserKdfInformation
{
Kdf = KdfType.Argon2id,
KdfIterations = AuthConstants.ARGON2_ITERATIONS.Default,
KdfMemory = AuthConstants.ARGON2_MEMORY.Default,
KdfParallelism = AuthConstants.ARGON2_PARALLELISM.Default
};
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(userKdfInfo);
var response = await _sut.PostPasswordPrelogin(new PasswordPreloginRequestModel { Email = email });
// New fields exist and match repository values
Assert.NotNull(response.KdfSettings);
Assert.Equal(userKdfInfo.Kdf, response.KdfSettings!.KdfType);
Assert.Equal(userKdfInfo.KdfIterations, response.KdfSettings!.Iterations);
Assert.Equal(userKdfInfo.KdfMemory, response.KdfSettings!.Memory);
Assert.Equal(userKdfInfo.KdfParallelism, response.KdfSettings!.Parallelism);
// New and legacy fields are aligned during migration
Assert.Equal(response.Kdf, response.KdfSettings!.KdfType);
Assert.Equal(response.KdfIterations, response.KdfSettings!.Iterations);
Assert.Equal(response.KdfMemory, response.KdfSettings!.Memory);
Assert.Equal(response.KdfParallelism, response.KdfSettings!.Parallelism);
// Salt is set to the input email during migration
Assert.Equal(email, response.Salt);
}
[Fact]
public async Task PostPasswordPrelogin_WhenUserDoesNotExistAndNoDefaultKdfHmacKeySet_ShouldDefaultToPBKDF()
{
SetDefaultKdfHmacKey(null);
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null));
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
var response = await _sut.PostPasswordPrelogin(new PasswordPreloginRequestModel { Email = "user@example.com" });
Assert.Equal(KdfType.PBKDF2_SHA256, response.Kdf);
Assert.Equal(AuthConstants.PBKDF2_ITERATIONS.Default, response.KdfIterations);
}
[Fact]
public async Task PostPasswordPrelogin_NoUser_NoDefaultHmacKey_ReturnsAlignedNewFieldsAndSalt()
{
var email = "user@example.com";
SetDefaultKdfHmacKey(null);
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null));
var response = await _sut.PostPasswordPrelogin(new PasswordPreloginRequestModel { Email = email });
// New fields exist
Assert.NotNull(response.KdfSettings);
// New and legacy fields are aligned during migration
Assert.Equal(response.Kdf, response.KdfSettings!.KdfType);
Assert.Equal(response.KdfIterations, response.KdfSettings!.Iterations);
Assert.Equal(response.KdfMemory, response.KdfSettings!.Memory);
Assert.Equal(response.KdfParallelism, response.KdfSettings!.Parallelism);
// Salt is set to the input email during migration
Assert.Equal(email, response.Salt);
}
[Theory]
[BitAutoData]
public async Task PostPrelogin_WhenUserDoesNotExistAndDefaultKdfHmacKeyIsSet_ShouldComputeHmacAndReturnExpectedKdf(string email)
public async Task PostPasswordPrelogin_WhenUserDoesNotExistAndDefaultKdfHmacKeyIsSet_ShouldComputeHmacAndReturnExpectedKdf(string email)
{
// Arrange:
var defaultKey = Encoding.UTF8.GetBytes("my-secret-key");
var defaultKey = "my-secret-key"u8.ToArray();
SetDefaultKdfHmacKey(defaultKey);
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null));
@@ -122,7 +205,7 @@ public class AccountsControllerTests : IDisposable
var expectedKdf = defaultKdfResults[expectedIndex];
// Act
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = email });
var response = await _sut.PostPasswordPrelogin(new PasswordPreloginRequestModel { Email = email });
// Assert: Ensure the returned KDF matches the expected one from the computed hash
Assert.Equal(expectedKdf.Kdf, response.Kdf);
@@ -132,6 +215,16 @@ public class AccountsControllerTests : IDisposable
Assert.Equal(expectedKdf.KdfMemory, response.KdfMemory);
Assert.Equal(expectedKdf.KdfParallelism, response.KdfParallelism);
}
// New and legacy fields are aligned during migration
Assert.NotNull(response.KdfSettings);
Assert.Equal(response.Kdf, response.KdfSettings!.KdfType);
Assert.Equal(response.KdfIterations, response.KdfSettings!.Iterations);
Assert.Equal(response.KdfMemory, response.KdfSettings!.Memory);
Assert.Equal(response.KdfParallelism, response.KdfSettings!.Parallelism);
// Salt is set to the input email during migration
Assert.Equal(email, response.Salt);
}
[Theory]