1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

feat(2FA): [PM-17129] Login with 2FA Recovery Code

* feat(2FA): [PM-17129] Login with 2FA Recovery Code - Login with Recovery Code working.

* feat(2FA): [PM-17129] Login with 2FA Recovery Code - Feature flagged implementation.

* style(2FA): [PM-17129] Login with 2FA Recovery Code - Code cleanup.

* test(2FA): [PM-17129] Login with 2FA Recovery Code - Tests.
This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-02-13 15:51:36 -05:00
committed by GitHub
parent 465549b812
commit ac6bc40d85
10 changed files with 220 additions and 76 deletions

View File

@ -730,6 +730,46 @@ public class UserServiceTests
.RemoveAsync(Arg.Any<string>());
}
[Theory, BitAutoData]
public async Task RecoverTwoFactorAsync_CorrectCode_ReturnsTrueAndProcessesPolicies(
User user, SutProvider<UserService> sutProvider)
{
// Arrange
var recoveryCode = "1234";
user.TwoFactorRecoveryCode = recoveryCode;
// Act
var response = await sutProvider.Sut.RecoverTwoFactorAsync(user, recoveryCode);
// Assert
Assert.True(response);
Assert.Null(user.TwoFactorProviders);
// Make sure a new code was generated for the user
Assert.NotEqual(recoveryCode, user.TwoFactorRecoveryCode);
await sutProvider.GetDependency<IMailService>()
.Received(1)
.SendRecoverTwoFactorEmail(Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogUserEventAsync(user.Id, EventType.User_Recovered2fa);
}
[Theory, BitAutoData]
public async Task RecoverTwoFactorAsync_IncorrectCode_ReturnsFalse(
User user, SutProvider<UserService> sutProvider)
{
// Arrange
var recoveryCode = "1234";
user.TwoFactorRecoveryCode = "4567";
// Act
var response = await sutProvider.Sut.RecoverTwoFactorAsync(user, recoveryCode);
// Assert
Assert.False(response);
Assert.NotNull(user.TwoFactorProviders);
}
private static void SetupUserAndDevice(User user,
bool shouldHavePassword)
{

View File

@ -105,7 +105,7 @@ public class BaseRequestValidatorTests
// Assert
await _eventService.Received(1)
.LogUserEventAsync(context.CustomValidatorRequestContext.User.Id,
Core.Enums.EventType.User_FailedLogIn);
EventType.User_FailedLogIn);
Assert.True(context.GrantResult.IsError);
Assert.Equal("Username or password is incorrect. Try again.", errorResponse.Message);
}

View File

@ -1,4 +1,5 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Auth.Models.Business.Tokenables;
@ -328,7 +329,7 @@ public class TwoFactorAuthenticationValidatorTests
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
// Act
var result = await _sut.VerifyTwoFactor(
var result = await _sut.VerifyTwoFactorAsync(
user, null, TwoFactorProviderType.U2f, token);
// Assert
@ -348,7 +349,7 @@ public class TwoFactorAuthenticationValidatorTests
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
// Act
var result = await _sut.VerifyTwoFactor(
var result = await _sut.VerifyTwoFactorAsync(
user, null, TwoFactorProviderType.Email, token);
// Assert
@ -368,7 +369,7 @@ public class TwoFactorAuthenticationValidatorTests
_userManager.TWO_FACTOR_PROVIDERS = ["OrganizationDuo"];
// Act
var result = await _sut.VerifyTwoFactor(
var result = await _sut.VerifyTwoFactorAsync(
user, null, TwoFactorProviderType.OrganizationDuo, token);
// Assert
@ -394,7 +395,7 @@ public class TwoFactorAuthenticationValidatorTests
_userManager.TWO_FACTOR_TOKEN_VERIFIED = true;
// Act
var result = await _sut.VerifyTwoFactor(user, null, providerType, token);
var result = await _sut.VerifyTwoFactorAsync(user, null, providerType, token);
// Assert
Assert.True(result);
@ -419,7 +420,7 @@ public class TwoFactorAuthenticationValidatorTests
_userManager.TWO_FACTOR_TOKEN_VERIFIED = false;
// Act
var result = await _sut.VerifyTwoFactor(user, null, providerType, token);
var result = await _sut.VerifyTwoFactorAsync(user, null, providerType, token);
// Assert
Assert.False(result);
@ -445,13 +446,56 @@ public class TwoFactorAuthenticationValidatorTests
organization.Enabled = true;
// Act
var result = await _sut.VerifyTwoFactor(
var result = await _sut.VerifyTwoFactorAsync(
user, organization, providerType, token);
// Assert
Assert.True(result);
}
[Theory]
[BitAutoData(TwoFactorProviderType.RecoveryCode)]
public async void VerifyTwoFactorAsync_RecoveryCode_ValidToken_ReturnsTrue(
TwoFactorProviderType providerType,
User user,
Organization organization)
{
var token = "1234";
user.TwoFactorRecoveryCode = token;
_userService.RecoverTwoFactorAsync(Arg.Is(user), Arg.Is(token)).Returns(true);
_featureService.IsEnabled(FeatureFlagKeys.RecoveryCodeLogin).Returns(true);
// Act
var result = await _sut.VerifyTwoFactorAsync(
user, organization, providerType, token);
// Assert
Assert.True(result);
}
[Theory]
[BitAutoData(TwoFactorProviderType.RecoveryCode)]
public async void VerifyTwoFactorAsync_RecoveryCode_InvalidToken_ReturnsFalse(
TwoFactorProviderType providerType,
User user,
Organization organization)
{
// Arrange
var token = "1234";
user.TwoFactorRecoveryCode = token;
_userService.RecoverTwoFactorAsync(Arg.Is(user), Arg.Is(token)).Returns(false);
_featureService.IsEnabled(FeatureFlagKeys.RecoveryCodeLogin).Returns(true);
// Act
var result = await _sut.VerifyTwoFactorAsync(
user, organization, providerType, token);
// Assert
Assert.False(result);
}
private static UserManagerTestWrapper<User> SubstituteUserManager()
{
return new UserManagerTestWrapper<User>(