1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

logout fixes and token handling improvements

This commit is contained in:
Matt Portune
2022-01-19 16:15:09 -05:00
parent e717992b2b
commit d41e1a0bc3
13 changed files with 75 additions and 107 deletions

View File

@@ -290,13 +290,23 @@ namespace Bit.App
private async Task SetMainPageAsync()
{
await _stateService.RefreshAccountViewsAsync();
var authed = await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLockedAsync())
var isLocked = await _vaultTimeoutService.IsLockedAsync();
var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync();
var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (isLocked || shouldTimeout)
{
Current.MainPage = new NavigationPage(new LockPage(Options));
if (vaultTimeoutAction == "logOut")
{
var email = await _stateService.GetEmailAsync();
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{

View File

@@ -45,7 +45,7 @@ namespace Bit.App.Pages
_mainContent.Content = _mainLayout;
if (await ShowAccountSwitcherAsync())
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(false);
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
else
{
@@ -146,7 +146,7 @@ namespace Bit.App.Pages
}
else
{
await RefreshAccountViewsAsync(_accountListView);
await RefreshAccountViewsAsync(_accountListView, false);
await ShowAccountListAsync(true, _accountListContainer, _accountListOverlay);
}
}

View File

@@ -163,7 +163,7 @@ namespace Bit.App.Pages
}
else
{
await RefreshAccountViewsAsync(_accountListView);
await RefreshAccountViewsAsync(_accountListView, false);
await ShowAccountListAsync(true, _accountListContainer, _accountListOverlay);
}
}

View File

@@ -145,7 +145,7 @@ namespace Bit.App.Pages
}
else
{
await RefreshAccountViewsAsync(_accountListView);
await RefreshAccountViewsAsync(_accountListView, false);
await ShowAccountListAsync(true, _accountListContainer, _accountListOverlay);
}
}

View File

@@ -110,13 +110,12 @@ namespace Bit.App.Pages
protected async Task<bool> ShowAccountSwitcherAsync()
{
return await _stateService.HasMultipleAccountsAsync()
|| await _stateService.IsAuthenticatedAsync();
return await _stateService.HasMultipleAccountsAsync();
}
protected async Task RefreshAccountViewsAsync(Xamarin.Forms.ListView accountListView)
protected async Task RefreshAccountViewsAsync(Xamarin.Forms.ListView accountListView, bool allowAddAccountRow)
{
await _stateService.RefreshAccountViewsAsync();
await _stateService.RefreshAccountViewsAsync(allowAddAccountRow);
// Property change trigger on account listview is yielding inconsistent results, using a hammer instead
accountListView.ItemsSource = null;
accountListView.ItemsSource = _stateService.AccountViews;

View File

@@ -302,7 +302,7 @@ namespace Bit.App.Pages
}
else
{
await RefreshAccountViewsAsync(_accountListView);
await RefreshAccountViewsAsync(_accountListView, true);
await ShowAccountListAsync(true, _accountListContainer, _accountListOverlay, _fab);
}
}

View File

@@ -136,18 +136,7 @@ namespace Bit.App.Pages
}
public ExtendedObservableCollection<AccountView> AccountViews
{
get
{
if (_stateService.AccountViews.Count < Constants.MaxAccounts)
{
// create a separate collection that includes the "add new" row
var accountViews = new ExtendedObservableCollection<AccountView>();
accountViews.AddRange(_stateService.AccountViews);
accountViews.Add(new AccountView());
return accountViews;
}
return _stateService.AccountViews;
}
get => _stateService.AccountViews;
}
public ExtendedObservableCollection<GroupingsPageListGroup> GroupedItems { get; set; }

View File

@@ -18,7 +18,7 @@ namespace Bit.Core.Abstractions
Task SetActiveUserAsync(string userId);
Task<bool> IsAuthenticatedAsync(string userId = null);
Task<bool> HasMultipleAccountsAsync();
Task RefreshAccountViewsAsync();
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
Task AddAccountAsync(Account account);
Task ClearAsync(string userId);
Task<EnvironmentUrlData> GetPreAuthEnvironmentUrlsAsync();
@@ -154,9 +154,9 @@ namespace Bit.Core.Abstractions
Task<Dictionary<string, object>> GetSettingsAsync(string userId = null);
Task SetSettingsAsync(Dictionary<string, object> value, string userId = null);
Task<string> GetAccessTokenAsync(string userId = null);
Task SetAccessTokenAsync(string value, string userId = null);
Task SetAccessTokenAsync(string value, bool skipTokenStorage, string userId = null);
Task<string> GetRefreshTokenAsync(string userId = null);
Task SetRefreshTokenAsync(string value, string userId = null);
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
Task<string> GetTwoFactorTokenAsync(string email = null);
Task SetTwoFactorTokenAsync(string value, string email = null);
}

