From 271e6b3d92fb9c2c6d566e638955631f60f67730 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Fri, 24 Sep 2021 13:14:26 -0500 Subject: [PATCH] [Reset Password v1] Update Temp Password (#1492) * [Reset Password v1] Update Temp Password * fixed order of operations for reset temp password flow * Refactored bool with auth result * Finished removal of temp password flow from set password * iOS extension support plus extension bugfixes Co-authored-by: addison Co-authored-by: Matt Portune --- src/App/App.xaml.cs | 36 +--- .../Accounts/BaseChangePasswordViewModel.cs | 177 ++++++++++++++++++ src/App/Pages/Accounts/LoginPage.xaml.cs | 8 + src/App/Pages/Accounts/LoginPageViewModel.cs | 20 +- src/App/Pages/Accounts/LoginSsoPage.xaml.cs | 8 + .../Pages/Accounts/LoginSsoPageViewModel.cs | 5 + src/App/Pages/Accounts/RegisterPage.xaml | 4 +- .../Pages/Accounts/RegisterPageViewModel.cs | 25 +-- .../Accounts/SetPasswordPageViewModel.cs | 2 +- src/App/Pages/Accounts/TwoFactorPage.xaml.cs | 8 + .../Pages/Accounts/TwoFactorPageViewModel.cs | 23 ++- .../Accounts/UpdateTempPasswordPage.xaml | 160 ++++++++++++++++ .../Accounts/UpdateTempPasswordPage.xaml.cs | 83 ++++++++ .../UpdateTempPasswordPageViewModel.cs | 90 +++++++++ src/App/Resources/AppResources.Designer.cs | 30 +++ src/App/Resources/AppResources.resx | 15 ++ src/App/Utilities/AppHelpers.cs | 35 ++++ src/Core/Abstractions/IApiService.cs | 1 + src/Core/Abstractions/IUserService.cs | 2 + src/Core/Models/Domain/AuthResult.cs | 1 + .../Request/UpdateTempPasswordRequest.cs | 9 + .../Models/Response/IdentityTokenResponse.cs | 1 + src/Core/Models/Response/ProfileResponse.cs | 4 +- src/Core/Services/ApiService.cs | 6 + src/Core/Services/AuthService.cs | 1 + src/Core/Services/SyncService.cs | 1 + src/Core/Services/UserService.cs | 18 ++ .../CredentialProviderViewController.cs | 44 +++++ .../Controllers/LockPasswordViewController.cs | 28 +-- src/iOS.Extension/LoadingViewController.cs | 47 ++++- 30 files changed, 795 insertions(+), 97 deletions(-) create mode 100644 src/App/Pages/Accounts/BaseChangePasswordViewModel.cs create mode 100644 src/App/Pages/Accounts/UpdateTempPasswordPage.xaml create mode 100644 src/App/Pages/Accounts/UpdateTempPasswordPage.xaml.cs create mode 100644 src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs create mode 100644 src/Core/Models/Request/UpdateTempPasswordRequest.cs diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 61ae2ffa3..aff7401d7 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -17,21 +17,12 @@ namespace Bit.App { public partial class App : Application { - private readonly MobileI18nService _i18nService; private readonly IUserService _userService; private readonly IBroadcasterService _broadcasterService; private readonly IMessagingService _messagingService; private readonly IStateService _stateService; private readonly IVaultTimeoutService _vaultTimeoutService; private readonly ISyncService _syncService; - private readonly ITokenService _tokenService; - private readonly ICryptoService _cryptoService; - private readonly ICipherService _cipherService; - private readonly IFolderService _folderService; - private readonly ICollectionService _collectionService; - private readonly ISettingsService _settingsService; - private readonly IPasswordGenerationService _passwordGenerationService; - private readonly ISearchService _searchService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IAuthService _authService; private readonly IStorageService _storageService; @@ -54,20 +45,10 @@ namespace Bit.App _stateService = ServiceContainer.Resolve("stateService"); _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _syncService = ServiceContainer.Resolve("syncService"); - _tokenService = ServiceContainer.Resolve("tokenService"); - _cryptoService = ServiceContainer.Resolve("cryptoService"); - _cipherService = ServiceContainer.Resolve("cipherService"); - _folderService = ServiceContainer.Resolve("folderService"); - _settingsService = ServiceContainer.Resolve("settingsService"); - _collectionService = ServiceContainer.Resolve("collectionService"); - _searchService = ServiceContainer.Resolve("searchService"); _authService = ServiceContainer.Resolve("authService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _storageService = ServiceContainer.Resolve("storageService"); _secureStorageService = ServiceContainer.Resolve("secureStorageService"); - _passwordGenerationService = ServiceContainer.Resolve( - "passwordGenerationService"); - _i18nService = ServiceContainer.Resolve("i18nService") as MobileI18nService; _deviceActionService = ServiceContainer.Resolve("deviceActionService"); Bootstrap(); @@ -240,22 +221,7 @@ namespace Bit.App private async Task LogOutAsync(bool expired) { - var userId = await _userService.GetUserIdAsync(); - await Task.WhenAll( - _syncService.SetLastSyncAsync(DateTime.MinValue), - _tokenService.ClearTokenAsync(), - _cryptoService.ClearKeysAsync(), - _userService.ClearAsync(), - _settingsService.ClearAsync(userId), - _cipherService.ClearAsync(userId), - _folderService.ClearAsync(userId), - _collectionService.ClearAsync(userId), - _passwordGenerationService.ClearAsync(), - _vaultTimeoutService.ClearAsync(), - _stateService.PurgeAsync(), - _deviceActionService.ClearCacheAsync()); - _vaultTimeoutService.BiometricLocked = true; - _searchService.ClearIndex(); + await AppHelpers.LogOutAsync(); _authService.LogOut(() => { Current.MainPage = new HomePage(); diff --git a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs new file mode 100644 index 000000000..f5dae6ee6 --- /dev/null +++ b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs @@ -0,0 +1,177 @@ +using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.Core.Models.Domain; +using Xamarin.Essentials; + +namespace Bit.App.Pages +{ + public class BaseChangePasswordViewModel : BaseViewModel + { + protected readonly IPlatformUtilsService _platformUtilsService; + protected readonly IUserService _userService; + protected readonly IPolicyService _policyService; + protected readonly IPasswordGenerationService _passwordGenerationService; + protected readonly II18nService _i18nService; + protected readonly ICryptoService _cryptoService; + protected readonly IDeviceActionService _deviceActionService; + protected readonly IApiService _apiService; + + private bool _showPassword; + private bool _isPolicyInEffect; + private string _policySummary; + private MasterPasswordPolicyOptions _policy; + + protected BaseChangePasswordViewModel() + { + _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + _userService = ServiceContainer.Resolve("userService"); + _policyService = ServiceContainer.Resolve("policyService"); + _passwordGenerationService = + ServiceContainer.Resolve("passwordGenerationService"); + _i18nService = ServiceContainer.Resolve("i18nService"); + _cryptoService = ServiceContainer.Resolve("cryptoService"); + _deviceActionService = ServiceContainer.Resolve("deviceActionService"); + _apiService = ServiceContainer.Resolve("apiService"); + } + + public bool ShowPassword + { + get => _showPassword; + set => SetProperty(ref _showPassword, value, + additionalPropertyNames: new[] { nameof(ShowPasswordIcon) }); + } + + public bool IsPolicyInEffect + { + get => _isPolicyInEffect; + set => SetProperty(ref _isPolicyInEffect, value); + } + + public string PolicySummary + { + get => _policySummary; + set => SetProperty(ref _policySummary, value); + } + + public MasterPasswordPolicyOptions Policy + { + get => _policy; + set => SetProperty(ref _policy, value); + } + + public string ShowPasswordIcon => ShowPassword ? "" : ""; + public string MasterPassword { get; set; } + public string ConfirmMasterPassword { get; set; } + public string Hint { get; set; } + + public async Task InitAsync() + { + await CheckPasswordPolicy(); + } + + private async Task CheckPasswordPolicy() + { + Policy = await _policyService.GetMasterPasswordPolicyOptions(); + IsPolicyInEffect = Policy?.InEffect() ?? false; + if (!IsPolicyInEffect) + { + return; + } + + var bullet = "\n" + "".PadLeft(4) + "\u2022 "; + var sb = new StringBuilder(); + sb.Append(_i18nService.T("MasterPasswordPolicyInEffect")); + if (Policy.MinComplexity > 0) + { + sb.Append(bullet) + .Append(string.Format(_i18nService.T("PolicyInEffectMinComplexity"), Policy.MinComplexity)); + } + if (Policy.MinLength > 0) + { + sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectMinLength"), Policy.MinLength)); + } + if (Policy.RequireUpper) + { + sb.Append(bullet).Append(_i18nService.T("PolicyInEffectUppercase")); + } + if (Policy.RequireLower) + { + sb.Append(bullet).Append(_i18nService.T("PolicyInEffectLowercase")); + } + if (Policy.RequireNumbers) + { + sb.Append(bullet).Append(_i18nService.T("PolicyInEffectNumbers")); + } + if (Policy.RequireSpecial) + { + sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectSpecial"), "!@#$%^&*")); + } + PolicySummary = sb.ToString(); + } + + protected async Task ValidateMasterPasswordAsync() + { + if (Connectivity.NetworkAccess == NetworkAccess.None) + { + await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, + AppResources.InternetConnectionRequiredTitle, AppResources.Ok); + return false; + } + if (string.IsNullOrWhiteSpace(MasterPassword)) + { + await _platformUtilsService.ShowDialogAsync( + string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), + AppResources.AnErrorHasOccurred, AppResources.Ok); + return false; + } + if (IsPolicyInEffect) + { + var userInput = await GetPasswordStrengthUserInput(); + var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput); + if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy)) + { + await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage, + AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok); + return false; + } + } + else + { + if (MasterPassword.Length < 8) + { + await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage, + AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok); + return false; + } + } + if (MasterPassword != ConfirmMasterPassword) + { + await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordConfirmationValMessage, + AppResources.AnErrorHasOccurred, AppResources.Ok); + return false; + } + + return true; + } + + private async Task> GetPasswordStrengthUserInput() + { + var email = await _userService.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/LoginPage.xaml.cs b/src/App/Pages/Accounts/LoginPage.xaml.cs index 66c0ce596..867eccd5b 100644 --- a/src/App/Pages/Accounts/LoginPage.xaml.cs +++ b/src/App/Pages/Accounts/LoginPage.xaml.cs @@ -29,6 +29,8 @@ namespace Bit.App.Pages _vm.Page = this; _vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync()); _vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync()); + _vm.UpdateTempPasswordAction = + () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); _vm.CloseAction = async () => { _messagingService.Send("showStatusBar", false); @@ -122,5 +124,11 @@ namespace Bit.App.Pages var previousPage = await AppHelpers.ClearPreviousPage(); Application.Current.MainPage = new TabsPage(_appOptions, previousPage); } + + private async Task UpdateTempPasswordAsync() + { + var page = new UpdateTempPasswordPage(); + await Navigation.PushModalAsync(new NavigationPage(page)); + } } } diff --git a/src/App/Pages/Accounts/LoginPageViewModel.cs b/src/App/Pages/Accounts/LoginPageViewModel.cs index 58dd75a36..2090af196 100644 --- a/src/App/Pages/Accounts/LoginPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginPageViewModel.cs @@ -8,11 +8,6 @@ using System; using System.Threading.Tasks; using Bit.App.Utilities; using Xamarin.Forms; -using Newtonsoft.Json; -using System.Text; -using Xamarin.Essentials; -using System.Text.RegularExpressions; -using Bit.Core.Services; namespace Bit.App.Pages { @@ -78,6 +73,7 @@ namespace Bit.App.Pages public bool RememberEmail { get; set; } public Action StartTwoFactorAction { get; set; } public Action LogInSuccessAction { get; set; } + public Action UpdateTempPasswordAction { get; set; } public Action CloseAction { get; set; } protected override II18nService i18nService => _i18nService; @@ -100,15 +96,14 @@ namespace Bit.App.Pages if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None) { await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, - AppResources.InternetConnectionRequiredTitle); + AppResources.InternetConnectionRequiredTitle, AppResources.Ok); return; } if (string.IsNullOrWhiteSpace(Email)) { await _platformUtilsService.ShowDialogAsync( string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress), - AppResources.AnErrorHasOccurred, - AppResources.Ok); + AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (!Email.Contains("@")) @@ -121,8 +116,7 @@ namespace Bit.App.Pages { await _platformUtilsService.ShowDialogAsync( string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), - AppResources.AnErrorHasOccurred, - AppResources.Ok); + AppResources.AnErrorHasOccurred, AppResources.Ok); return; } @@ -163,6 +157,10 @@ namespace Bit.App.Pages { StartTwoFactorAction?.Invoke(); } + else if (response.ForcePasswordReset) + { + UpdateTempPasswordAction?.Invoke(); + } else { var disableFavicon = await _storageService.GetAsync(Constants.DisableFaviconKey); @@ -179,7 +177,7 @@ namespace Bit.App.Pages if (e?.Error != null) { await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), - AppResources.AnErrorHasOccurred); + AppResources.AnErrorHasOccurred, AppResources.Ok); } } } diff --git a/src/App/Pages/Accounts/LoginSsoPage.xaml.cs b/src/App/Pages/Accounts/LoginSsoPage.xaml.cs index c30853d2c..4b9bb4b70 100644 --- a/src/App/Pages/Accounts/LoginSsoPage.xaml.cs +++ b/src/App/Pages/Accounts/LoginSsoPage.xaml.cs @@ -32,6 +32,8 @@ namespace Bit.App.Pages _vm.StartSetPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync()); _vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync()); + _vm.UpdateTempPasswordAction = + () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); _vm.CloseAction = async () => { _messagingService.Send("showStatusBar", false); @@ -103,6 +105,12 @@ namespace Bit.App.Pages var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier); await Navigation.PushModalAsync(new NavigationPage(page)); } + + private async Task UpdateTempPasswordAsync() + { + var page = new UpdateTempPasswordPage(); + await Navigation.PushModalAsync(new NavigationPage(page)); + } private async Task SsoAuthSuccessAsync() { diff --git a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs index d3416571e..5a1eac876 100644 --- a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs @@ -60,6 +60,7 @@ namespace Bit.App.Pages public Action StartSetPasswordAction { get; set; } public Action SsoAuthSuccessAction { get; set; } public Action CloseAction { get; set; } + public Action UpdateTempPasswordAction { get; set; } public async Task InitAsync() { @@ -185,6 +186,10 @@ namespace Bit.App.Pages else if (response.ResetMasterPassword) { StartSetPasswordAction?.Invoke(); + } + else if (response.ForcePasswordReset) + { + UpdateTempPasswordAction?.Invoke(); } else { diff --git a/src/App/Pages/Accounts/RegisterPage.xaml b/src/App/Pages/Accounts/RegisterPage.xaml index 19d2a30fc..668fe28db 100644 --- a/src/App/Pages/Accounts/RegisterPage.xaml +++ b/src/App/Pages/Accounts/RegisterPage.xaml @@ -137,7 +137,7 @@ + TextColor="{DynamicResource HyperlinkColor}"> @@ -145,7 +145,7 @@ + TextColor="{DynamicResource HyperlinkColor}"> diff --git a/src/App/Pages/Accounts/RegisterPageViewModel.cs b/src/App/Pages/Accounts/RegisterPageViewModel.cs index e06d9306d..b1174a867 100644 --- a/src/App/Pages/Accounts/RegisterPageViewModel.cs +++ b/src/App/Pages/Accounts/RegisterPageViewModel.cs @@ -90,44 +90,47 @@ namespace Bit.App.Pages if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None) { await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, - AppResources.InternetConnectionRequiredTitle); + AppResources.InternetConnectionRequiredTitle, AppResources.Ok); return; } if (string.IsNullOrWhiteSpace(Email)) { - await Page.DisplayAlert(AppResources.AnErrorHasOccurred, + await _platformUtilsService.ShowDialogAsync( string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress), + AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (!Email.Contains("@")) { - await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok); + await _platformUtilsService.ShowDialogAsync(AppResources.InvalidEmail, AppResources.AnErrorHasOccurred, + AppResources.Ok); return; } if (string.IsNullOrWhiteSpace(MasterPassword)) { - await Page.DisplayAlert(AppResources.AnErrorHasOccurred, + await _platformUtilsService.ShowDialogAsync( string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), + AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (MasterPassword.Length < 8) { - await Page.DisplayAlert(AppResources.AnErrorHasOccurred, - AppResources.MasterPasswordLengthValMessage, AppResources.Ok); + await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage, + AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (MasterPassword != ConfirmMasterPassword) { - await Page.DisplayAlert(AppResources.AnErrorHasOccurred, - AppResources.MasterPasswordConfirmationValMessage, AppResources.Ok); + await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordConfirmationValMessage, + AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (ShowTerms && !AcceptPolicies) { - await Page.DisplayAlert(AppResources.AnErrorHasOccurred, - AppResources.AcceptPoliciesError, AppResources.Ok); + await _platformUtilsService.ShowDialogAsync(AppResources.AcceptPoliciesError, + AppResources.AnErrorHasOccurred, AppResources.Ok); return; } @@ -190,7 +193,7 @@ namespace Bit.App.Pages if (e?.Error != null) { await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), - AppResources.AnErrorHasOccurred); + AppResources.AnErrorHasOccurred, AppResources.Ok); } } } diff --git a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs index 6172f2595..39a3041e7 100644 --- a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs +++ b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs @@ -90,6 +90,7 @@ namespace Bit.App.Pages public string ConfirmMasterPassword { get; set; } public string Hint { get; set; } public Action SetPasswordSuccessAction { get; set; } + public Action UpdateTempPasswordAction { get; set; } public Action CloseAction { get; set; } public string OrgIdentifier { get; set; } public string OrgId { get; set; } @@ -221,7 +222,6 @@ namespace Bit.App.Pages } await _deviceActionService.HideLoadingAsync(); - SetPasswordSuccessAction?.Invoke(); } catch (ApiException e) diff --git a/src/App/Pages/Accounts/TwoFactorPage.xaml.cs b/src/App/Pages/Accounts/TwoFactorPage.xaml.cs index 44307a22e..b97591f5f 100644 --- a/src/App/Pages/Accounts/TwoFactorPage.xaml.cs +++ b/src/App/Pages/Accounts/TwoFactorPage.xaml.cs @@ -40,6 +40,8 @@ namespace Bit.App.Pages Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync()); _vm.TwoFactorAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync()); + _vm.UpdateTempPasswordAction = + () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); _vm.CloseAction = async () => await Navigation.PopModalAsync(); DuoWebView = _duoWebView; if (Device.RuntimePlatform == Device.Android) @@ -189,6 +191,12 @@ namespace Bit.App.Pages var page = new SetPasswordPage(_appOptions, _orgIdentifier); await Navigation.PushModalAsync(new NavigationPage(page)); } + + private async Task UpdateTempPasswordAsync() + { + var page = new UpdateTempPasswordPage(); + await Navigation.PushModalAsync(new NavigationPage(page)); + } private async Task TwoFactorAuthSuccessAsync() { diff --git a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs index a79a82432..768be9eeb 100644 --- a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs +++ b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs @@ -49,7 +49,7 @@ namespace Bit.App.Pages _environmentService = ServiceContainer.Resolve("environmentService"); _messagingService = ServiceContainer.Resolve("messagingService"); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); - _stateService = ServiceContainer.Resolve("stateService"); + _stateService = ServiceContainer.Resolve("stateService"); PageTitle = AppResources.TwoStepLogin; SubmitCommand = new Command(async () => await SubmitAsync()); @@ -113,6 +113,7 @@ namespace Bit.App.Pages public Action TwoFactorAuthSuccessAction { get; set; } public Action StartSetPasswordAction { get; set; } public Action CloseAction { get; set; } + public Action UpdateTempPasswordAction { get; set; } public void Init() { @@ -243,12 +244,13 @@ namespace Bit.App.Pages if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError)) { var message = AppResources.Fido2CheckBrowser + "\n\n" + resultError; - await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred); + await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred, + AppResources.Ok); } else { await _platformUtilsService.ShowDialogAsync(AppResources.Fido2CheckBrowser, - AppResources.AnErrorHasOccurred); + AppResources.AnErrorHasOccurred, AppResources.Ok); } } } @@ -262,14 +264,14 @@ namespace Bit.App.Pages if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None) { await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, - AppResources.InternetConnectionRequiredTitle); + AppResources.InternetConnectionRequiredTitle, AppResources.Ok); return; } if (string.IsNullOrWhiteSpace(Token)) { await _platformUtilsService.ShowDialogAsync( string.Format(AppResources.ValidationFieldRequired, AppResources.VerificationCode), - AppResources.AnErrorHasOccurred); + AppResources.AnErrorHasOccurred, AppResources.Ok); return; } if (SelectedProviderType == TwoFactorProviderType.Email || @@ -293,6 +295,10 @@ namespace Bit.App.Pages { StartSetPasswordAction?.Invoke(); } + else if (result.ForcePasswordReset) + { + UpdateTempPasswordAction?.Invoke(); + } else { var disableFavicon = await _storageService.GetAsync(Constants.DisableFaviconKey); @@ -306,7 +312,7 @@ namespace Bit.App.Pages if (e?.Error != null) { await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), - AppResources.AnErrorHasOccurred); + AppResources.AnErrorHasOccurred, AppResources.Ok); } } } @@ -344,7 +350,7 @@ namespace Bit.App.Pages if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None) { await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, - AppResources.InternetConnectionRequiredTitle); + AppResources.InternetConnectionRequiredTitle, AppResources.Ok); return false; } try @@ -375,7 +381,8 @@ namespace Bit.App.Pages { await _deviceActionService.HideLoadingAsync(); } - await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent); + await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent, + AppResources.AnErrorHasOccurred, AppResources.Ok); return false; } } diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml new file mode 100644 index 000000000..a9fefa6a4 --- /dev/null +++ b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml.cs b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml.cs new file mode 100644 index 000000000..ae2ee4787 --- /dev/null +++ b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml.cs @@ -0,0 +1,83 @@ +using Bit.Core.Abstractions; +using Bit.Core.Utilities; +using System; +using Bit.App.Resources; +using Xamarin.Forms; + +namespace Bit.App.Pages +{ + public partial class UpdateTempPasswordPage : BaseContentPage + { + private readonly IMessagingService _messagingService; + private readonly IPlatformUtilsService _platformUtilsService; + private readonly UpdateTempPasswordPageViewModel _vm; + + public UpdateTempPasswordPage() + { + // Service Init + _messagingService = ServiceContainer.Resolve("messagingService"); + _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + + // Service Use + _messagingService.Send("showStatusBar", true); + + // Binding + InitializeComponent(); + _vm = BindingContext as UpdateTempPasswordPageViewModel; + _vm.Page = this; + + // Actions Declaration + _vm.LogOutAction = () => + { + _messagingService.Send("logout"); + }; + _vm.UpdateTempPasswordSuccessAction = () => Device.BeginInvokeOnMainThread(UpdateTempPasswordSuccess); + + // Link fields that will be referenced in codebehind + MasterPasswordEntry = _masterPassword; + ConfirmMasterPasswordEntry = _confirmMasterPassword; + + // Return Types and Commands + _masterPassword.ReturnType = ReturnType.Next; + _masterPassword.ReturnCommand = new Command(() => _confirmMasterPassword.Focus()); + _confirmMasterPassword.ReturnType = ReturnType.Next; + _confirmMasterPassword.ReturnCommand = new Command(() => _hint.Focus()); + } + + public Entry MasterPasswordEntry { get; set; } + public Entry ConfirmMasterPasswordEntry { get; set; } + + protected override async void OnAppearing() + { + base.OnAppearing(); + await _vm.InitAsync(); + RequestFocus(_masterPassword); + } + + private async void Submit_Clicked(object sender, EventArgs e) + { + if (DoOnce()) + { + await _vm.SubmitAsync(); + } + } + + private async void LogOut_Clicked(object sender, EventArgs e) + { + if (DoOnce()) + { + var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LogoutConfirmation, + AppResources.LogOut, AppResources.Yes, AppResources.Cancel); + if (confirmed) + { + _vm.LogOutAction(); + } + } + } + + private void UpdateTempPasswordSuccess() + { + _messagingService.Send("logout"); + } + } +} diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs b/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs new file mode 100644 index 000000000..91dc14e0d --- /dev/null +++ b/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs @@ -0,0 +1,90 @@ +using Bit.App.Resources; +using System; +using System.Threading.Tasks; +using Bit.Core.Exceptions; +using Bit.Core.Models.Request; +using Xamarin.Forms; + +namespace Bit.App.Pages +{ + public class UpdateTempPasswordPageViewModel : BaseChangePasswordViewModel + { + public UpdateTempPasswordPageViewModel() + { + PageTitle = AppResources.UpdateMasterPassword; + TogglePasswordCommand = new Command(TogglePassword); + ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword); + SubmitCommand = new Command(async () => await SubmitAsync()); + } + + public Command SubmitCommand { get; } + public Command TogglePasswordCommand { get; } + public Command ToggleConfirmPasswordCommand { get; } + public Action UpdateTempPasswordSuccessAction { get; set; } + public Action LogOutAction { get; set; } + + public void TogglePassword() + { + ShowPassword = !ShowPassword; + (Page as UpdateTempPasswordPage).MasterPasswordEntry.Focus(); + } + + public void ToggleConfirmPassword() + { + ShowPassword = !ShowPassword; + (Page as UpdateTempPasswordPage).ConfirmMasterPasswordEntry.Focus(); + } + + public async Task SubmitAsync() + { + if (!await ValidateMasterPasswordAsync()) + { + return; + } + + // Retrieve details for key generation + var kdf = await _userService.GetKdfAsync(); + var kdfIterations = await _userService.GetKdfIterationsAsync(); + var email = await _userService.GetEmailAsync(); + + // Create new key and hash new password + var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations); + var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); + + // Create new encKey for the User + var newEncKey = await _cryptoService.RemakeEncKeyAsync(key); + + // Create request + var request = new UpdateTempPasswordRequest + { + Key = newEncKey.Item2.EncryptedString, + NewMasterPasswordHash = masterPasswordHash, + MasterPasswordHint = Hint + }; + + // Initiate API action + try + { + await _deviceActionService.ShowLoadingAsync(AppResources.UpdatingPassword); + await _apiService.PutUpdateTempPasswordAsync(request); + await _deviceActionService.HideLoadingAsync(); + + UpdateTempPasswordSuccessAction?.Invoke(); + } + catch (ApiException e) + { + await _deviceActionService.HideLoadingAsync(); + if (e?.Error != null) + { + await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), + AppResources.AnErrorHasOccurred, AppResources.Ok); + } + else + { + await _platformUtilsService.ShowDialogAsync(AppResources.UpdatePasswordError, + AppResources.AnErrorHasOccurred, AppResources.Ok); + } + } + } + } +} diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index 956f4f922..85b73bdc9 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -5362,6 +5362,36 @@ namespace Bit.App.Resources { } } + public static string UpdatedMasterPassword { + get { + return ResourceManager.GetString("UpdatedMasterPassword", resourceCulture); + } + } + + public static string UpdateMasterPassword { + get { + return ResourceManager.GetString("UpdateMasterPassword", resourceCulture); + } + } + + public static string UpdateMasterPasswordWarning { + get { + return ResourceManager.GetString("UpdateMasterPasswordWarning", resourceCulture); + } + } + + public static string UpdatingPassword { + get { + return ResourceManager.GetString("UpdatingPassword", resourceCulture); + } + } + + public static string UpdatePasswordError { + get { + return ResourceManager.GetString("UpdatePasswordError", resourceCulture); + } + } + /// /// Looks up a localized string similar to Your account's fingerprint phrase. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 71b086221..e69880ae3 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -2016,6 +2016,21 @@ Captcha Failed. Please try again. + + Updated Master Password + + + Update Master Password + + + Your Master Password was recently changed by an administrator in your organization. In order to access the vault, you must update your Master Password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour. + + + Updating Password + + + Currently unable to update password + FIDO2 WebAuthn diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index 6f605aafa..6192fb9da 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -462,5 +462,40 @@ namespace Bit.App.Utilities var multiByteEscaped = Regex.Replace(escaped, "%([0-9A-F]{2})", EncodeMultibyte); return Convert.ToBase64String(Encoding.UTF8.GetBytes(multiByteEscaped)); } + + public static async Task LogOutAsync() + { + var userService = ServiceContainer.Resolve("userService"); + var syncService = ServiceContainer.Resolve("syncService"); + var tokenService = ServiceContainer.Resolve("tokenService"); + var cryptoService = ServiceContainer.Resolve("cryptoService"); + var settingsService = ServiceContainer.Resolve("settingsService"); + var cipherService = ServiceContainer.Resolve("cipherService"); + var folderService = ServiceContainer.Resolve("folderService"); + var collectionService = ServiceContainer.Resolve("collectionService"); + var passwordGenerationService = ServiceContainer.Resolve( + "passwordGenerationService"); + var vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); + var stateService = ServiceContainer.Resolve("stateService"); + var deviceActionService = ServiceContainer.Resolve("deviceActionService"); + var searchService = ServiceContainer.Resolve("searchService"); + + var userId = await userService.GetUserIdAsync(); + await Task.WhenAll( + syncService.SetLastSyncAsync(DateTime.MinValue), + tokenService.ClearTokenAsync(), + cryptoService.ClearKeysAsync(), + userService.ClearAsync(), + settingsService.ClearAsync(userId), + cipherService.ClearAsync(userId), + folderService.ClearAsync(userId), + collectionService.ClearAsync(userId), + passwordGenerationService.ClearAsync(), + vaultTimeoutService.ClearAsync(), + stateService.PurgeAsync(), + deviceActionService.ClearCacheAsync()); + vaultTimeoutService.BiometricLocked = true; + searchService.ClearIndex(); + } } } diff --git a/src/Core/Abstractions/IApiService.cs b/src/Core/Abstractions/IApiService.cs index dc09b2d6b..b19a37de5 100644 --- a/src/Core/Abstractions/IApiService.cs +++ b/src/Core/Abstractions/IApiService.cs @@ -58,6 +58,7 @@ namespace Bit.Core.Abstractions Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request); Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request); Task PostEventsCollectAsync(IEnumerable request); + Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request); Task GetOrganizationKeysAsync(string id); Task GetOrganizationAutoEnrollStatusAsync(string identifier); Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId, diff --git a/src/Core/Abstractions/IUserService.cs b/src/Core/Abstractions/IUserService.cs index dbdd17e83..d7aaef82b 100644 --- a/src/Core/Abstractions/IUserService.cs +++ b/src/Core/Abstractions/IUserService.cs @@ -19,11 +19,13 @@ namespace Bit.Core.Abstractions Task GetOrganizationByIdentifierAsync(string identifier); Task GetSecurityStampAsync(); Task GetEmailVerifiedAsync(); + Task GetForcePasswordReset(); Task GetUserIdAsync(); Task IsAuthenticatedAsync(); Task ReplaceOrganizationsAsync(Dictionary organizations); Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations); Task SetSecurityStampAsync(string stamp); Task SetEmailVerifiedAsync(bool emailVerified); + Task SetForcePasswordReset(bool forcePasswordReset); } } diff --git a/src/Core/Models/Domain/AuthResult.cs b/src/Core/Models/Domain/AuthResult.cs index 258589240..404cea660 100644 --- a/src/Core/Models/Domain/AuthResult.cs +++ b/src/Core/Models/Domain/AuthResult.cs @@ -9,6 +9,7 @@ namespace Bit.Core.Models.Domain public bool CaptchaNeeded => !string.IsNullOrWhiteSpace(CaptchaSiteKey); public string CaptchaSiteKey { get; set; } public bool ResetMasterPassword { get; set; } + public bool ForcePasswordReset { get; set; } public Dictionary> TwoFactorProviders { get; set; } } } diff --git a/src/Core/Models/Request/UpdateTempPasswordRequest.cs b/src/Core/Models/Request/UpdateTempPasswordRequest.cs new file mode 100644 index 000000000..1c3ddf009 --- /dev/null +++ b/src/Core/Models/Request/UpdateTempPasswordRequest.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Models.Request +{ + public class UpdateTempPasswordRequest + { + public string NewMasterPasswordHash { get; set; } + public string MasterPasswordHint { get; set; } + public string Key { get; set; } + } +} diff --git a/src/Core/Models/Response/IdentityTokenResponse.cs b/src/Core/Models/Response/IdentityTokenResponse.cs index 27fe84fa7..8cb541e20 100644 --- a/src/Core/Models/Response/IdentityTokenResponse.cs +++ b/src/Core/Models/Response/IdentityTokenResponse.cs @@ -20,5 +20,6 @@ namespace Bit.Core.Models.Response public string TwoFactorToken { get; set; } public KdfType Kdf { get; set; } public int? KdfIterations { get; set; } + public bool ForcePasswordReset { get; set; } } } diff --git a/src/Core/Models/Response/ProfileResponse.cs b/src/Core/Models/Response/ProfileResponse.cs index 07bd1ea70..9cb9e36ba 100644 --- a/src/Core/Models/Response/ProfileResponse.cs +++ b/src/Core/Models/Response/ProfileResponse.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Bit.Core.Models.Response { @@ -15,6 +16,7 @@ namespace Bit.Core.Models.Response public string Key { get; set; } public string PrivateKey { get; set; } public string SecurityStamp { get; set; } + public bool ForcePasswordReset { get; set; } public List Organizations { get; set; } } } diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs index 6f24c52ca..6ca924419 100644 --- a/src/Core/Services/ApiService.cs +++ b/src/Core/Services/ApiService.cs @@ -177,6 +177,12 @@ namespace Bit.Core.Services return SendAsync(HttpMethod.Post, "/accounts/verify-password", request, true, false); } + + public Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request) + { + return SendAsync(HttpMethod.Put, "/accounts/update-temp-password", + request, true, false); + } #endregion diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index 59790989c..952a35fe0 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -343,6 +343,7 @@ namespace Bit.Core.Services var tokenResponse = response.TokenResponse; result.ResetMasterPassword = tokenResponse.ResetMasterPassword; + result.ForcePasswordReset = tokenResponse.ForcePasswordReset; if (tokenResponse.TwoFactorToken != null) { await _tokenService.SetTwoFactorTokenAsync(tokenResponse.TwoFactorToken, email); diff --git a/src/Core/Services/SyncService.cs b/src/Core/Services/SyncService.cs index b90716bac..7e9ea380b 100644 --- a/src/Core/Services/SyncService.cs +++ b/src/Core/Services/SyncService.cs @@ -328,6 +328,7 @@ namespace Bit.Core.Services var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o)); await _userService.ReplaceOrganizationsAsync(organizations); await _userService.SetEmailVerifiedAsync(response.EmailVerified); + await _userService.SetForcePasswordReset(response.ForcePasswordReset); } private async Task SyncFoldersAsync(string userId, List response) diff --git a/src/Core/Services/UserService.cs b/src/Core/Services/UserService.cs index 519e27f41..c71e52e07 100644 --- a/src/Core/Services/UserService.cs +++ b/src/Core/Services/UserService.cs @@ -16,6 +16,7 @@ namespace Bit.Core.Services private KdfType? _kdf; private int? _kdfIterations; private bool? _emailVerified; + private bool? _forcePasswordReset; private const string Keys_UserId = "userId"; private const string Keys_UserEmail = "userEmail"; @@ -24,6 +25,7 @@ namespace Bit.Core.Services private const string Keys_KdfIterations = "kdfIterations"; private const string Keys_OrganizationsFormat = "organizations_{0}"; private const string Keys_EmailVerified = "emailVerified"; + private const string Keys_ForcePasswordReset = "forcePasswordReset"; private readonly IStorageService _storageService; private readonly ITokenService _tokenService; @@ -59,6 +61,12 @@ namespace Bit.Core.Services await _storageService.SaveAsync(Keys_EmailVerified, emailVerified); } + public async Task SetForcePasswordReset(bool forcePasswordReset) + { + _forcePasswordReset = forcePasswordReset; + await _storageService.SaveAsync(Keys_ForcePasswordReset, forcePasswordReset); + } + public async Task GetUserIdAsync() { if (_userId == null) @@ -113,6 +121,15 @@ namespace Bit.Core.Services return _kdfIterations; } + public async Task GetForcePasswordReset() + { + if (_forcePasswordReset == null) + { + _forcePasswordReset = await _storageService.GetAsync(Keys_ForcePasswordReset); + } + return _forcePasswordReset.GetValueOrDefault(); + } + public async Task ClearAsync() { var userId = await GetUserIdAsync(); @@ -122,6 +139,7 @@ namespace Bit.Core.Services _storageService.RemoveAsync(Keys_Stamp), _storageService.RemoveAsync(Keys_Kdf), _storageService.RemoveAsync(Keys_KdfIterations), + _storageService.RemoveAsync(Keys_ForcePasswordReset), ClearOrganizationsAsync(userId)); _userId = _email = _stamp = null; _kdf = null; diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index b0a53155a..383f41a17 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -300,6 +300,22 @@ namespace Bit.iOS.Autofill return userService.IsAuthenticatedAsync(); } + private void LogoutIfAuthed() + { + NSRunLoop.Main.BeginInvokeOnMainThread(async () => + { + if (await IsAuthed()) + { + await AppHelpers.LogOutAsync(); + var deviceActionService = ServiceContainer.Resolve("deviceActionService"); + if (deviceActionService.SystemMajorVersion() >= 12) + { + await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); + } + } + }); + } + private void InitApp() { // Init Xamarin Forms @@ -353,6 +369,8 @@ namespace Bit.iOS.Autofill var loginController = navigationPage.CreateViewController(); loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; PresentViewController(loginController, true, null); + + LogoutIfAuthed(); } private void LaunchEnvironmentFlow() @@ -400,6 +418,7 @@ namespace Bit.iOS.Autofill if (loginPage.BindingContext is LoginPageViewModel vm) { vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false)); + vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.LogInSuccessAction = () => DismissLockAndContinue(); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); } @@ -408,6 +427,8 @@ namespace Bit.iOS.Autofill var loginController = navigationPage.CreateViewController(); loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; PresentViewController(loginController, true, null); + + LogoutIfAuthed(); } private void LaunchLoginSsoFlow() @@ -420,6 +441,7 @@ namespace Bit.iOS.Autofill { vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true)); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); + vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.SsoAuthSuccessAction = () => DismissLockAndContinue(); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); } @@ -428,6 +450,8 @@ namespace Bit.iOS.Autofill var loginController = navigationPage.CreateViewController(); loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; PresentViewController(loginController, true, null); + + LogoutIfAuthed(); } private void LaunchTwoFactorFlow(bool authingWithSso) @@ -448,6 +472,7 @@ namespace Bit.iOS.Autofill { vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow()); } + vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); } var navigationPage = new NavigationPage(twoFactorPage); @@ -464,6 +489,7 @@ namespace Bit.iOS.Autofill ThemeManager.ApplyResourcesToPage(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { + vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.SetPasswordSuccessAction = () => DismissLockAndContinue(); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); } @@ -473,5 +499,23 @@ namespace Bit.iOS.Autofill setPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; PresentViewController(setPasswordController, true, null); } + + private void LaunchUpdateTempPasswordFlow() + { + var updateTempPasswordPage = new UpdateTempPasswordPage(); + var app = new App.App(new AppOptions { IosExtension = true }); + ThemeManager.SetTheme(false, app.Resources); + ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); + if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) + { + vm.UpdateTempPasswordSuccessAction = () => DismissViewController(false, () => LaunchHomePage()); + vm.LogOutAction = () => DismissViewController(false, () => LaunchHomePage()); + } + + var navigationPage = new NavigationPage(updateTempPasswordPage); + var updateTempPasswordController = navigationPage.CreateViewController(); + updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; + PresentViewController(updateTempPasswordController, true, null); + } } } diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index 6d3bc6049..ff11e27bf 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -288,34 +288,8 @@ namespace Bit.iOS.Core.Controllers private async Task LogOutAsync() { - var syncService = ServiceContainer.Resolve("syncService"); - var tokenService = ServiceContainer.Resolve("tokenService"); - var settingsService = ServiceContainer.Resolve("settingsService"); - var cipherService = ServiceContainer.Resolve("cipherService"); - var folderService = ServiceContainer.Resolve("folderService"); - var collectionService = ServiceContainer.Resolve("collectionService"); - var passwordGenerationService = ServiceContainer.Resolve( - "passwordGenerationService"); - var stateService = ServiceContainer.Resolve("stateService"); - var searchService = ServiceContainer.Resolve("searchService"); + await AppHelpers.LogOutAsync(); var authService = ServiceContainer.Resolve("authService"); - - var userId = await _userService.GetUserIdAsync(); - await Task.WhenAll( - syncService.SetLastSyncAsync(DateTime.MinValue), - tokenService.ClearTokenAsync(), - _cryptoService.ClearKeysAsync(), - _userService.ClearAsync(), - settingsService.ClearAsync(userId), - cipherService.ClearAsync(userId), - folderService.ClearAsync(userId), - collectionService.ClearAsync(userId), - passwordGenerationService.ClearAsync(), - _vaultTimeoutService.ClearAsync(), - stateService.PurgeAsync(), - _deviceActionService.ClearCacheAsync()); - _vaultTimeoutService.BiometricLocked = true; - searchService.ClearIndex(); authService.LogOut(() => { Cancel?.Invoke(); diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs index 164026cfa..26cb8bfaf 100644 --- a/src/iOS.Extension/LoadingViewController.cs +++ b/src/iOS.Extension/LoadingViewController.cs @@ -1,4 +1,5 @@ -using System; +using AuthenticationServices; +using System; using System.Diagnostics; using Foundation; using UIKit; @@ -431,6 +432,22 @@ namespace Bit.iOS.Extension return userService.IsAuthenticatedAsync(); } + private void LogoutIfAuthed() + { + NSRunLoop.Main.BeginInvokeOnMainThread(async () => + { + if (await IsAuthed()) + { + await AppHelpers.LogOutAsync(); + var deviceActionService = ServiceContainer.Resolve("deviceActionService"); + if (deviceActionService.SystemMajorVersion() >= 12) + { + await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); + } + } + }); + } + private void LaunchHomePage() { var homePage = new HomePage(); @@ -450,6 +467,8 @@ namespace Bit.iOS.Extension var loginController = navigationPage.CreateViewController(); loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; PresentViewController(loginController, true, null); + + LogoutIfAuthed(); } private void LaunchEnvironmentFlow() @@ -497,6 +516,7 @@ namespace Bit.iOS.Extension if (loginPage.BindingContext is LoginPageViewModel vm) { vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false)); + vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.LogInSuccessAction = () => DismissLockAndContinue(); vm.CloseAction = () => CompleteRequest(null, null); } @@ -505,6 +525,8 @@ namespace Bit.iOS.Extension var loginController = navigationPage.CreateViewController(); loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; PresentViewController(loginController, true, null); + + LogoutIfAuthed(); } private void LaunchLoginSsoFlow() @@ -517,6 +539,7 @@ namespace Bit.iOS.Extension { vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true)); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); + vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.SsoAuthSuccessAction = () => DismissLockAndContinue(); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); } @@ -525,6 +548,8 @@ namespace Bit.iOS.Extension var loginController = navigationPage.CreateViewController(); loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; PresentViewController(loginController, true, null); + + LogoutIfAuthed(); } private void LaunchTwoFactorFlow(bool authingWithSso) @@ -545,6 +570,7 @@ namespace Bit.iOS.Extension { vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow()); } + vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); } var navigationPage = new NavigationPage(twoFactorPage); @@ -561,6 +587,7 @@ namespace Bit.iOS.Extension ThemeManager.ApplyResourcesToPage(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { + vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.SetPasswordSuccessAction = () => DismissLockAndContinue(); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); } @@ -570,5 +597,23 @@ namespace Bit.iOS.Extension setPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; PresentViewController(setPasswordController, true, null); } + + private void LaunchUpdateTempPasswordFlow() + { + var updateTempPasswordPage = new UpdateTempPasswordPage(); + var app = new App.App(new AppOptions { IosExtension = true }); + ThemeManager.SetTheme(false, app.Resources); + ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); + if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) + { + vm.UpdateTempPasswordSuccessAction = () => DismissViewController(false, () => LaunchHomePage()); + vm.LogOutAction = () => DismissViewController(false, () => LaunchHomePage()); + } + + var navigationPage = new NavigationPage(updateTempPasswordPage); + var updateTempPasswordController = navigationPage.CreateViewController(); + updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; + PresentViewController(updateTempPasswordController, true, null); + } } }