1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -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

@ -1,4 +1,5 @@
using System.Text.Json;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Identity.TokenProviders;
@ -44,7 +45,7 @@ public interface ITwoFactorAuthenticationValidator
/// <param name="twoFactorProviderType">Two Factor Provider to use to verify the token</param>
/// <param name="token">secret passed from the user and consumed by the two-factor provider's verify method</param>
/// <returns>boolean</returns>
Task<bool> VerifyTwoFactor(User user, Organization organization, TwoFactorProviderType twoFactorProviderType, string token);
Task<bool> VerifyTwoFactorAsync(User user, Organization organization, TwoFactorProviderType twoFactorProviderType, string token);
}
public class TwoFactorAuthenticationValidator(
@ -139,7 +140,7 @@ public class TwoFactorAuthenticationValidator(
return twoFactorResultDict;
}
public async Task<bool> VerifyTwoFactor(
public async Task<bool> VerifyTwoFactorAsync(
User user,
Organization organization,
TwoFactorProviderType type,
@ -154,24 +155,39 @@ public class TwoFactorAuthenticationValidator(
return false;
}
switch (type)
if (_featureService.IsEnabled(FeatureFlagKeys.RecoveryCodeLogin))
{
case TwoFactorProviderType.Authenticator:
case TwoFactorProviderType.Email:
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.YubiKey:
case TwoFactorProviderType.WebAuthn:
case TwoFactorProviderType.Remember:
if (type != TwoFactorProviderType.Remember &&
!await _userService.TwoFactorProviderIsEnabledAsync(type, user))
{
return false;
}
return await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(type), token);
default:
return false;
if (type is TwoFactorProviderType.RecoveryCode)
{
return await _userService.RecoverTwoFactorAsync(user, token);
}
}
// These cases we want to always return false, U2f is deprecated and OrganizationDuo
// uses a different flow than the other two factor providers, it follows the same
// structure of a UserTokenProvider but has it's logic ran outside the usual token
// provider flow. See IOrganizationDuoUniversalTokenProvider.cs
if (type is TwoFactorProviderType.U2f or TwoFactorProviderType.OrganizationDuo)
{
return false;
}
// Now we are concerning the rest of the Two Factor Provider Types
// The intent of this check is to make sure that the user is using a 2FA provider that
// is enabled and allowed by their premium status. The exception for Remember
// is because it is a "special" 2FA type that isn't ever explicitly
// enabled by a user, so we can't check the user's 2FA providers to see if they're
// enabled. We just have to check if the token is valid.
if (type != TwoFactorProviderType.Remember &&
!await _userService.TwoFactorProviderIsEnabledAsync(type, user))
{
return false;
}
// Finally, verify the token based on the provider type.
return await _userManager.VerifyTwoFactorTokenAsync(
user, CoreHelpers.CustomProviderName(type), token);
}
private async Task<List<KeyValuePair<TwoFactorProviderType, TwoFactorProvider>>> GetEnabledTwoFactorProvidersAsync(