[SG-813] Not You? crashes app after vault logout timeout (#2184)

* Merge branch 'master' into feature/SG-174-login-with-device

* [SG-813] Fix merge

* [SG-813] rename HomePage parameter name

* [SG-813] Added NavParams for home page on account switching.

* [SG-813] Remove account showing when adding new account.

* [SG-813] Add account switch pop up if email already exists

* [SG-813] Add default account avatar to HomePage

* [SG-813] Code format

* [SG-813] Remove unused import

* [SG-813] Renamed checkNavigateLogin to shouldCheckRememberEmail

* [SG-813] Move prompt account switch to account manager service

* [SG-813] Remove Account button appears if email is the same as the ActiveUser

* [SG-813] Fix code duplicate

* [SG-813] Fix for android RemoveAccount button

* [SG-813] Code format
This commit is contained in:
André Bispo 2022-11-11 18:14:30 +00:00 committed by GitHub
parent 20c1e2d7f2
commit 7e8e86a77a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 123 additions and 57 deletions

View File

@ -9,5 +9,6 @@ namespace Bit.App.Abstractions
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
Task LogOutAsync(string userId, bool userInitiated, bool expired);
Task PromptToSwitchToExistingAccountAsync(string userId);
}
}

View File

@ -460,7 +460,14 @@ namespace Bit.App
switch (navTarget)
{
case NavigationTarget.HomeLogin:
Current.MainPage = new NavigationPage(new HomePage(Options));
if (navParams is HomeNavigationParams homeParams)
{
Current.MainPage = new NavigationPage(new HomePage(Options, homeParams.ShouldCheckRememberEmail));
}
else
{
Current.MainPage = new NavigationPage(new HomePage(Options));
}
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)

View File

@ -15,14 +15,14 @@ namespace Bit.App.Pages
private readonly AppOptions _appOptions;
private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null, bool checkRememberedEmail = true)
public HomePage(AppOptions appOptions = null, bool shouldCheckRememberEmail = true)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as HomeViewModel;
_vm.Page = this;
_vm.CheckHasRememberedEmail = checkRememberedEmail;
_vm.ShouldCheckRememberEmail = shouldCheckRememberEmail;
_vm.ShowCancelButton = _appOptions?.IosExtension ?? false;
_vm.StartLoginAction = async () => await StartLoginAsync();
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
@ -59,7 +59,7 @@ namespace Bit.App.Pages
if (!_appOptions?.HideAccountSwitcher ?? false)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(false);
}
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
{

View File

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
@ -24,13 +25,17 @@ namespace Bit.App.Pages
private bool _canLogin;
private IPlatformUtilsService _platformUtilsService;
private ILogger _logger;
private IEnvironmentService _environmentService;
private IAccountsManager _accountManager;
public HomeViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>();
_messagingService = ServiceContainer.Resolve<IMessagingService>();
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
_logger = ServiceContainer.Resolve<ILogger>("logger");
_logger = ServiceContainer.Resolve<ILogger>();
_environmentService = ServiceContainer.Resolve<IEnvironmentService>();
_accountManager = ServiceContainer.Resolve<IAccountsManager>();
PageTitle = AppResources.Bitwarden;
@ -68,7 +73,7 @@ namespace Bit.App.Pages
public bool CanContinue => !string.IsNullOrEmpty(Email);
public bool CheckHasRememberedEmail { get; set; }
public bool ShouldCheckRememberEmail { get; set; }
public FormattedString CreateAccountText
{
@ -107,11 +112,11 @@ namespace Bit.App.Pages
public void CheckNavigateLoginStep()
{
if (CheckHasRememberedEmail && RememberEmail)
if (ShouldCheckRememberEmail && RememberEmail)
{
StartLoginAction();
}
CheckHasRememberedEmail = false;
ShouldCheckRememberEmail = false;
}
public async Task ContinueToLoginStepAsync()
@ -132,6 +137,16 @@ namespace Bit.App.Pages
return;
}
await _stateService.SetRememberedEmailAsync(RememberEmail ? Email : null);
var userId = await _stateService.GetUserIdAsync(Email);
if (!string.IsNullOrWhiteSpace(userId))
{
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
if (userEnvUrls?.Base == _environmentService.BaseUrl)
{
await _accountManager.PromptToSwitchToExistingAccountAsync(userId);
return;
}
}
StartLoginAction();
}
catch (Exception ex)

View File

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
@ -53,11 +54,6 @@ namespace Bit.App.Pages
ToolbarItems.Add(_getPasswordHint);
}
if (Device.RuntimePlatform == Device.Android && !_vm.IsEmailEnabled)
{
ToolbarItems.Add(_removeAccount);
}
if (_appOptions?.IosExtension ?? false)
{
_vm.ShowCancelButton = true;
@ -77,16 +73,20 @@ namespace Bit.App.Pages
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
await _vm.InitAsync();
if (!_appOptions?.HideAccountSwitcher ?? false)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(_vm.EmailIsInSavedAccounts);
}
await _vm.InitAsync();
if (!_inputFocused)
{
RequestFocus(_masterPassword);
_inputFocused = true;
}
if (Device.RuntimePlatform == Device.Android && !_vm.CanRemoveAccount)
{
ToolbarItems.Add(_removeAccount);
}
}
protected override bool OnBackButtonPressed()

