diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 9b67a71f3..489c2bac3 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -270,11 +270,18 @@ namespace Bit.App private async Task SwitchedAccountAsync() { await AppHelpers.OnAccountSwitchAsync(); + var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync(); Device.BeginInvokeOnMainThread(async () => { - await SetMainPageAsync(); + if (shouldTimeout) + { + await _vaultTimeoutService.ExecuteTimeoutActionAsync(); + } + else + { + await SetMainPageAsync(); + } UpdateTheme(); - await _vaultTimeoutService.CheckVaultTimeoutAsync(); }); } diff --git a/src/App/Controls/AccountViewCell/AccountViewCell.xaml b/src/App/Controls/AccountViewCell/AccountViewCell.xaml index a7f6b735a..a9c74c992 100644 --- a/src/App/Controls/AccountViewCell/AccountViewCell.xaml +++ b/src/App/Controls/AccountViewCell/AccountViewCell.xaml @@ -7,8 +7,7 @@ xmlns:u="clr-namespace:Bit.App.Utilities" x:DataType="controls:AccountViewCellViewModel"> + ColumnSpacing="0"> diff --git a/src/App/Pages/Accounts/HomePage.xaml b/src/App/Pages/Accounts/HomePage.xaml index 375a9d4e5..c499b87b6 100644 --- a/src/App/Pages/Accounts/HomePage.xaml +++ b/src/App/Pages/Accounts/HomePage.xaml @@ -89,6 +89,7 @@ diff --git a/src/App/Pages/Accounts/LockPage.xaml b/src/App/Pages/Accounts/LockPage.xaml index dde114331..06e6604f0 100644 --- a/src/App/Pages/Accounts/LockPage.xaml +++ b/src/App/Pages/Accounts/LockPage.xaml @@ -183,6 +183,7 @@ diff --git a/src/App/Pages/Accounts/LoginPage.xaml b/src/App/Pages/Accounts/LoginPage.xaml index 6b0a88e27..85f613c23 100644 --- a/src/App/Pages/Accounts/LoginPage.xaml +++ b/src/App/Pages/Accounts/LoginPage.xaml @@ -132,6 +132,7 @@ diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml index ce85a8415..28b24c1a4 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml @@ -182,6 +182,7 @@ diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index 7bdccac67..ad1e3d238 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Bit.Core; using Xamarin.Forms; namespace Bit.App.Pages @@ -137,11 +138,15 @@ namespace Bit.App.Pages { get { - // create a separate collection that includes the "add new" row - var accounts = new ExtendedObservableCollection(); - accounts.AddRange(_stateService.AccountViews); - accounts.Add(new AccountView()); - return accounts; + if (_stateService.AccountViews.Count < Constants.MaxAccounts) + { + // create a separate collection that includes the "add new" row + var accountViews = new ExtendedObservableCollection(); + accountViews.AddRange(_stateService.AccountViews); + accountViews.Add(new AccountView()); + return accountViews; + } + return _stateService.AccountViews; } } diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index 834d82acc..c06b5b272 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -25,7 +25,7 @@ namespace Bit.Core.Abstractions Task EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task GetEncKeyAsync(SymmetricCryptoKey key = null); Task> GetFingerprintAsync(string userId, byte[] publicKey = null); - Task GetKeyAsync(); + Task GetKeyAsync(string userId = null); Task GetKeyHashAsync(); Task GetOrgKeyAsync(string orgId); Task> GetOrgKeysAsync(); @@ -34,7 +34,7 @@ namespace Bit.Core.Abstractions Task CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key); Task HasEncKeyAsync(); Task HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization); - Task HasKeyAsync(); + Task HasKeyAsync(string userId = null); Task> MakeEncKeyAsync(SymmetricCryptoKey key); Task MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations); Task MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations, diff --git a/src/Core/Abstractions/IVaultTimeoutService.cs b/src/Core/Abstractions/IVaultTimeoutService.cs index ef23b98f4..1dca3c2d5 100644 --- a/src/Core/Abstractions/IVaultTimeoutService.cs +++ b/src/Core/Abstractions/IVaultTimeoutService.cs @@ -6,10 +6,12 @@ namespace Bit.Core.Abstractions public interface IVaultTimeoutService { Task CheckVaultTimeoutAsync(); + Task ShouldTimeoutAsync(string userId = null); + Task ExecuteTimeoutActionAsync(string userId = null); Task ClearAsync(string userId = null); Task IsLockedAsync(string userId = null); - Task> IsPinLockSetAsync(); - Task IsBiometricLockSetAsync(); + Task> IsPinLockSetAsync(string userId = null); + Task IsBiometricLockSetAsync(string userId = null); Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null); Task LogOutAsync(bool userInitiated = true, string userId = null); Task SetVaultTimeoutOptionsAsync(int? timeout, string action); diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 9bca4a0a6..6df2df9c8 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -2,6 +2,7 @@ { public static class Constants { + public const int MaxAccounts = 5; public const string AndroidAppProtocol = "androidapp://"; public const string iOSAppProtocol = "iosapp://"; public static string StateKey = "state"; diff --git a/src/Core/Models/Domain/Account.cs b/src/Core/Models/Domain/Account.cs index 091dd9d69..32260b064 100644 --- a/src/Core/Models/Domain/Account.cs +++ b/src/Core/Models/Domain/Account.cs @@ -5,7 +5,6 @@ namespace Bit.Core.Models.Domain { public class Account : Domain { - public AuthenticationStatus? AuthStatus; public AccountProfile Profile; public AccountTokens Tokens; public AccountSettings Settings; @@ -24,7 +23,6 @@ namespace Bit.Core.Models.Domain public Account(Account account) { // Copy constructor excludes Keys (for storage) - AuthStatus = account.AuthStatus; Profile = new AccountProfile(account.Profile); Tokens = new AccountTokens(account.Tokens); Settings = new AccountSettings(account.Settings); diff --git a/src/Core/Models/View/AccountView.cs b/src/Core/Models/View/AccountView.cs index f47265e9c..db16f3254 100644 --- a/src/Core/Models/View/AccountView.cs +++ b/src/Core/Models/View/AccountView.cs @@ -15,7 +15,6 @@ namespace Bit.Core.Models.View return; } IsAccount = true; - AuthStatus = a.AuthStatus; UserId = a.Profile?.UserId; Email = a.Profile?.Email; Hostname = a.Settings?.EnvironmentUrls?.Base; diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 454743a34..3b3318be7 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -80,18 +80,18 @@ namespace Bit.Core.Services await _stateService.SetOrgKeysEncryptedAsync(orgKeys); } - public async Task GetKeyAsync() + public async Task GetKeyAsync(string userId = null) { - var inMemoryKey = await _stateService.GetKeyDecryptedAsync(); + var inMemoryKey = await _stateService.GetKeyDecryptedAsync(userId); if (inMemoryKey != null) { return inMemoryKey; } - var key = await _stateService.GetKeyEncryptedAsync(); + var key = await _stateService.GetKeyEncryptedAsync(userId); if (key != null) { inMemoryKey = new SymmetricCryptoKey(Convert.FromBase64String(key)); - await _stateService.SetKeyDecryptedAsync(inMemoryKey); + await _stateService.SetKeyDecryptedAsync(inMemoryKey, userId); } return inMemoryKey; } @@ -295,9 +295,9 @@ namespace Bit.Core.Services return false; } - public async Task HasKeyAsync() + public async Task HasKeyAsync(string userId = null) { - var key = await GetKeyAsync(); + var key = await GetKeyAsync(userId); return key != null; } diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index 855c1fdeb..9544c2920 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -89,12 +89,17 @@ namespace Bit.Core.Services { await CheckStateAsync(); - AccountViews = new ExtendedObservableCollection(); + if (AccountViews == null) + { + AccountViews = new ExtendedObservableCollection(); + } + AccountViews.Clear(); var accountList = _state?.Accounts?.Values.ToList(); if (accountList == null) { return; } + var vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); foreach (var account in accountList) { var accountView = new AccountView(account); @@ -104,7 +109,9 @@ namespace Bit.Core.Services } else { - if (await IsLockedAsync(accountView.UserId)) + var isLocked = await vaultTimeoutService.IsLockedAsync(accountView.UserId); + var shouldTimeout = await vaultTimeoutService.ShouldTimeoutAsync(accountView.UserId); + if (isLocked || shouldTimeout) { var action = account.Settings.VaultTimeoutAction; accountView.AuthStatus = action == "logOut" ? AuthenticationStatus.LoggedOut diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index 5809ffa0b..898ba87e8 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -54,10 +54,10 @@ namespace Bit.Core.Services public async Task IsLockedAsync(string userId = null) { - var hasKey = await _cryptoService.HasKeyAsync(); + var hasKey = await _cryptoService.HasKeyAsync(userId); if (hasKey) { - var biometricSet = await IsBiometricLockSetAsync(); + var biometricSet = await IsBiometricLockSetAsync(userId); if (biometricSet && _stateService.BiometricLocked) { return true; @@ -73,16 +73,13 @@ namespace Bit.Core.Services return; } - foreach (var userId in await _stateService.GetUserIdsAsync()) + if (await ShouldTimeoutAsync()) { - if (userId != null && await ShouldLockAsync(userId)) - { - await ExecuteTimeoutActionAsync(userId); - } + await ExecuteTimeoutActionAsync(); } } - private async Task ShouldLockAsync(string userId) + public async Task ShouldTimeoutAsync(string userId = null) { var authed = await _stateService.IsAuthenticatedAsync(userId); if (!authed) @@ -108,7 +105,7 @@ namespace Bit.Core.Services return diffMs >= vaultTimeoutMs; } - private async Task ExecuteTimeoutActionAsync(string userId) + public async Task ExecuteTimeoutActionAsync(string userId = null) { var action = await _stateService.GetVaultTimeoutActionAsync(userId); if (action == "logOut") @@ -130,8 +127,8 @@ namespace Bit.Core.Services } if (await _keyConnectorService.GetUsesKeyConnector()) { - var pinSet = await IsPinLockSetAsync(); - var pinLock = (pinSet.Item1 && _stateService.GetPinProtectedAsync() != null) || pinSet.Item2; + var pinSet = await IsPinLockSetAsync(userId); + var pinLock = (pinSet.Item1 && _stateService.GetPinProtectedAsync(userId) != null) || pinSet.Item2; if (!pinLock && !await IsBiometricLockSetAsync()) { @@ -142,21 +139,14 @@ namespace Bit.Core.Services if (allowSoftLock) { - var biometricLocked = await IsBiometricLockSetAsync(); - _stateService.BiometricLocked = biometricLocked; - if (biometricLocked) + _stateService.BiometricLocked = await IsBiometricLockSetAsync(); + if (_stateService.BiometricLocked) { _messagingService.Send("locked", userInitiated); _lockedCallback?.Invoke(userInitiated); return; } } - - if (userId == null || userId == await _stateService.GetActiveUserIdAsync()) - { - _searchService.ClearIndex(); - } - await Task.WhenAll( _cryptoService.ClearKeyAsync(userId), _cryptoService.ClearOrgKeysAsync(true, userId), @@ -187,16 +177,16 @@ namespace Bit.Core.Services await _tokenService.ToggleTokensAsync(); } - public async Task> IsPinLockSetAsync() + public async Task> IsPinLockSetAsync(string userId = null) { - var protectedPin = await _stateService.GetProtectedPinAsync(); - var pinProtectedKey = await _stateService.GetPinProtectedAsync(); + var protectedPin = await _stateService.GetProtectedPinAsync(userId); + var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId); return new Tuple(protectedPin != null, pinProtectedKey != null); } - public async Task IsBiometricLockSetAsync() + public async Task IsBiometricLockSetAsync(string userId = null) { - var biometricLock = await _stateService.GetBiometricUnlockAsync(); + var biometricLock = await _stateService.GetBiometricUnlockAsync(userId); return biometricLock.GetValueOrDefault(); }