[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 <addisonbeck1@gmail.com>
Co-authored-by: Matt Portune <mportune@bitwarden.com>
This commit is contained in:
Vincent Salucci 2021-09-24 13:14:26 -05:00 committed by GitHub
parent 750faf8a83
commit 271e6b3d92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 795 additions and 97 deletions

View file

@ -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<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("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();

View file

@ -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<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_apiService = ServiceContainer.Resolve<IApiService>("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<bool> 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<List<string>> GetPasswordStrengthUserInput()
{
var email = await _userService.GetEmailAsync();
List<string> 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<string>(data);
}
return userInput;
}
}
}

View file

@ -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));
}
}
}

View file

@ -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<bool?>(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);
}
}
}

View file

@ -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()
{

View file

@ -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
{

View file

@ -137,7 +137,7 @@
<FormattedString>
<Span Text="{u:I18n AcceptPolicies}" />
<Span Text="{u:I18n TermsOfService}"
TextColor="{StaticResource HyperlinkColor}">
TextColor="{DynamicResource HyperlinkColor}">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PoliciesClickCommand}"
CommandParameter="https://bitwarden.com/terms/" />
@ -145,7 +145,7 @@
</Span>
<Span Text=", " />
<Span Text="{u:I18n PrivacyPolicy}"
TextColor="{StaticResource HyperlinkColor}">
TextColor="{DynamicResource HyperlinkColor}">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PoliciesClickCommand}"
CommandParameter="https://bitwarden.com/privacy/" />

View file

@ -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);
}
}
}

View file

@ -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)

View file

@ -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()
{

View file

@ -49,7 +49,7 @@ namespace Bit.App.Pages
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_stateService = ServiceContainer.Resolve<IStateService>("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<bool?>(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;
}
}

View file

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.UpdateTempPasswordPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:UpdateTempPasswordPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:UpdateTempPasswordPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n LogOut}" Clicked="LogOut_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Grid Margin="0, 12, 0, 0"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n UpdateMasterPasswordWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
</Grid>
<Grid IsVisible="{Binding IsPolicyInEffect}"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{Binding PolicySummary}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Start" />
</Frame>
</Grid>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout StyleClass="box">
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n RetypeMasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_confirmMasterPassword"
Text="{Binding ConfirmMasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding ToggleConfirmPasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n MasterPasswordHint}"
StyleClass="box-label" />
<Entry
x:Name="_hint"
Text="{Binding Hint}"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
</StackLayout>
<Label
Text="{u:I18n MasterPasswordHintDescription}"
StyleClass="box-footer-label" />
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View file

@ -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<IMessagingService>("messagingService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("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");
}
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -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);
}
}
/// <summary>
/// Looks up a localized string similar to Your account&apos;s fingerprint phrase.
/// </summary>

View file

@ -2016,6 +2016,21 @@
<data name="CaptchaFailed" xml:space="preserve">
<value>Captcha Failed. Please try again.</value>
</data>
<data name="UpdatedMasterPassword" xml:space="preserve">
<value>Updated Master Password</value>
</data>
<data name="UpdateMasterPassword" xml:space="preserve">
<value>Update Master Password</value>
</data>
<data name="UpdateMasterPasswordWarning" xml:space="preserve">
<value>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.</value>
</data>
<data name="UpdatingPassword" xml:space="preserve">
<value>Updating Password</value>
</data>
<data name="UpdatePasswordError" xml:space="preserve">
<value>Currently unable to update password</value>
</data>
<data name="Fido2Title" xml:space="preserve">
<value>FIDO2 WebAuthn</value>
</data>

View file

@ -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<IUserService>("userService");
var syncService = ServiceContainer.Resolve<ISyncService>("syncService");
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
var settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
var folderService = ServiceContainer.Resolve<IFolderService>("folderService");
var collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
var passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
var searchService = ServiceContainer.Resolve<ISearchService>("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();
}
}
}

View file

@ -58,6 +58,7 @@ namespace Bit.Core.Abstractions
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
Task PostEventsCollectAsync(IEnumerable<EventRequest> request);
Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request);
Task<OrganizationKeysResponse> GetOrganizationKeysAsync(string id);
Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier);
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,

View file

@ -19,11 +19,13 @@ namespace Bit.Core.Abstractions
Task<Organization> GetOrganizationByIdentifierAsync(string identifier);
Task<string> GetSecurityStampAsync();
Task<bool> GetEmailVerifiedAsync();
Task<bool> GetForcePasswordReset();
Task<string> GetUserIdAsync();
Task<bool> IsAuthenticatedAsync();
Task ReplaceOrganizationsAsync(Dictionary<string, OrganizationData> organizations);
Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations);
Task SetSecurityStampAsync(string stamp);
Task SetEmailVerifiedAsync(bool emailVerified);
Task SetForcePasswordReset(bool forcePasswordReset);
}
}

View file

@ -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<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
}
}

View file

@ -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; }
}
}

View file

@ -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; }
}
}

View file

@ -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<ProfileOrganizationResponse> Organizations { get; set; }
}
}

View file

@ -177,6 +177,12 @@ namespace Bit.Core.Services
return SendAsync<PasswordVerificationRequest, object>(HttpMethod.Post, "/accounts/verify-password", request,
true, false);
}
public Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request)
{
return SendAsync<UpdateTempPasswordRequest, object>(HttpMethod.Put, "/accounts/update-temp-password",
request, true, false);
}
#endregion

View file

@ -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);

View file

@ -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<FolderResponse> response)

View file

@ -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<string> GetUserIdAsync()
{
if (_userId == null)
@ -113,6 +121,15 @@ namespace Bit.Core.Services
return _kdfIterations;
}
public async Task<bool> GetForcePasswordReset()
{
if (_forcePasswordReset == null)
{
_forcePasswordReset = await _storageService.GetAsync<bool>(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;

View file

@ -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<IDeviceActionService>("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);
}
}
}

View file

@ -288,34 +288,8 @@ namespace Bit.iOS.Core.Controllers
private async Task LogOutAsync()
{
var syncService = ServiceContainer.Resolve<ISyncService>("syncService");
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
var settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
var folderService = ServiceContainer.Resolve<IFolderService>("folderService");
var collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
var passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var searchService = ServiceContainer.Resolve<ISearchService>("searchService");
await AppHelpers.LogOutAsync();
var authService = ServiceContainer.Resolve<IAuthService>("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();

View file

@ -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<IDeviceActionService>("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);
}
}
}