View File

@ -8,9 +8,11 @@ using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@ -31,6 +33,7 @@ namespace Bit.App.Pages
private readonly ILogger _logger;
private readonly IApiService _apiService;
private readonly IAppIdService _appIdService;
private readonly IAccountsManager _accountManager;
private bool _showPassword;
private bool _showCancelButton;
private string _email;
@ -51,6 +54,7 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>("logger");
_apiService = ServiceContainer.Resolve<IApiService>();
_appIdService = ServiceContainer.Resolve<IAppIdService>();
_accountManager = ServiceContainer.Resolve<IAccountsManager>();
PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword);
@ -110,10 +114,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _isKnownDevice, value);
}
public bool IsIosExtension { get; set; }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; }
public ICommand MoreCommand { get; internal set; }
@ -121,6 +122,8 @@ namespace Bit.App.Pages
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
public bool IsIosExtension { get; set; }
public bool CanRemoveAccount { get; set; }
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action LogInWithDeviceAction { get; set; }
@ -128,6 +131,8 @@ namespace Bit.App.Pages
public Action StartSsoLoginAction { get; set; }
public Action CloseAction { get; set; }
public bool EmailIsInSavedAccounts => _stateService.AccountViews != null && _stateService.AccountViews.Any(e => e.Email == Email);
protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService;
protected override IDeviceActionService deviceActionService => _deviceActionService;
@ -138,12 +143,14 @@ namespace Bit.App.Pages
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await AccountSwitchingOverlayViewModel.RefreshAccountViewsAsync();
if (string.IsNullOrWhiteSpace(Email))
{
Email = await _stateService.GetRememberedEmailAsync();
}
var deviceIdentifier = await _appIdService.GetAppIdAsync();
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, deviceIdentifier);
CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email;
await _deviceActionService.HideLoadingAsync();
}
catch (Exception ex)
@ -192,7 +199,7 @@ namespace Bit.App.Pages
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
if (userEnvUrls?.Base == _environmentService.BaseUrl)
{
await PromptToSwitchToExistingAccountAsync(userId);
await _accountManager.PromptToSwitchToExistingAccountAsync(userId);
return;
}
}
@ -249,8 +256,7 @@ namespace Bit.App.Pages
private async Task MoreAsync()
{
var emailExists = _stateService.AccountViews != null && _stateService.AccountViews.Any(e => e.Email == Email);
var buttons = IsEmailEnabled || !emailExists
var buttons = IsEmailEnabled || CanRemoveAccount
? new[] { AppResources.GetPasswordHint }
: new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount };
var selection = await _deviceActionService.DisplayActionSheetAsync(AppResources.Options, AppResources.Cancel, null, buttons);
@ -300,18 +306,6 @@ namespace Bit.App.Pages
}
}
private async Task PromptToSwitchToExistingAccountAsync(string userId)
{
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
AppResources.SwitchToAlreadyAddedAccountConfirmation,
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
if (switchToAccount)
{
await _stateService.SetActiveUserAsync(userId);
_messagingService.Send("switchedAccount");
}
}
private void HandleException(Exception ex)
{
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>

View File

@ -7,6 +7,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Utilities.AccountManagement
@ -103,7 +104,8 @@ namespace Bit.App.Utilities.AccountManagement
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
await _stateService.SetRememberedEmailAsync(email);
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin, new HomeNavigationParams(true));
}
else
{
@ -183,7 +185,7 @@ namespace Bit.App.Utilities.AccountManagement
await Device.InvokeOnMainThreadAsync(() =>
{
Options.HideAccountSwitcher = false;
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin, new HomeNavigationParams(false));
});
}
@ -218,5 +220,17 @@ namespace Bit.App.Utilities.AccountManagement
_messagingService.Send(AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED);
});
}
public async Task PromptToSwitchToExistingAccountAsync(string userId)
{
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
AppResources.SwitchToAlreadyAddedAccountConfirmation,
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
if (switchToAccount)
{
await _stateService.SetActiveUserAsync(userId);
_messagingService.Send("switchedAccount");
}
}
}
}

View File

@ -0,0 +1,14 @@
using Bit.App.Abstractions;
namespace Bit.App.Utilities.AccountManagement
{
public class HomeNavigationParams : INavigationParams
{
public HomeNavigationParams(bool shouldCheckRememberEmail)
{
ShouldCheckRememberEmail = shouldCheckRememberEmail;
}
public bool ShouldCheckRememberEmail { get; }
}
}

