mirror of
https://github.com/bitwarden/android.git
synced 2024-12-26 02:48:29 +03:00
[SG-460] Master Password security checks (mobile) (#2312)
* [SG-886] MasterPassword Strength Indicator (#2238) * [SG-886] Add password strength indicator control * [SG-570] Add weak password dialog check * [SG-886] rename enum password strength * [SG-886] Change control scale * [SG-886] Move calculate user inputs to IPasswordGenerationService, refactor. * [SG-886] Move formatted string to xaml. Move minimum chars to constant * [SG-886] String to enum converter * [SG-886] PR fixes. Code refactor control * [SG-886] Update UI on OS theme change. * [SG-886] Move colors to view * [SG-886] Fixed password strength validation * [SG-564][SG-565] Check Exposed Password (#2239) * [SG-886] Add password strength indicator control * [SG-570] Add weak password dialog check * [SG-886] rename enum password strength * [SG-564] [SG-565] Add check for exposed password and show dialog * code format * [SG-886] Change control scale * [SG-886] Move calculate user inputs to IPasswordGenerationService, refactor. * [SG-886] Move formatted string to xaml. Move minimum chars to constant * [SG-886] String to enum converter * [SG-886] Remove import * [SG-886] Update UI on OS theme change. * [SG-886] Move colors to view * [SG-886] Fixed password strength validation
This commit is contained in:
parent
5aa1146657
commit
d61bc4b5c1
18 changed files with 553 additions and 31 deletions
|
@ -143,6 +143,7 @@
|
||||||
<Folder Include="Utilities\AccountManagement\" />
|
<Folder Include="Utilities\AccountManagement\" />
|
||||||
<Folder Include="Controls\DateTime\" />
|
<Folder Include="Controls\DateTime\" />
|
||||||
<Folder Include="Controls\IconLabelButton\" />
|
<Folder Include="Controls\IconLabelButton\" />
|
||||||
|
<Folder Include="Controls\PasswordStrengthProgressBar\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -435,5 +436,6 @@
|
||||||
<None Remove="Utilities\AccountManagement\" />
|
<None Remove="Utilities\AccountManagement\" />
|
||||||
<None Remove="Controls\DateTime\" />
|
<None Remove="Controls\DateTime\" />
|
||||||
<None Remove="Controls\IconLabelButton\" />
|
<None Remove="Controls\IconLabelButton\" />
|
||||||
|
<None Remove="Controls\PasswordStrengthProgressBar\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Bit.App.Controls
|
||||||
|
{
|
||||||
|
public interface IPasswordStrengthable
|
||||||
|
{
|
||||||
|
string Password { get; }
|
||||||
|
List<string> UserInputs { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<StackLayout
|
||||||
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
x:DataType="controls:PasswordStrengthViewModel"
|
||||||
|
x:Class="Bit.App.Controls.PasswordStrengthProgressBar"
|
||||||
|
StyleClass="box">
|
||||||
|
|
||||||
|
<StackLayout.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<u:LocalizableEnumConverter x:Key="localizableEnum" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</StackLayout.Resources>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
x:Name="_progressBar"
|
||||||
|
u:ProgressBarExtensions.AnimatedProgress="{Binding PasswordStrength}"
|
||||||
|
ScaleY="2" />
|
||||||
|
|
||||||
|
<Label
|
||||||
|
x:Name="_progressLabel"
|
||||||
|
Text="{Binding PasswordStrengthLevel, Converter={StaticResource localizableEnum}, TargetNullValue=' ' }"
|
||||||
|
StyleClass="box-footer-label" />
|
||||||
|
|
||||||
|
</StackLayout>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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<IPasswordGenerationService>();
|
||||||
|
_passwordStrengthable = passwordStrengthable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double PasswordStrength
|
||||||
|
{
|
||||||
|
get => _passwordStrength;
|
||||||
|
set => SetProperty(ref _passwordStrength, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordStrengthLevel? PasswordStrengthLevel
|
||||||
|
{
|
||||||
|
get => _passwordStrengthLevel;
|
||||||
|
set => SetProperty(ref _passwordStrengthLevel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
@ -147,8 +149,8 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
if (IsPolicyInEffect)
|
if (IsPolicyInEffect)
|
||||||
{
|
{
|
||||||
var userInput = await GetPasswordStrengthUserInput();
|
var userInputs = _passwordGenerationService.GetPasswordStrengthUserInput(await _stateService.GetEmailAsync());
|
||||||
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput);
|
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInputs);
|
||||||
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
|
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage,
|
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage,
|
||||||
|
@ -158,7 +160,7 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (MasterPassword.Length < 8)
|
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars)
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
|
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
|
||||||
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
|
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
|
||||||
|
@ -174,19 +176,5 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<string>> GetPasswordStrengthUserInput()
|
|
||||||
{
|
|
||||||
var email = await _stateService.GetEmailAsync();
|
|
||||||
List<string> 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<string>(data);
|
|
||||||
}
|
|
||||||
return userInput;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<StackLayout Spacing="20">
|
<StackLayout Spacing="10">
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
@ -72,8 +72,19 @@
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
StyleClass="box-sub-label"
|
||||||
StyleClass="box-footer-label" />
|
Margin="0,0,0,10">
|
||||||
|
<Label.FormattedText>
|
||||||
|
<FormattedString>
|
||||||
|
<Span Text="{u:I18n Important}" TextColor="{DynamicResource InfoColor}"/>
|
||||||
|
<Span Text=": " TextColor="{DynamicResource InfoColor}"/>
|
||||||
|
<Span Text="{Binding MasterPasswordMininumCharactersDescription}" TextColor="{DynamicResource MutedColor}"/>
|
||||||
|
</FormattedString>
|
||||||
|
</Label.FormattedText>
|
||||||
|
</Label>
|
||||||
|
<controls:PasswordStrengthProgressBar
|
||||||
|
BindingContext="{Binding PasswordStrengthViewModel}"
|
||||||
|
Margin="0,0"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<Grid StyleClass="box-row">
|
<Grid StyleClass="box-row">
|
||||||
|
@ -126,6 +137,17 @@
|
||||||
StyleClass="box-footer-label" />
|
StyleClass="box-footer-label" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding CheckExposedMasterPassword}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
HorizontalOptions="Start"
|
||||||
|
Margin="0, 0, 10, 0"/>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n CheckKnownDataBreachesForThisPassword}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
VerticalOptions="Center"/>
|
||||||
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-switch"
|
<StackLayout StyleClass="box-row, box-row-switch"
|
||||||
IsVisible="{Binding ShowTerms}">
|
IsVisible="{Binding ShowTerms}">
|
||||||
<Switch
|
<Switch
|
||||||
|
|
|
@ -1,28 +1,36 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class RegisterPageViewModel : CaptchaProtectedViewModel
|
public class RegisterPageViewModel : CaptchaProtectedViewModel, IPasswordStrengthable
|
||||||
{
|
{
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly IEnvironmentService _environmentService;
|
private readonly IEnvironmentService _environmentService;
|
||||||
|
private readonly IAuditService _auditService;
|
||||||
private readonly IApiService _apiService;
|
private readonly IApiService _apiService;
|
||||||
private readonly ICryptoService _cryptoService;
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
|
private string _email;
|
||||||
|
private string _masterPassword;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private bool _acceptPolicies;
|
private bool _acceptPolicies;
|
||||||
|
private bool _checkExposedMasterPassword;
|
||||||
|
|
||||||
public RegisterPageViewModel()
|
public RegisterPageViewModel()
|
||||||
{
|
{
|
||||||
|
@ -32,12 +40,14 @@ namespace Bit.App.Pages
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||||
|
_auditService = ServiceContainer.Resolve<IAuditService>();
|
||||||
|
|
||||||
PageTitle = AppResources.CreateAccount;
|
PageTitle = AppResources.CreateAccount;
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
|
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
ShowTerms = !_platformUtilsService.IsSelfHost();
|
ShowTerms = !_platformUtilsService.IsSelfHost();
|
||||||
|
PasswordStrengthViewModel = new PasswordStrengthViewModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommand PoliciesClickCommand => new Command<string>((url) =>
|
public ICommand PoliciesClickCommand => new Command<string>((url) =>
|
||||||
|
@ -61,6 +71,34 @@ namespace Bit.App.Pages
|
||||||
get => _acceptPolicies;
|
get => _acceptPolicies;
|
||||||
set => SetProperty(ref _acceptPolicies, value);
|
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<string> UserInputs => PasswordStrengthViewModel.GetPasswordStrengthUserInput(Email);
|
||||||
|
public string MasterPasswordMininumCharactersDescription => string.Format(AppResources.YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum,
|
||||||
|
Constants.MasterPasswordMinimumChars);
|
||||||
|
public PasswordStrengthViewModel PasswordStrengthViewModel { get; }
|
||||||
public bool ShowTerms { get; set; }
|
public bool ShowTerms { get; set; }
|
||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
|
@ -68,13 +106,10 @@ namespace Bit.App.Pages
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Email { get; set; }
|
|
||||||
public string MasterPassword { get; set; }
|
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
public Action RegistrationSuccess { get; set; }
|
public Action RegistrationSuccess { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
protected override II18nService i18nService => _i18nService;
|
protected override II18nService i18nService => _i18nService;
|
||||||
protected override IEnvironmentService environmentService => _environmentService;
|
protected override IEnvironmentService environmentService => _environmentService;
|
||||||
protected override IDeviceActionService deviceActionService => _deviceActionService;
|
protected override IDeviceActionService deviceActionService => _deviceActionService;
|
||||||
|
@ -110,7 +145,7 @@ namespace Bit.App.Pages
|
||||||
AppResources.Ok);
|
AppResources.Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (MasterPassword.Length < 8)
|
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars)
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
|
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
|
||||||
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||||
|
@ -128,8 +163,10 @@ namespace Bit.App.Pages
|
||||||
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (await IsPasswordWeakOrExposed())
|
||||||
// TODO: Password strength check?
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (showLoading)
|
if (showLoading)
|
||||||
{
|
{
|
||||||
|
@ -160,6 +197,7 @@ namespace Bit.App.Pages
|
||||||
},
|
},
|
||||||
CaptchaResponse = _captchaToken,
|
CaptchaResponse = _captchaToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: org invite?
|
// TODO: org invite?
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -208,5 +246,43 @@ namespace Bit.App.Pages
|
||||||
entry.Focus();
|
entry.Focus();
|
||||||
entry.CursorPosition = String.IsNullOrEmpty(ConfirmMasterPassword) ? 0 : ConfirmMasterPassword.Length;
|
entry.CursorPosition = String.IsNullOrEmpty(ConfirmMasterPassword) ? 0 : ConfirmMasterPassword.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
@ -137,8 +138,8 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
if (IsPolicyInEffect)
|
if (IsPolicyInEffect)
|
||||||
{
|
{
|
||||||
var userInput = await GetPasswordStrengthUserInput();
|
var userInputs = _passwordGenerationService.GetPasswordStrengthUserInput(await _stateService.GetEmailAsync());
|
||||||
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput);
|
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInputs);
|
||||||
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
|
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
|
||||||
{
|
{
|
||||||
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
|
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
|
||||||
|
@ -148,7 +149,7 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (MasterPassword.Length < 8)
|
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars)
|
||||||
{
|
{
|
||||||
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
|
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
|
||||||
AppResources.MasterPasswordLengthValMessage, AppResources.Ok);
|
AppResources.MasterPasswordLengthValMessage, AppResources.Ok);
|
||||||
|
|
|
@ -3,6 +3,7 @@ using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
@ -33,6 +34,11 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
protected void HandleException(Exception ex, string message = null)
|
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 () =>
|
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
||||||
{
|
{
|
||||||
await _deviceActionService.Value.HideLoadingAsync();
|
await _deviceActionService.Value.HideLoadingAsync();
|
||||||
|
|
108
src/App/Resources/AppResources.Designer.cs
generated
108
src/App/Resources/AppResources.Designer.cs
generated
|
@ -1372,6 +1372,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Check known data breaches for this password.
|
||||||
|
/// </summary>
|
||||||
|
public static string CheckKnownDataBreachesForThisPassword {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CheckKnownDataBreachesForThisPassword", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Check if password has been exposed..
|
/// Looks up a localized string similar to Check if password has been exposed..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -2416,6 +2425,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Exposed Master Password.
|
||||||
|
/// </summary>
|
||||||
|
public static string ExposedMasterPassword {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ExposedMasterPassword", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Extension activated!.
|
/// Looks up a localized string similar to Extension activated!.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -2938,6 +2956,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Good.
|
||||||
|
/// </summary>
|
||||||
|
public static string Good {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Good", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Go to my vault.
|
/// Looks up a localized string similar to Go to my vault.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3073,6 +3100,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Important.
|
||||||
|
/// </summary>
|
||||||
|
public static string Important {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Important", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Import items.
|
/// Looks up a localized string similar to Import items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -4570,6 +4606,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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?.
|
||||||
|
/// </summary>
|
||||||
|
public static string PasswordFoundInADataBreachAlertDescription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PasswordFoundInADataBreachAlertDescription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Password generated.
|
/// Looks up a localized string similar to Password generated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -5669,6 +5714,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Strong.
|
||||||
|
/// </summary>
|
||||||
|
public static string Strong {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Strong", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Submit.
|
/// Looks up a localized string similar to Submit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -6650,6 +6704,51 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Weak.
|
||||||
|
/// </summary>
|
||||||
|
public static string Weak {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Weak", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Weak and Exposed Master Password.
|
||||||
|
/// </summary>
|
||||||
|
public static string WeakAndExposedMasterPassword {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("WeakAndExposedMasterPassword", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Weak Master Password.
|
||||||
|
/// </summary>
|
||||||
|
public static string WeakMasterPassword {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("WeakMasterPassword", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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?.
|
||||||
|
/// </summary>
|
||||||
|
public static string WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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?.
|
||||||
|
/// </summary>
|
||||||
|
public static string WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Website.
|
/// Looks up a localized string similar to Website.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -6767,6 +6866,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Your master password cannot be recovered if you forget it! {0} characters minimum..
|
||||||
|
/// </summary>
|
||||||
|
public static string YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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..
|
/// 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..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -2515,4 +2515,40 @@ Do you want to switch to this account?</value>
|
||||||
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
|
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
|
||||||
<value>Enable camera permission to use the scanner</value>
|
<value>Enable camera permission to use the scanner</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Important" xml:space="preserve">
|
||||||
|
<value>Important</value>
|
||||||
|
</data>
|
||||||
|
<data name="YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum" xml:space="preserve">
|
||||||
|
<value>Your master password cannot be recovered if you forget it! {0} characters minimum.</value>
|
||||||
|
</data>
|
||||||
|
<data name="WeakMasterPassword" xml:space="preserve">
|
||||||
|
<value>Weak Master Password</value>
|
||||||
|
</data>
|
||||||
|
<data name="WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount" xml:space="preserve">
|
||||||
|
<value>Weak password identified. Use a strong password to protect your account. Are you sure you want to use a weak password?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Weak" xml:space="preserve">
|
||||||
|
<value>Weak</value>
|
||||||
|
</data>
|
||||||
|
<data name="Good" xml:space="preserve">
|
||||||
|
<value>Good</value>
|
||||||
|
</data>
|
||||||
|
<data name="Strong" xml:space="preserve">
|
||||||
|
<value>Strong</value>
|
||||||
|
</data>
|
||||||
|
<data name="CheckKnownDataBreachesForThisPassword" xml:space="preserve">
|
||||||
|
<value>Check known data breaches for this password</value>
|
||||||
|
</data>
|
||||||
|
<data name="ExposedMasterPassword" xml:space="preserve">
|
||||||
|
<value>Exposed Master Password</value>
|
||||||
|
</data>
|
||||||
|
<data name="PasswordFoundInADataBreachAlertDescription" xml:space="preserve">
|
||||||
|
<value>Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?</value>
|
||||||
|
</data>
|
||||||
|
<data name="WeakAndExposedMasterPassword" xml:space="preserve">
|
||||||
|
<value>Weak and Exposed Master Password</value>
|
||||||
|
</data>
|
||||||
|
<data name="WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription" xml:space="preserve">
|
||||||
|
<value>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?</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
|
@ -562,4 +562,15 @@
|
||||||
</VisualStateGroupList>
|
</VisualStateGroupList>
|
||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="controls:PasswordStrengthProgressBar">
|
||||||
|
<Setter Property="VeryWeakColor"
|
||||||
|
Value="{DynamicResource DangerColor}" />
|
||||||
|
<Setter Property="WeakColor"
|
||||||
|
Value="{DynamicResource WarningColor}" />
|
||||||
|
<Setter Property="GoodColor"
|
||||||
|
Value="{DynamicResource PrimaryColor}" />
|
||||||
|
<Setter Property="StrongColor"
|
||||||
|
Value="{DynamicResource SuccessColor}" />
|
||||||
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
25
src/App/Utilities/ProgressBarExtensions.cs
Normal file
25
src/App/Utilities/ProgressBarExtensions.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Zxcvbn;
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
|
@ -16,8 +17,9 @@ namespace Bit.Core.Abstractions
|
||||||
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> GetOptionsAsync();
|
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> GetOptionsAsync();
|
||||||
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)>
|
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)>
|
||||||
EnforcePasswordGeneratorPoliciesOnOptionsAsync(PasswordGenerationOptions options);
|
EnforcePasswordGeneratorPoliciesOnOptionsAsync(PasswordGenerationOptions options);
|
||||||
Zxcvbn.Result PasswordStrength(string password, List<string> userInputs = null);
|
Result PasswordStrength(string password, List<string> userInputs = null);
|
||||||
Task SaveOptionsAsync(PasswordGenerationOptions options);
|
Task SaveOptionsAsync(PasswordGenerationOptions options);
|
||||||
void NormalizeOptions(PasswordGenerationOptions options, PasswordGeneratorPolicyOptions enforcedPolicyOptions);
|
void NormalizeOptions(PasswordGenerationOptions options, PasswordGeneratorPolicyOptions enforcedPolicyOptions);
|
||||||
|
List<string> GetPasswordStrengthUserInput(string email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
public const int SaveFileRequestCode = 44;
|
public const int SaveFileRequestCode = 44;
|
||||||
public const int TotpDefaultTimer = 30;
|
public const int TotpDefaultTimer = 30;
|
||||||
public const int PasswordlessNotificationTimeoutInMinutes = 15;
|
public const int PasswordlessNotificationTimeoutInMinutes = 15;
|
||||||
|
public const int MasterPasswordMinimumChars = 8;
|
||||||
|
|
||||||
public static readonly string[] AndroidAllClearCipherCacheKeys =
|
public static readonly string[] AndroidAllClearCipherCacheKeys =
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
@ -395,6 +396,19 @@ namespace Bit.Core.Services
|
||||||
return enforcedOptions;
|
return enforcedOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<string> 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<string>(data);
|
||||||
|
}
|
||||||
|
|
||||||
private int? GetPolicyInt(Policy policy, string key)
|
private int? GetPolicyInt(Policy policy, string key)
|
||||||
{
|
{
|
||||||
if (policy.Data.ContainsKey(key))
|
if (policy.Data.ContainsKey(key))
|
||||||
|
|
Loading…
Reference in a new issue