View File

@@ -23,7 +23,7 @@ namespace Bit.Core.Abstractions
Task<string> GetTwoFactorTokenAsync(string email);
string GetUserId();
Task SetRefreshTokenAsync(string refreshToken);
Task SetTokenAsync(string token);
Task SetAccessTokenAsync(string token);
Task SetTokensAsync(string accessToken, string refreshToken);
Task SetTwoFactorTokenAsync(string token, string email);
bool TokenNeedsRefresh(int minutes = 5);

View File

@@ -8,5 +8,6 @@ namespace Bit.Core.Models.Domain
public bool? UseSecureStorage { get; set; }
public string UserId { get; set; }
public string Email { get; set; }
public bool? SkipTokenStorage { get; set; }
}
}

View File

@@ -72,10 +72,6 @@ namespace Bit.Core.Services
public async Task<bool> IsAuthenticatedAsync(string userId = null)
{
if (userId != null)
{
return await GetAccessTokenAsync(userId) != null && (_state?.Accounts?.ContainsKey(userId) ?? false);
}
return await GetAccessTokenAsync(userId) != null;
}
@@ -85,7 +81,7 @@ namespace Bit.Core.Services
return _state.Accounts?.Count > 1;
}
public async Task RefreshAccountViewsAsync()
public async Task RefreshAccountViewsAsync(bool allowAddAccountRow)
{
await CheckStateAsync();
@@ -124,13 +120,17 @@ namespace Bit.Core.Services
}
AccountViews.Add(accountView);
}
if (allowAddAccountRow && AccountViews.Count < Constants.MaxAccounts)
{
AccountViews.Add(new AccountView());
}
}
public async Task AddAccountAsync(Account account)
{
await ScaffoldNewAccountAsync(account);
await SetActiveUserAsync(account.Profile.UserId);
await RefreshAccountViewsAsync();
await RefreshAccountViewsAsync(true);
}
public async Task ClearAsync(string userId)
@@ -1224,9 +1224,10 @@ namespace Bit.Core.Services
))?.Tokens?.AccessToken;
}
public async Task SetAccessTokenAsync(string value, string userId = null)
public async Task SetAccessTokenAsync(string value, bool skipTokenStorage, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
var reconciledOptions = ReconcileOptions(
new StorageOptions { UserId = userId, SkipTokenStorage = skipTokenStorage },
await GetDefaultStorageOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.Tokens.AccessToken = value;
@@ -1240,9 +1241,10 @@ namespace Bit.Core.Services
))?.Tokens?.RefreshToken;
}
public async Task SetRefreshTokenAsync(string value, string userId = null)
public async Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
var reconciledOptions = ReconcileOptions(
new StorageOptions { UserId = userId, SkipTokenStorage = skipTokenStorage },
await GetDefaultStorageOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.Tokens.RefreshToken = value;
@@ -1356,7 +1358,7 @@ namespace Bit.Core.Services
state.Accounts[account.Profile.UserId] = new Account(account);
// If we have a vault timeout and the action is log out, don't store token
if (await SkipTokenStorageAsync())
if (options?.SkipTokenStorage.GetValueOrDefault() ?? false)
{
state.Accounts[account.Profile.UserId].Tokens.AccessToken = null;
state.Accounts[account.Profile.UserId].Tokens.RefreshToken = null;
@@ -1366,13 +1368,6 @@ namespace Bit.Core.Services
}
}
private async Task<bool> SkipTokenStorageAsync()
{
var timeout = await GetVaultTimeoutAsync();
var action = await GetVaultTimeoutActionAsync();
return timeout.HasValue && action == "logOut";
}
private async Task RemoveAccountAsync(string userId)
{
if (userId == null)
@@ -1468,6 +1463,7 @@ namespace Bit.Core.Services
requestedOptions.UseSecureStorage = requestedOptions.UseSecureStorage ?? defaultOptions.UseSecureStorage;
requestedOptions.UserId = requestedOptions.UserId ?? defaultOptions.UserId;
requestedOptions.Email = requestedOptions.Email ?? defaultOptions.Email;
requestedOptions.SkipTokenStorage = requestedOptions.SkipTokenStorage ?? defaultOptions.SkipTokenStorage;
return requestedOptions;
}

View File

