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 @@
-
+
+ StyleClass="box-sub-label"
+ Margin="0,0,0,10">
+
+
+
+
+
+
+
+
+
@@ -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))