View File

@ -13,6 +13,7 @@ namespace Bit.Core.Abstractions
{
List<AccountView> AccountViews { get; }
Task<string> GetActiveUserIdAsync();
Task<string> GetActiveUserEmailAsync();
Task<bool> IsActiveAccountAsync(string userId = null);
Task SetActiveUserAsync(string userId);
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();

View File

@ -46,6 +46,12 @@ namespace Bit.Core.Services
return activeUserId;
}
public async Task<string> GetActiveUserEmailAsync()
{
var activeUserId = await GetActiveUserIdAsync();
return await GetEmailAsync(activeUserId);
}
public async Task<bool> IsActiveAccountAsync(string userId = null)
{
if (userId == null)

View File

@ -425,10 +425,10 @@ namespace Bit.iOS.Autofill
}
}
private void LaunchHomePage(bool checkRememberedEmail = true)
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
{
var appOptions = new AppOptions { IosExtension = true };
var homePage = new HomePage(appOptions, checkRememberedEmail: checkRememberedEmail);
var homePage = new HomePage(appOptions, shouldCheckRememberEmail);
var app = new App.App(appOptions);
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(homePage);
@ -457,8 +457,8 @@ namespace Bit.iOS.Autofill
ThemeManager.ApplyResourcesTo(environmentPage);
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
{
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
}
var navigationPage = new NavigationPage(environmentPage);
@ -476,7 +476,7 @@ namespace Bit.iOS.Autofill
if (registerPage.BindingContext is RegisterPageViewModel vm)
{
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
}
var navigationPage = new NavigationPage(registerPage);
@ -499,7 +499,7 @@ namespace Bit.iOS.Autofill
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email));
vm.LogInSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
}
var navigationPage = new NavigationPage(loginPage);
@ -628,7 +628,14 @@ namespace Bit.iOS.Autofill
switch (navTarget)
{
case NavigationTarget.HomeLogin:
DismissViewController(false, () => LaunchHomePage());
if (navParams is HomeNavigationParams homeParams)
{
DismissViewController(false, () => LaunchHomePage(homeParams.ShouldCheckRememberEmail));
}
else
{
DismissViewController(false, () => LaunchHomePage());
}
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)

View File

@ -446,10 +446,10 @@ namespace Bit.iOS.Extension
});
}
private void LaunchHomePage(bool checkRememberedEmail = true)
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
{
var appOptions = new AppOptions { IosExtension = true };
var homePage = new HomePage(appOptions, checkRememberedEmail: checkRememberedEmail);
var homePage = new HomePage(appOptions, shouldCheckRememberEmail);
var app = new App.App(appOptions);
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(homePage);
@ -478,8 +478,8 @@ namespace Bit.iOS.Extension
ThemeManager.ApplyResourcesTo(environmentPage);
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
{
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
}
var navigationPage = new NavigationPage(environmentPage);
@ -497,7 +497,7 @@ namespace Bit.iOS.Extension
if (registerPage.BindingContext is RegisterPageViewModel vm)
{
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
}
var navigationPage = new NavigationPage(registerPage);
@ -520,7 +520,7 @@ namespace Bit.iOS.Extension
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email));
vm.LogInSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
}
var navigationPage = new NavigationPage(loginPage);

View File

@ -287,9 +287,9 @@ namespace Bit.iOS.ShareExtension
return _app;
}
private void LaunchHomePage(bool checkRememberedEmail = true)
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
{
var homePage = new HomePage(_appOptions.Value, checkRememberedEmail: checkRememberedEmail);
var homePage = new HomePage(_appOptions.Value, shouldCheckRememberEmail);
SetupAppAndApplyResources(homePage);
if (homePage.BindingContext is HomeViewModel vm)
{
@ -311,8 +311,8 @@ namespace Bit.iOS.ShareExtension
ThemeManager.ApplyResourcesTo(environmentPage);
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
{
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(checkRememberedEmail: false));
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
}
NavigateToPage(environmentPage);
@ -325,7 +325,7 @@ namespace Bit.iOS.ShareExtension
if (registerPage.BindingContext is RegisterPageViewModel vm)
{
vm.RegistrationSuccess = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
}
NavigateToPage(registerPage);
}
@ -341,7 +341,7 @@ namespace Bit.iOS.ShareExtension
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(email));
vm.LogInSuccessAction = () => { DismissLockAndContinue(); };
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(checkRememberedEmail: false));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
}
NavigateToPage(loginPage);
@ -441,7 +441,14 @@ namespace Bit.iOS.ShareExtension
switch (navTarget)
{
case NavigationTarget.HomeLogin:
ExecuteLaunch(() => LaunchHomePage());
if (navParams is HomeNavigationParams homeParams)
{
ExecuteLaunch(() => LaunchHomePage(homeParams.ShouldCheckRememberEmail));
}
else
{
ExecuteLaunch(() => LaunchHomePage());
}
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)