@@ -12,9 +12,8 @@ namespace Bit.Core.Services
{
private readonly IStateService _stateService;
private string _token;
private JObject _decodedToken;
private string _refreshToken;
private string _accessTokenForDecoding;
private JObject _decodedAccessToken;
public TokenService(IStateService stateService)
{
@@ -24,70 +23,39 @@ namespace Bit.Core.Services
public async Task SetTokensAsync(string accessToken, string refreshToken)
{
await Task.WhenAll(
SetTokenAsync(accessToken),
SetAccessTokenAsync(accessToken),
SetRefreshTokenAsync(refreshToken));
}
public async Task SetTokenAsync(string token)
public async Task SetAccessTokenAsync(string accessToken)
{
_token = token;
_decodedToken = null;
if (await SkipTokenStorage())
{
// If we have a vault timeout and the action is log out, don't store token
return;
}
// await _stateService.SetAccessTokenAsync(token);
_accessTokenForDecoding = accessToken;
_decodedAccessToken = null;
await _stateService.SetAccessTokenAsync(accessToken, await SkipTokenStorage());
}
public async Task<string> GetTokenAsync()
{
if (_token != null)
{
return _token;
}
_token = await _stateService.GetAccessTokenAsync();
return _token;
_accessTokenForDecoding = await _stateService.GetAccessTokenAsync();
return _accessTokenForDecoding;
}
public async Task SetRefreshTokenAsync(string refreshToken)
{
_refreshToken = refreshToken;
if (await SkipTokenStorage())
{
// If we have a vault timeout and the action is log out, don't store token
return;
}
// await _stateService.SetRefreshTokenAsync(refreshToken);
await _stateService.SetRefreshTokenAsync(refreshToken, await SkipTokenStorage());
}
public async Task<string> GetRefreshTokenAsync()
{
if (_refreshToken != null)
{
return _refreshToken;
}
_refreshToken = await _stateService.GetRefreshTokenAsync();
return _refreshToken;
return await _stateService.GetRefreshTokenAsync();
}
public async Task ToggleTokensAsync()
{
// load and re-save tokens to reflect latest value of SkipTokenStorage()
var token = await GetTokenAsync();
var refreshToken = await GetRefreshTokenAsync();
if (await SkipTokenStorage())
{
await ClearTokenAsync();
_token = token;
_refreshToken = refreshToken;
return;
}
await SetTokenAsync(token);
await SetAccessTokenAsync(token);
await SetRefreshTokenAsync(refreshToken);
}
@@ -110,28 +78,27 @@ namespace Bit.Core.Services
{
ClearCache();
await Task.WhenAll(
_stateService.SetAccessTokenAsync(null, userId),
_stateService.SetRefreshTokenAsync(null, userId));
_stateService.SetAccessTokenAsync(null, false, userId),
_stateService.SetRefreshTokenAsync(null, false, userId));
}
public void ClearCache()
{
_token = null;
_decodedToken = null;
_refreshToken = null;
_accessTokenForDecoding = null;
_decodedAccessToken = null;
}
public JObject DecodeToken()
{
if (_decodedToken != null)
if (_decodedAccessToken != null)
{
return _decodedToken;
return _decodedAccessToken;
}
if (_token == null)
if (_accessTokenForDecoding == null)
{
throw new InvalidOperationException("Token not found.");
throw new InvalidOperationException("Access token not found.");
}
var parts = _token.Split('.');
var parts = _accessTokenForDecoding.Split('.');
if (parts.Length != 3)
{
throw new InvalidOperationException("JWT must have 3 parts.");
@@ -141,8 +108,8 @@ namespace Bit.Core.Services
{
throw new InvalidOperationException("Cannot decode the token.");
}
_decodedToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes));
return _decodedToken;
_decodedAccessToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes));
return _decodedAccessToken;
}
public DateTime? GetTokenExpirationDate()
@@ -234,7 +201,7 @@ namespace Bit.Core.Services
public async Task<bool> GetIsExternal()
{
if (_token == null)
if (_accessTokenForDecoding == null)
{
await GetTokenAsync();
}

View File

@@ -141,11 +141,15 @@ namespace Bit.iOS
await ASHelpers.ReplaceAllIdentities();
}
}
else if (message.Command == "loggedOut")
else if (message.Command == "logout")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{
// TODO make account-specific
var extras = message.Data as Tuple<string, bool, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2;
var expired = extras?.Item3;
// TODO make specific to userId
// await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
}
@@ -159,7 +163,9 @@ namespace Bit.iOS
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (timeoutAction == "logOut")
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
var userId = await _stateService.GetActiveUserIdAsync();
// TODO make specific to userId
// await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
else
{