mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -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:

committed by
GitHub

parent
465549b812
commit
ac6bc40d85
@ -10,4 +10,5 @@ public enum TwoFactorProviderType : byte
|
||||
Remember = 5,
|
||||
OrganizationDuo = 6,
|
||||
WebAuthn = 7,
|
||||
RecoveryCode = 8,
|
||||
}
|
||||
|
@ -170,6 +170,7 @@ public static class FeatureFlagKeys
|
||||
public const string EnablePMAuthenticatorSync = "enable-pm-bwa-sync";
|
||||
public const string P15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal";
|
||||
public const string AndroidMutualTls = "mutual-tls";
|
||||
public const string RecoveryCodeLogin = "pm-17128-recovery-code-login";
|
||||
public const string PM3503_MobileAnonAddySelfHostAlias = "anon-addy-self-host-alias";
|
||||
|
||||
public static List<string> GetAllKeys()
|
||||
|
@ -22,7 +22,6 @@ public interface IUserService
|
||||
Task<IdentityResult> CreateUserAsync(User user, string masterPasswordHash);
|
||||
Task SendMasterPasswordHintAsync(string email);
|
||||
Task SendTwoFactorEmailAsync(User user);
|
||||
Task<bool> VerifyTwoFactorEmailAsync(User user, string token);
|
||||
Task<CredentialCreateOptions> StartWebAuthnRegistrationAsync(User user);
|
||||
Task<bool> DeleteWebAuthnKeyAsync(User user, int id);
|
||||
Task<bool> CompleteWebAuthRegistrationAsync(User user, int value, string name, AuthenticatorAttestationRawResponse attestationResponse);
|
||||
@ -41,8 +40,6 @@ public interface IUserService
|
||||
Task<IdentityResult> RefreshSecurityStampAsync(User user, string masterPasswordHash);
|
||||
Task UpdateTwoFactorProviderAsync(User user, TwoFactorProviderType type, bool setEnabled = true, bool logEvent = true);
|
||||
Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type);
|
||||
Task<bool> RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode);
|
||||
Task<string> GenerateUserTokenAsync(User user, string tokenProvider, string purpose);
|
||||
Task<IdentityResult> DeleteAsync(User user);
|
||||
Task<IdentityResult> DeleteAsync(User user, string token);
|
||||
Task SendDeleteConfirmationAsync(string email);
|
||||
@ -55,9 +52,7 @@ public interface IUserService
|
||||
Task CancelPremiumAsync(User user, bool? endOfPeriod = null);
|
||||
Task ReinstatePremiumAsync(User user);
|
||||
Task EnablePremiumAsync(Guid userId, DateTime? expirationDate);
|
||||
Task EnablePremiumAsync(User user, DateTime? expirationDate);
|
||||
Task DisablePremiumAsync(Guid userId, DateTime? expirationDate);
|
||||
Task DisablePremiumAsync(User user, DateTime? expirationDate);
|
||||
Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate);
|
||||
Task<UserLicense> GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null,
|
||||
int? version = null);
|
||||
@ -91,9 +86,26 @@ public interface IUserService
|
||||
|
||||
void SetTwoFactorProvider(User user, TwoFactorProviderType type, bool setEnabled = true);
|
||||
|
||||
[Obsolete("To be removed when the feature flag pm-17128-recovery-code-login is removed PM-18175.")]
|
||||
Task<bool> RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the user is a legacy user. Legacy users use their master key as their encryption key.
|
||||
/// We force these users to the web to migrate their encryption scheme.
|
||||
/// This method is used by the TwoFactorAuthenticationValidator to recover two
|
||||
/// factor for a user. This allows users to be logged in after a successful recovery
|
||||
/// attempt.
|
||||
///
|
||||
/// This method logs the event, sends an email to the user, and removes two factor
|
||||
/// providers on the user account. This means that a user will have to accomplish
|
||||
/// new device verification on their account on new logins, if it is enabled for their user.
|
||||
/// </summary>
|
||||
/// <param name="recoveryCode">recovery code associated with the user logging in</param>
|
||||
/// <param name="user">The user to refresh the 2FA and Recovery Code on.</param>
|
||||
/// <returns>true if the recovery code is valid; false otherwise</returns>
|
||||
Task<bool> RecoverTwoFactorAsync(User user, string recoveryCode);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the user is a legacy user. Legacy users use their master key as their
|
||||
/// encryption key. We force these users to the web to migrate their encryption scheme.
|
||||
/// </summary>
|
||||
Task<bool> IsLegacyUser(string userId);
|
||||
|
||||
@ -101,7 +113,8 @@ public interface IUserService
|
||||
/// Indicates if the user is managed by any organization.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A user is considered managed by an organization if their email domain matches one of the verified domains of that organization, and the user is a member of it.
|
||||
/// A user is considered managed by an organization if their email domain matches one of the
|
||||
/// verified domains of that organization, and the user is a member of it.
|
||||
/// The organization must be enabled and able to have verified domains.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
|
@ -315,7 +315,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount");
|
||||
var token = await GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount");
|
||||
await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token);
|
||||
}
|
||||
|
||||
@ -868,6 +868,10 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be removed when the feature flag pm-17128-recovery-code-login is removed PM-18175.
|
||||
/// </summary>
|
||||
[Obsolete("Two Factor recovery is handled in the TwoFactorAuthenticationValidator.")]
|
||||
public async Task<bool> RecoverTwoFactorAsync(string email, string secret, string recoveryCode)
|
||||
{
|
||||
var user = await _userRepository.GetByEmailAsync(email);
|
||||
@ -897,6 +901,25 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> RecoverTwoFactorAsync(User user, string recoveryCode)
|
||||
{
|
||||
if (!CoreHelpers.FixedTimeEquals(
|
||||
user.TwoFactorRecoveryCode,
|
||||
recoveryCode.Replace(" ", string.Empty).Trim().ToLower()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
user.TwoFactorProviders = null;
|
||||
user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false);
|
||||
await SaveUserAsync(user);
|
||||
await _mailService.SendRecoverTwoFactorEmail(user.Email, DateTime.UtcNow, _currentContext.IpAddress);
|
||||
await _eventService.LogUserEventAsync(user.Id, EventType.User_Recovered2fa);
|
||||
await CheckPoliciesOnTwoFactorRemovalAsync(user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
|
||||
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license,
|
||||
TaxInfo taxInfo)
|
||||
@ -1081,7 +1104,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
await EnablePremiumAsync(user, expirationDate);
|
||||
}
|
||||
|
||||
public async Task EnablePremiumAsync(User user, DateTime? expirationDate)
|
||||
private async Task EnablePremiumAsync(User user, DateTime? expirationDate)
|
||||
{
|
||||
if (user != null && !user.Premium && user.Gateway.HasValue)
|
||||
{
|
||||
@ -1098,7 +1121,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
await DisablePremiumAsync(user, expirationDate);
|
||||
}
|
||||
|
||||
public async Task DisablePremiumAsync(User user, DateTime? expirationDate)
|
||||
private async Task DisablePremiumAsync(User user, DateTime? expirationDate)
|
||||
{
|
||||
if (user != null && user.Premium)
|
||||
{
|
||||
|
Reference in New Issue
Block a user