using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Controls; using Bit.App.Resources; using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Request; using Bit.Core.Services; using Bit.Core.Utilities; using Xamarin.Forms; namespace Bit.App.Pages { public class RegisterPageViewModel : CaptchaProtectedViewModel, IPasswordStrengthable { private readonly IDeviceActionService _deviceActionService; private readonly II18nService _i18nService; private readonly IEnvironmentService _environmentService; private readonly IAuditService _auditService; private readonly IApiService _apiService; private readonly ICryptoService _cryptoService; private readonly IPlatformUtilsService _platformUtilsService; private string _email; private string _masterPassword; private bool _showPassword; private bool _acceptPolicies; private bool _checkExposedMasterPassword; public RegisterPageViewModel() { _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _apiService = ServiceContainer.Resolve("apiService"); _cryptoService = ServiceContainer.Resolve("cryptoService"); _platformUtilsService = ServiceContainer.Resolve("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) => { _platformUtilsService.LaunchUri(url); }); public bool ShowPassword { get => _showPassword; set => SetProperty(ref _showPassword, value, additionalPropertyNames: new string[] { nameof(ShowPasswordIcon), nameof(PasswordVisibilityAccessibilityText) }); } public bool AcceptPolicies { 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; } public Command ToggleConfirmPasswordCommand { get; } public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow; public string Name { 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; protected override IPlatformUtilsService platformUtilsService => _platformUtilsService; public async Task SubmitAsync(bool showLoading = true) { if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None) { await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, AppResources.InternetConnectionRequiredTitle, AppResources.Ok); return; } if (string.IsNullOrWhiteSpace(Email)) { await _platformUtilsService.ShowDialogAsync( string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress), AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (!Email.Contains("@")) { await _platformUtilsService.ShowDialogAsync(AppResources.InvalidEmail, AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (string.IsNullOrWhiteSpace(MasterPassword)) { await _platformUtilsService.ShowDialogAsync( string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (MasterPassword.Length < Constants.MasterPasswordMinimumChars) { await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage, AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (MasterPassword != ConfirmMasterPassword) { await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordConfirmationValMessage, AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (ShowTerms && !AcceptPolicies) { await _platformUtilsService.ShowDialogAsync(AppResources.AcceptPoliciesError, AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (await IsPasswordWeakOrExposed()) { return; } if (showLoading) { await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount); } Name = string.IsNullOrWhiteSpace(Name) ? null : Name; Email = Email.Trim().ToLower(); var kdf = KdfType.PBKDF2_SHA256; var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdf, Constants.KdfIterations); var encKey = await _cryptoService.MakeEncKeyAsync(key); var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key); var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); var request = new RegisterRequest { Email = Email, Name = Name, MasterPasswordHash = hashedPassword, MasterPasswordHint = Hint, Key = encKey.Item2.EncryptedString, Kdf = kdf, KdfIterations = Constants.KdfIterations, Keys = new KeysRequest { PublicKey = keys.Item1, EncryptedPrivateKey = keys.Item2.EncryptedString }, CaptchaResponse = _captchaToken, }; // TODO: org invite? try { await _apiService.PostRegisterAsync(request); await _deviceActionService.HideLoadingAsync(); _platformUtilsService.ShowToast("success", null, AppResources.AccountCreated, new System.Collections.Generic.Dictionary { ["longDuration"] = true }); RegistrationSuccess?.Invoke(); } catch (ApiException e) { if (e?.Error != null && e.Error.CaptchaRequired) { if (await HandleCaptchaAsync(e.Error.CaptchaSiteKey)) { await SubmitAsync(false); _captchaToken = null; } return; } await _deviceActionService.HideLoadingAsync(); if (e?.Error != null) { await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), AppResources.AnErrorHasOccurred, AppResources.Ok); } } } public void TogglePassword() { ShowPassword = !ShowPassword; var entry = (Page as RegisterPage).MasterPasswordEntry; entry.Focus(); entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length; } public void ToggleConfirmPassword() { ShowPassword = !ShowPassword; var entry = (Page as RegisterPage).ConfirmMasterPasswordEntry; 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; } } }