diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index ad38b5965..5cda53e20 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -303,6 +303,7 @@ namespace Bit.App.Pages if (storedKeyHash != null) { passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key); + enforcedMasterPasswordOptions = await _policyService.GetMasterPasswordPolicyOptions(); } else { @@ -310,7 +311,7 @@ namespace Bit.App.Pages var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); var request = new PasswordVerificationRequest(); request.MasterPasswordHash = keyHash; - + try { var response = await _apiService.PostAccountVerifyPasswordAsync(request); @@ -336,7 +337,8 @@ namespace Bit.App.Pages await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey)); } - if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions)) + if (await _policyService.RequirePasswordChangeOnLoginAsync(MasterPassword, _email, + enforcedMasterPasswordOptions)) { // Save the ForcePasswordResetReason to force a password reset after unlock await _stateService.SetForcePasswordResetReasonAsync( @@ -367,37 +369,6 @@ namespace Bit.App.Pages } } - /// - /// Checks if the master password requires updating to meet the enforced policy requirements - /// - /// - private async Task RequirePasswordChangeAsync(MasterPasswordPolicyOptions options = null) - { - // If no policy options are provided, attempt to load them from the policy service - var enforcedOptions = options ?? await _policyService.GetMasterPasswordPolicyOptions(); - - // No policy to enforce on login/unlock - if (!(enforcedOptions is { EnforceOnLogin: true })) - { - return false; - } - - var strength = _passwordGenerationService.PasswordStrength( - MasterPassword, _passwordGenerationService.GetPasswordStrengthUserInput(_email))?.Score; - - if (!strength.HasValue) - { - _logger.Error("Unable to evaluate master password strength during unlock"); - return false; - } - - return !await _policyService.EvaluateMasterPassword( - strength.Value, - MasterPassword, - enforcedOptions - ); - } - public async Task LogOutAsync() { var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LogoutConfirmation, diff --git a/src/Core/Abstractions/IPolicyService.cs b/src/Core/Abstractions/IPolicyService.cs index caabeca3d..23d833b75 100644 --- a/src/Core/Abstractions/IPolicyService.cs +++ b/src/Core/Abstractions/IPolicyService.cs @@ -21,5 +21,14 @@ namespace Bit.Core.Abstractions Task PolicyAppliesToUser(PolicyType policyType, Func policyFilter = null, string userId = null); int? GetPolicyInt(Policy policy, string key); Task ShouldShowVaultFilterAsync(); + + /// + /// Checks if the master password requires updating to meet the enforced policy requirements + /// + /// + /// The user's email, used to help evaluate password strength + /// + Task RequirePasswordChangeOnLoginAsync(string masterPassword, string email, + MasterPasswordPolicyOptions enforcedOptions); } } diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index a9a0170c0..aae2d9c3e 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -149,7 +149,7 @@ namespace Bit.Core.Services var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization); var result = await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null, captchaToken); - if (await RequirePasswordChange(email, masterPassword)) + if (await _policyService.RequirePasswordChangeOnLoginAsync(masterPassword, email, _masterPasswordPolicy)) { if (!string.IsNullOrEmpty(_authedUserId)) { @@ -168,28 +168,6 @@ namespace Bit.Core.Services return result; } - /// - /// Evaluates the supplied master password against the master password policy provided by the Identity response. - /// - /// - /// - /// True if the master password does NOT meet any policy requirements, false otherwise (or if no policy present) - private async Task RequirePasswordChange(string email, string masterPassword) - { - // No policy with EnforceOnLogin enabled, we're done. - if (!(_masterPasswordPolicy is { EnforceOnLogin: true })) - { - return false; - } - - var passwordStrength = _passwordGenerationService.PasswordStrength( - masterPassword, - _passwordGenerationService.GetPasswordStrengthUserInput(email) - ).Score; - - return !await _policyService.EvaluateMasterPassword(passwordStrength, masterPassword, _masterPasswordPolicy); - } - public async Task LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered) { var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey); diff --git a/src/Core/Services/PolicyService.cs b/src/Core/Services/PolicyService.cs index 218a1ba99..b0d81acac 100644 --- a/src/Core/Services/PolicyService.cs +++ b/src/Core/Services/PolicyService.cs @@ -14,15 +14,18 @@ namespace Bit.Core.Services { private readonly IStateService _stateService; private readonly IOrganizationService _organizationService; + private readonly IPasswordGenerationService _passwordGenerationService; private IEnumerable _policyCache; public PolicyService( IStateService stateService, - IOrganizationService organizationService) + IOrganizationService organizationService, + IPasswordGenerationService passwordGenerationService) { _stateService = stateService; _organizationService = organizationService; + _passwordGenerationService = passwordGenerationService; } public void ClearCache() @@ -181,6 +184,30 @@ namespace Bit.Core.Services return true; } + + public async Task RequirePasswordChangeOnLoginAsync(string masterPassword, string email, + MasterPasswordPolicyOptions enforcedOptions) + { + // No policy to enforce on login/unlock + if (!(enforcedOptions is { EnforceOnLogin: true })) + { + return false; + } + + var strength = _passwordGenerationService.PasswordStrength( + masterPassword, _passwordGenerationService.GetPasswordStrengthUserInput(email))?.Score; + + if (!strength.HasValue) + { + return false; + } + + return !await EvaluateMasterPassword( + strength.Value, + masterPassword, + enforcedOptions + ); + } public Tuple GetResetPasswordPolicyOptions(IEnumerable policies, string orgId)