diff --git a/src/App/App.csproj b/src/App/App.csproj index 8dbcc000f..23b7a7d0e 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -143,6 +143,7 @@ + @@ -435,5 +436,6 @@ + diff --git a/src/App/Controls/PasswordStrengthProgressBar/IPasswordStrengthable.cs b/src/App/Controls/PasswordStrengthProgressBar/IPasswordStrengthable.cs new file mode 100644 index 000000000..b26c7468c --- /dev/null +++ b/src/App/Controls/PasswordStrengthProgressBar/IPasswordStrengthable.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Bit.App.Controls +{ + public interface IPasswordStrengthable + { + string Password { get; } + List UserInputs { get; } + } +} + diff --git a/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthCategory.cs b/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthCategory.cs new file mode 100644 index 000000000..8752b0077 --- /dev/null +++ b/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthCategory.cs @@ -0,0 +1,17 @@ +using Bit.Core.Attributes; + +namespace Bit.App.Controls +{ + public enum PasswordStrengthLevel + { + [LocalizableEnum("Weak")] + VeryWeak, + [LocalizableEnum("Weak")] + Weak, + [LocalizableEnum("Good")] + Good, + [LocalizableEnum("Strong")] + Strong + } +} + diff --git a/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml b/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml new file mode 100644 index 000000000..76c224631 --- /dev/null +++ b/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml.cs b/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml.cs new file mode 100644 index 000000000..4508bd934 --- /dev/null +++ b/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml.cs @@ -0,0 +1,107 @@ +using System; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public partial class PasswordStrengthProgressBar : StackLayout + { + public static readonly BindableProperty PasswordStrengthLevelProperty = BindableProperty.Create( + nameof(PasswordStrengthLevel), + typeof(PasswordStrengthLevel), + typeof(PasswordStrengthProgressBar), + propertyChanged: OnControlPropertyChanged); + + public static readonly BindableProperty VeryWeakColorProperty = BindableProperty.Create( + nameof(VeryWeakColor), + typeof(Color), + typeof(PasswordStrengthProgressBar), + propertyChanged: OnControlPropertyChanged); + + public static readonly BindableProperty WeakColorProperty = BindableProperty.Create( + nameof(WeakColor), + typeof(Color), + typeof(PasswordStrengthProgressBar), + propertyChanged: OnControlPropertyChanged); + + public static readonly BindableProperty GoodColorProperty = BindableProperty.Create( + nameof(GoodColor), + typeof(Color), + typeof(PasswordStrengthProgressBar), + propertyChanged: OnControlPropertyChanged); + + public static readonly BindableProperty StrongColorProperty = BindableProperty.Create( + nameof(StrongColor), + typeof(Color), + typeof(PasswordStrengthProgressBar), + propertyChanged: OnControlPropertyChanged); + + public PasswordStrengthLevel? PasswordStrengthLevel + { + get { return (PasswordStrengthLevel?)GetValue(PasswordStrengthLevelProperty); } + set { SetValue(PasswordStrengthLevelProperty, value); } + } + + public Color VeryWeakColor + { + get { return (Color)GetValue(VeryWeakColorProperty); } + set { SetValue(VeryWeakColorProperty, value); } + } + + public Color WeakColor + { + get { return (Color)GetValue(WeakColorProperty); } + set { SetValue(WeakColorProperty, value); } + } + + public Color GoodColor + { + get { return (Color)GetValue(GoodColorProperty); } + set { SetValue(GoodColorProperty, value); } + } + + public Color StrongColor + { + get { return (Color)GetValue(StrongColorProperty); } + set { SetValue(StrongColorProperty, value); } + } + + public PasswordStrengthProgressBar() + { + InitializeComponent(); + SetBinding(PasswordStrengthProgressBar.PasswordStrengthLevelProperty, new Binding() { Path = nameof(PasswordStrengthViewModel.PasswordStrengthLevel) }); + } + + private static void OnControlPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + (bindable as PasswordStrengthProgressBar)?.UpdateColors(); + } + + public void UpdateColors() + { + if (_progressBar == null || _progressLabel == null) + { + return; + } + _progressBar.ProgressColor = GetColorForStrength(); + _progressLabel.TextColor = _progressBar.ProgressColor; + } + + private Color GetColorForStrength() + { + switch (PasswordStrengthLevel) + { + case Controls.PasswordStrengthLevel.VeryWeak: + return VeryWeakColor; + case Controls.PasswordStrengthLevel.Weak: + return WeakColor; + case Controls.PasswordStrengthLevel.Good: + return GoodColor; + case Controls.PasswordStrengthLevel.Strong: + return StrongColor; + default: + return Color.Transparent; + } + } + } +} + diff --git a/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthViewModel.cs b/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthViewModel.cs new file mode 100644 index 000000000..b022be4d1 --- /dev/null +++ b/src/App/Controls/PasswordStrengthProgressBar/PasswordStrengthViewModel.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class PasswordStrengthViewModel : ExtendedViewModel + { + private readonly IPasswordGenerationService _passwordGenerationService; + private readonly IPasswordStrengthable _passwordStrengthable; + private double _passwordStrength; + private Color _passwordColor; + private PasswordStrengthLevel? _passwordStrengthLevel; + + public PasswordStrengthViewModel(IPasswordStrengthable passwordStrengthable) + { + _passwordGenerationService = ServiceContainer.Resolve(); + _passwordStrengthable = passwordStrengthable; + } + + public double PasswordStrength + { + get => _passwordStrength; + set => SetProperty(ref _passwordStrength, value); + } + + public PasswordStrengthLevel? PasswordStrengthLevel + { + get => _passwordStrengthLevel; + set => SetProperty(ref _passwordStrengthLevel, value); + } + + public List GetPasswordStrengthUserInput(string email) => _passwordGenerationService.GetPasswordStrengthUserInput(email); + + public void CalculatePasswordStrength() + { + if (string.IsNullOrEmpty(_passwordStrengthable.Password)) + { + PasswordStrength = 0; + PasswordStrengthLevel = null; + return; + } + + var passwordStrength = _passwordGenerationService.PasswordStrength(_passwordStrengthable.Password, _passwordStrengthable.UserInputs); + // The passwordStrength.Score is 0..4, convertion was made to be used as a progress directly by the control 0..1 + PasswordStrength = (passwordStrength.Score + 1f) / 5f; + if (PasswordStrength <= 0.4f) + { + PasswordStrengthLevel = Controls.PasswordStrengthLevel.VeryWeak; + } + else if (PasswordStrength <= 0.6f) + { + PasswordStrengthLevel = Controls.PasswordStrengthLevel.Weak; + } + else if (PasswordStrength <= 0.8f) + { + PasswordStrengthLevel = Controls.PasswordStrengthLevel.Good; + } + else + { + PasswordStrengthLevel = Controls.PasswordStrengthLevel.Strong; + } + } + } +} + diff --git a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs index 81d16568e..5b8eae328 100644 --- a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs +++ b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs @@ -4,6 +4,8 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Resources; +using Bit.App.Utilities; +using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Models.Domain; using Bit.Core.Utilities; @@ -147,8 +149,8 @@ namespace Bit.App.Pages } if (IsPolicyInEffect) { - var userInput = await GetPasswordStrengthUserInput(); - var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput); + var userInputs = _passwordGenerationService.GetPasswordStrengthUserInput(await _stateService.GetEmailAsync()); + var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInputs); if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy)) { await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage, @@ -158,7 +160,7 @@ namespace Bit.App.Pages } else { - if (MasterPassword.Length < 8) + if (MasterPassword.Length < Constants.MasterPasswordMinimumChars) { await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage, AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok); @@ -174,19 +176,5 @@ namespace Bit.App.Pages return true; } - - private async Task> GetPasswordStrengthUserInput() - { - var email = await _stateService.GetEmailAsync(); - List userInput = null; - var atPosition = email.IndexOf('@'); - if (atPosition > -1) - { - var rx = new Regex("/[^A-Za-z0-9]/", RegexOptions.Compiled); - var data = rx.Split(email.Substring(0, atPosition).Trim().ToLower()); - userInput = new List(data); - } - return userInput; - } } } diff --git a/src/App/Pages/Accounts/RegisterPage.xaml b/src/App/Pages/Accounts/RegisterPage.xaml index b6db138f4..3902e3ca5 100644 --- a/src/App/Pages/Accounts/RegisterPage.xaml +++ b/src/App/Pages/Accounts/RegisterPage.xaml @@ -25,7 +25,7 @@ - + @@ -126,6 +137,17 @@ StyleClass="box-footer-label" /> + + + ("platformUtilsService"); _i18nService = ServiceContainer.Resolve("i18nService"); _environmentService = ServiceContainer.Resolve("environmentService"); + _auditService = ServiceContainer.Resolve(); PageTitle = AppResources.CreateAccount; TogglePasswordCommand = new Command(TogglePassword); ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword); SubmitCommand = new Command(async () => await SubmitAsync()); ShowTerms = !_platformUtilsService.IsSelfHost(); + PasswordStrengthViewModel = new PasswordStrengthViewModel(this); } public ICommand PoliciesClickCommand => new Command((url) => @@ -61,6 +71,34 @@ namespace Bit.App.Pages get => _acceptPolicies; set => SetProperty(ref _acceptPolicies, value); } + + public bool CheckExposedMasterPassword + { + get => _checkExposedMasterPassword; + set => SetProperty(ref _checkExposedMasterPassword, value); + } + + public string MasterPassword + { + get => _masterPassword; + set + { + SetProperty(ref _masterPassword, value); + PasswordStrengthViewModel.CalculatePasswordStrength(); + } + } + + public string Email + { + get => _email; + set => SetProperty(ref _email, value); + } + + public string Password => MasterPassword; + public List UserInputs => PasswordStrengthViewModel.GetPasswordStrengthUserInput(Email); + public string MasterPasswordMininumCharactersDescription => string.Format(AppResources.YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum, + Constants.MasterPasswordMinimumChars); + public PasswordStrengthViewModel PasswordStrengthViewModel { get; } public bool ShowTerms { get; set; } public Command SubmitCommand { get; } public Command TogglePasswordCommand { get; } @@ -68,13 +106,10 @@ namespace Bit.App.Pages public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow; public string Name { get; set; } - public string Email { get; set; } - public string MasterPassword { get; set; } public string ConfirmMasterPassword { get; set; } public string Hint { get; set; } public Action RegistrationSuccess { get; set; } public Action CloseAction { get; set; } - protected override II18nService i18nService => _i18nService; protected override IEnvironmentService environmentService => _environmentService; protected override IDeviceActionService deviceActionService => _deviceActionService; @@ -110,7 +145,7 @@ namespace Bit.App.Pages AppResources.Ok); return; } - if (MasterPassword.Length < 8) + if (MasterPassword.Length < Constants.MasterPasswordMinimumChars) { await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage, AppResources.AnErrorHasOccurred, AppResources.Ok); @@ -128,8 +163,10 @@ namespace Bit.App.Pages AppResources.AnErrorHasOccurred, AppResources.Ok); return; } - - // TODO: Password strength check? + if (await IsPasswordWeakOrExposed()) + { + return; + } if (showLoading) { @@ -160,6 +197,7 @@ namespace Bit.App.Pages }, CaptchaResponse = _captchaToken, }; + // TODO: org invite? try @@ -208,5 +246,43 @@ namespace Bit.App.Pages entry.Focus(); entry.CursorPosition = String.IsNullOrEmpty(ConfirmMasterPassword) ? 0 : ConfirmMasterPassword.Length; } + + private async Task IsPasswordWeakOrExposed() + { + try + { + var title = string.Empty; + var message = string.Empty; + var exposedPassword = CheckExposedMasterPassword ? await _auditService.PasswordLeakedAsync(MasterPassword) > 0 : false; + var weakPassword = PasswordStrengthViewModel.PasswordStrengthLevel <= PasswordStrengthLevel.Weak; + + if (exposedPassword && weakPassword) + { + title = AppResources.WeakAndExposedMasterPassword; + message = AppResources.WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription; + } + else if (exposedPassword) + { + title = AppResources.ExposedMasterPassword; + message = AppResources.PasswordFoundInADataBreachAlertDescription; + } + else if (weakPassword) + { + title = AppResources.WeakMasterPassword; + message = AppResources.WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount; + } + + if (exposedPassword || weakPassword) + { + return !await _platformUtilsService.ShowDialogAsync(message, title, AppResources.Yes, AppResources.No); + } + } + catch (Exception ex) + { + HandleException(ex); + } + + return false; + } } } diff --git a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs index b4c415959..06f056a6b 100644 --- a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs +++ b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Resources; +using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; @@ -137,8 +138,8 @@ namespace Bit.App.Pages } if (IsPolicyInEffect) { - var userInput = await GetPasswordStrengthUserInput(); - var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput); + var userInputs = _passwordGenerationService.GetPasswordStrengthUserInput(await _stateService.GetEmailAsync()); + var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInputs); if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy)) { await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle, @@ -148,7 +149,7 @@ namespace Bit.App.Pages } else { - if (MasterPassword.Length < 8) + if (MasterPassword.Length < Constants.MasterPasswordMinimumChars) { await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle, AppResources.MasterPasswordLengthValMessage, AppResources.Ok); diff --git a/src/App/Pages/BaseViewModel.cs b/src/App/Pages/BaseViewModel.cs index b8cc83c7a..57098f8d0 100644 --- a/src/App/Pages/BaseViewModel.cs +++ b/src/App/Pages/BaseViewModel.cs @@ -3,6 +3,7 @@ using Bit.App.Abstractions; using Bit.App.Controls; using Bit.App.Resources; using Bit.Core.Abstractions; +using Bit.Core.Exceptions; using Bit.Core.Services; using Bit.Core.Utilities; using Xamarin.Forms; @@ -33,6 +34,11 @@ namespace Bit.App.Pages protected void HandleException(Exception ex, string message = null) { + if (ex is ApiException apiException && apiException.Error != null) + { + message = apiException.Error.GetSingleMessage(); + } + Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () => { await _deviceActionService.Value.HideLoadingAsync(); diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index a38f1693f..7733618a5 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -1372,6 +1372,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Check known data breaches for this password. + /// + public static string CheckKnownDataBreachesForThisPassword { + get { + return ResourceManager.GetString("CheckKnownDataBreachesForThisPassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to Check if password has been exposed.. /// @@ -2416,6 +2425,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Exposed Master Password. + /// + public static string ExposedMasterPassword { + get { + return ResourceManager.GetString("ExposedMasterPassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to Extension activated!. /// @@ -2938,6 +2956,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Good. + /// + public static string Good { + get { + return ResourceManager.GetString("Good", resourceCulture); + } + } + /// /// Looks up a localized string similar to Go to my vault. /// @@ -3073,6 +3100,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Important. + /// + public static string Important { + get { + return ResourceManager.GetString("Important", resourceCulture); + } + } + /// /// Looks up a localized string similar to Import items. /// @@ -4570,6 +4606,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?. + /// + public static string PasswordFoundInADataBreachAlertDescription { + get { + return ResourceManager.GetString("PasswordFoundInADataBreachAlertDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Password generated. /// @@ -5669,6 +5714,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Strong. + /// + public static string Strong { + get { + return ResourceManager.GetString("Strong", resourceCulture); + } + } + /// /// Looks up a localized string similar to Submit. /// @@ -6650,6 +6704,51 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Weak. + /// + public static string Weak { + get { + return ResourceManager.GetString("Weak", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Weak and Exposed Master Password. + /// + public static string WeakAndExposedMasterPassword { + get { + return ResourceManager.GetString("WeakAndExposedMasterPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Weak Master Password. + /// + public static string WeakMasterPassword { + get { + return ResourceManager.GetString("WeakMasterPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?. + /// + public static string WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription { + get { + return ResourceManager.GetString("WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Weak password identified. Use a strong password to protect your account. Are you sure you want to use a weak password?. + /// + public static string WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount { + get { + return ResourceManager.GetString("WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount", resourceCulture); + } + } + /// /// Looks up a localized string similar to Website. /// @@ -6767,6 +6866,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Your master password cannot be recovered if you forget it! {0} characters minimum.. + /// + public static string YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum { + get { + return ResourceManager.GetString("YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum", resourceCulture); + } + } + /// /// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device's USB port, then touch its button.. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index e2ec11eb3..7cb9f9933 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -2515,4 +2515,40 @@ Do you want to switch to this account? Enable camera permission to use the scanner + + Important + + + Your master password cannot be recovered if you forget it! {0} characters minimum. + + + Weak Master Password + + + Weak password identified. Use a strong password to protect your account. Are you sure you want to use a weak password? + + + Weak + + + Good + + + Strong + + + Check known data breaches for this password + + + Exposed Master Password + + + Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password? + + + Weak and Exposed Master Password + + + Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password? + diff --git a/src/App/Styles/Base.xaml b/src/App/Styles/Base.xaml index e518d4c6f..1d74ba518 100644 --- a/src/App/Styles/Base.xaml +++ b/src/App/Styles/Base.xaml @@ -562,4 +562,15 @@ + + diff --git a/src/App/Utilities/ProgressBarExtensions.cs b/src/App/Utilities/ProgressBarExtensions.cs new file mode 100644 index 000000000..7dcf15f85 --- /dev/null +++ b/src/App/Utilities/ProgressBarExtensions.cs @@ -0,0 +1,25 @@ +using Xamarin.Forms; + +namespace Bit.App.Utilities +{ + public static class ProgressBarExtensions + { + public static BindableProperty AnimatedProgressProperty = + BindableProperty.CreateAttached("AnimatedProgress", + typeof(double), + typeof(ProgressBar), + 0.0d, + BindingMode.OneWay, + propertyChanged: (b, o, n) => ProgressBarProgressChanged((ProgressBar)b, (double)n)); + + public static double GetAnimatedProgress(BindableObject target) => (double)target.GetValue(AnimatedProgressProperty); + public static void SetAnimatedProgress(BindableObject target, double value) => target.SetValue(AnimatedProgressProperty, value); + + private static void ProgressBarProgressChanged(ProgressBar progressBar, double progress) + { + ViewExtensions.CancelAnimations(progressBar); + progressBar.ProgressTo(progress, 500, Easing.SinIn); + } + } +} + diff --git a/src/Core/Abstractions/IPasswordGenerationService.cs b/src/Core/Abstractions/IPasswordGenerationService.cs index 4e52f60a3..74f46ea7c 100644 --- a/src/Core/Abstractions/IPasswordGenerationService.cs +++ b/src/Core/Abstractions/IPasswordGenerationService.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Bit.Core.Models.Domain; +using Zxcvbn; namespace Bit.Core.Abstractions { @@ -16,8 +17,9 @@ namespace Bit.Core.Abstractions Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> GetOptionsAsync(); Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> EnforcePasswordGeneratorPoliciesOnOptionsAsync(PasswordGenerationOptions options); - Zxcvbn.Result PasswordStrength(string password, List userInputs = null); + Result PasswordStrength(string password, List userInputs = null); Task SaveOptionsAsync(PasswordGenerationOptions options); void NormalizeOptions(PasswordGenerationOptions options, PasswordGeneratorPolicyOptions enforcedPolicyOptions); + List GetPasswordStrengthUserInput(string email); } } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 9c0a4893c..d16fe3db2 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -46,6 +46,7 @@ public const int SaveFileRequestCode = 44; public const int TotpDefaultTimer = 30; public const int PasswordlessNotificationTimeoutInMinutes = 15; + public const int MasterPasswordMinimumChars = 8; public static readonly string[] AndroidAllClearCipherCacheKeys = { diff --git a/src/Core/Services/PasswordGenerationService.cs b/src/Core/Services/PasswordGenerationService.cs index b82152e97..e689616c2 100644 --- a/src/Core/Services/PasswordGenerationService.cs +++ b/src/Core/Services/PasswordGenerationService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Bit.Core.Abstractions; @@ -395,6 +396,19 @@ namespace Bit.Core.Services return enforcedOptions; } + public List GetPasswordStrengthUserInput(string email) + { + var atPosition = email?.IndexOf('@'); + if (atPosition is null || atPosition < 0) + { + return null; + } + var rx = new Regex("/[^A-Za-z0-9]/", RegexOptions.Compiled); + var data = rx.Split(email.Substring(0, atPosition.Value).Trim().ToLower()); + + return new List(data); + } + private int? GetPolicyInt(Policy policy, string key) { if (policy.Data.ContainsKey(key))