mirror of
https://github.com/bitwarden/android.git
synced 2024-12-26 02:48:29 +03:00
Account Deletion on SSO with CME (#1721)
* WIP Added Verification Code page and a verification flow helper to coordinate things * Improved Verification Code page verification flow helper and fix some issues, also added flag ApiService to choose whether to logout on Unanuthorized * Improved Verification Code page UI/UX verification flow helper and fix some issues and made some cleanups * Fix spelling
This commit is contained in:
parent
5a6aec51f3
commit
4e7ceaf5b5
12 changed files with 622 additions and 24 deletions
|
@ -18,6 +18,8 @@ using Plugin.Fingerprint;
|
||||||
using Xamarin.Android.Net;
|
using Xamarin.Android.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Pages;
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using Android.Gms.Security;
|
using Android.Gms.Security;
|
||||||
#endif
|
#endif
|
||||||
|
@ -45,6 +47,20 @@ namespace Bit.Droid
|
||||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
|
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
|
||||||
Constants.AndroidAllClearCipherCacheKeys);
|
Constants.AndroidAllClearCipherCacheKeys);
|
||||||
|
|
||||||
|
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
|
||||||
|
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
|
||||||
|
ServiceContainer.Resolve<IApiService>("apiService"),
|
||||||
|
ServiceContainer.Resolve<IMessagingService>("messagingService"),
|
||||||
|
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
|
||||||
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
|
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"));
|
||||||
|
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
|
||||||
|
|
||||||
|
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
|
||||||
|
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
|
||||||
|
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"));
|
||||||
|
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||||
|
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -414,5 +415,6 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Behaviors\" />
|
<None Remove="Behaviors\" />
|
||||||
|
<None Remove="Xamarin.CommunityToolkit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
@ -12,21 +13,13 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class DeleteAccountViewModel : BaseViewModel
|
public class DeleteAccountViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
readonly IApiService _apiService;
|
|
||||||
readonly IPasswordRepromptService _passwordRepromptService;
|
|
||||||
readonly IMessagingService _messagingService;
|
|
||||||
readonly ICryptoService _cryptoService;
|
|
||||||
readonly IPlatformUtilsService _platformUtilsService;
|
readonly IPlatformUtilsService _platformUtilsService;
|
||||||
readonly IDeviceActionService _deviceActionService;
|
readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
|
||||||
|
|
||||||
public DeleteAccountViewModel()
|
public DeleteAccountViewModel()
|
||||||
{
|
{
|
||||||
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
|
||||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_verificationActionsFlowHelper = ServiceContainer.Resolve<IVerificationActionsFlowHelper>("verificationActionsFlowHelper");
|
||||||
|
|
||||||
PageTitle = AppResources.DeleteAccount;
|
PageTitle = AppResources.DeleteAccount;
|
||||||
}
|
}
|
||||||
|
@ -42,15 +35,53 @@ namespace Bit.App.Pages
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync();
|
await _verificationActionsFlowHelper
|
||||||
if (!valid)
|
.Configure(VerificationFlowAction.DeleteAccount,
|
||||||
{
|
null,
|
||||||
return;
|
AppResources.DeleteAccount,
|
||||||
}
|
true)
|
||||||
|
.ValidateAndExecuteAsync();
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
#if !FDROID
|
||||||
|
Crashes.TrackError(ex);
|
||||||
|
#endif
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDeleteAccountActionFlowExecutioner : IActionFlowExecutioner { }
|
||||||
|
|
||||||
|
public class DeleteAccountActionFlowExecutioner : IDeleteAccountActionFlowExecutioner
|
||||||
|
{
|
||||||
|
readonly IApiService _apiService;
|
||||||
|
readonly IMessagingService _messagingService;
|
||||||
|
readonly ICryptoService _cryptoService;
|
||||||
|
readonly IPlatformUtilsService _platformUtilsService;
|
||||||
|
readonly IDeviceActionService _deviceActionService;
|
||||||
|
|
||||||
|
public DeleteAccountActionFlowExecutioner(IApiService apiService,
|
||||||
|
IMessagingService messagingService,
|
||||||
|
ICryptoService cryptoService,
|
||||||
|
IPlatformUtilsService platformUtilsService,
|
||||||
|
IDeviceActionService deviceActionService)
|
||||||
|
{
|
||||||
|
_apiService = apiService;
|
||||||
|
_messagingService = messagingService;
|
||||||
|
_cryptoService = cryptoService;
|
||||||
|
_platformUtilsService = platformUtilsService;
|
||||||
|
_deviceActionService = deviceActionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(IActionFlowParmeters parameters)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.DeletingYourAccount);
|
await _deviceActionService.ShowLoadingAsync(AppResources.DeletingYourAccount);
|
||||||
|
|
||||||
var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(password, null);
|
var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(parameters.Secret, null);
|
||||||
await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest
|
await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest
|
||||||
{
|
{
|
||||||
MasterPasswordHash = masterPasswordHashKey
|
MasterPasswordHash = masterPasswordHashKey
|
||||||
|
|
99
src/App/Pages/Accounts/VerificationCodePage.xaml
Normal file
99
src/App/Pages/Accounts/VerificationCodePage.xaml
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<?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.Accounts.VerificationCodePage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
x:DataType="pages:VerificationCodeViewModel"
|
||||||
|
Title="{Binding PageTitle}">
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:VerificationCodeViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
<Style TargetType="Label" x:Key="lblDescription">
|
||||||
|
<Setter Property="FontSize" Value="{OnPlatform Android=Large, iOS=Small}" />
|
||||||
|
</Style>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
|
<ContentPage.Content>
|
||||||
|
<ScrollView>
|
||||||
|
<Grid
|
||||||
|
RowSpacing="10"
|
||||||
|
ColumnSpacing="10"
|
||||||
|
RowDefinitions="Auto, Auto, Auto"
|
||||||
|
ColumnDefinitions="*, *"
|
||||||
|
Padding="10">
|
||||||
|
<Label
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Padding="0,10"
|
||||||
|
Text="{Binding SendCodeStatus}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
LineBreakMode="WordWrap"
|
||||||
|
Margin="0,0,0,10" />
|
||||||
|
<Grid
|
||||||
|
StyleClass="box-row"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
RowDefinitions="Auto,Auto,Auto"
|
||||||
|
ColumnDefinitions="*,Auto"
|
||||||
|
Padding="0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n VerificationCode}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.ColumnSpan="2" />
|
||||||
|
<controls:MonoEntry
|
||||||
|
x:Name="_secret"
|
||||||
|
Text="{Binding Secret}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
IsSpellCheckEnabled="False"
|
||||||
|
IsTextPredictionEnabled="False"
|
||||||
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
ReturnType="Go"
|
||||||
|
ReturnCommand="{Binding MainActionCommand}" />
|
||||||
|
<controls:FaButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowPasswordIcon}"
|
||||||
|
Command="{Binding TogglePasswordCommand}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"/>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n ConfirmYourIdentity}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
LineBreakMode="WordWrap"
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="0,10,0,0" />
|
||||||
|
</Grid>
|
||||||
|
<Button
|
||||||
|
x:Name="_mainActionButton"
|
||||||
|
Grid.Row="2"
|
||||||
|
Padding="10,0"
|
||||||
|
Text="{Binding MainActionText}"
|
||||||
|
Command="{Binding MainActionCommand}"
|
||||||
|
HorizontalOptions="FillAndExpand"
|
||||||
|
VerticalOptions="Start" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="{u:I18n ResendCode}"
|
||||||
|
Padding="10,0"
|
||||||
|
Command="{Binding RequestOTPCommand}"
|
||||||
|
HorizontalOptions="Fill"
|
||||||
|
VerticalOptions="Start"/>
|
||||||
|
</Grid>
|
||||||
|
</ScrollView>
|
||||||
|
</ContentPage.Content>
|
||||||
|
</pages:BaseContentPage>
|
49
src/App/Pages/Accounts/VerificationCodePage.xaml.cs
Normal file
49
src/App/Pages/Accounts/VerificationCodePage.xaml.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages.Accounts
|
||||||
|
{
|
||||||
|
public partial class VerificationCodePage : BaseContentPage
|
||||||
|
{
|
||||||
|
VerificationCodeViewModel _vm;
|
||||||
|
|
||||||
|
public VerificationCodePage(string mainActionText, bool mainActionStyleDanger)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_vm = BindingContext as VerificationCodeViewModel;
|
||||||
|
_vm.Page = this;
|
||||||
|
_vm.MainActionText = mainActionText;
|
||||||
|
|
||||||
|
_mainActionButton.StyleClass = new[]
|
||||||
|
{
|
||||||
|
mainActionStyleDanger ? "btn-danger" : "btn-primary"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(propertyName);
|
||||||
|
|
||||||
|
if (propertyName == nameof(VerificationCodeViewModel.ShowPassword))
|
||||||
|
{
|
||||||
|
RequestFocus(_secret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async override void OnAppearing()
|
||||||
|
{
|
||||||
|
base.OnAppearing();
|
||||||
|
await _vm.InitAsync();
|
||||||
|
RequestFocus(_secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Close_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
176
src/App/Pages/Accounts/VerificationCodeViewModel.cs
Normal file
176
src/App/Pages/Accounts/VerificationCodeViewModel.cs
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
using System;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
#if !FDROID
|
||||||
|
using Microsoft.AppCenter.Crashes;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class VerificationCodeViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
|
private readonly IUserVerificationService _userVerificationService;
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
|
||||||
|
|
||||||
|
private bool _showPassword;
|
||||||
|
private string _secret, _mainActionText, _sendCodeStatus;
|
||||||
|
|
||||||
|
public VerificationCodeViewModel()
|
||||||
|
{
|
||||||
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
|
||||||
|
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||||
|
_verificationActionsFlowHelper = ServiceContainer.Resolve<IVerificationActionsFlowHelper>("verificationActionsFlowHelper");
|
||||||
|
|
||||||
|
PageTitle = AppResources.VerificationCode;
|
||||||
|
|
||||||
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
|
MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false);
|
||||||
|
RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowPassword
|
||||||
|
{
|
||||||
|
get => _showPassword;
|
||||||
|
set => SetProperty(ref _showPassword, value,
|
||||||
|
additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Secret
|
||||||
|
{
|
||||||
|
get => _secret;
|
||||||
|
set => SetProperty(ref _secret, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MainActionText
|
||||||
|
{
|
||||||
|
get => _mainActionText;
|
||||||
|
set => SetProperty(ref _mainActionText, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SendCodeStatus
|
||||||
|
{
|
||||||
|
get => _sendCodeStatus;
|
||||||
|
set => SetProperty(ref _sendCodeStatus, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand TogglePasswordCommand { get; }
|
||||||
|
|
||||||
|
public ICommand MainActionCommand { get; }
|
||||||
|
|
||||||
|
public ICommand RequestOTPCommand { get; }
|
||||||
|
|
||||||
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
|
|
||||||
|
public void TogglePassword() => ShowPassword = !ShowPassword;
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
await RequestOTPAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RequestOTPAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
||||||
|
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
|
||||||
|
|
||||||
|
SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.SendingCode);
|
||||||
|
|
||||||
|
await _apiService.PostAccountRequestOTP();
|
||||||
|
|
||||||
|
SendCodeStatus = AppResources.AVerificationCodeWasSentToYourEmail;
|
||||||
|
|
||||||
|
_platformUtilsService.ShowToast(null, null, AppResources.CodeSent);
|
||||||
|
}
|
||||||
|
catch (ApiException e)
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
|
||||||
|
|
||||||
|
if (e?.Error != null)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), AppResources.AnErrorHasOccurred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
#if !FDROID
|
||||||
|
Crashes.TrackError(ex);
|
||||||
|
#endif
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MainActionAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(Secret))
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.EnterTheVerificationCodeThatWasSentToYourEmail, AppResources.AnErrorHasOccurred);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
||||||
|
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.Verifying);
|
||||||
|
|
||||||
|
if (!await _userVerificationService.VerifyUser(Secret, Core.Enums.VerificationType.OTP))
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|
||||||
|
var parameters = _verificationActionsFlowHelper.GetParameters();
|
||||||
|
parameters.Secret = Secret;
|
||||||
|
await _verificationActionsFlowHelper.ExecuteAsync(parameters);
|
||||||
|
|
||||||
|
Secret = string.Empty;
|
||||||
|
}
|
||||||
|
catch (ApiException e)
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
if (e?.Error != null)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
#if !FDROID
|
||||||
|
Crashes.TrackError(ex);
|
||||||
|
#endif
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/App/Resources/AppResources.Designer.cs
generated
36
src/App/Resources/AppResources.Designer.cs
generated
|
@ -3766,5 +3766,41 @@ namespace Bit.App.Resources {
|
||||||
return ResourceManager.GetString("Sending", resourceCulture);
|
return ResourceManager.GetString("Sending", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string SendingCode {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SendingCode", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Verifying {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Verifying", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ResendCode {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ResendCode", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AVerificationCodeWasSentToYourEmail {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AVerificationCodeWasSentToYourEmail", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string EnterTheVerificationCodeThatWasSentToYourEmail {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EnterTheVerificationCodeThatWasSentToYourEmail", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2117,4 +2117,22 @@
|
||||||
<data name="Sending" xml:space="preserve">
|
<data name="Sending" xml:space="preserve">
|
||||||
<value>Sending</value>
|
<value>Sending</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SendingCode" xml:space="preserve">
|
||||||
|
<value>Sending code</value>
|
||||||
|
</data>
|
||||||
|
<data name="Verifying" xml:space="preserve">
|
||||||
|
<value>Verifying</value>
|
||||||
|
</data>
|
||||||
|
<data name="ResendCode" xml:space="preserve">
|
||||||
|
<value>Resend Code</value>
|
||||||
|
</data>
|
||||||
|
<data name="AVerificationCodeWasSentToYourEmail" xml:space="preserve">
|
||||||
|
<value>A verification code was sent to your email</value>
|
||||||
|
</data>
|
||||||
|
<data name="AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain" xml:space="preserve">
|
||||||
|
<value>An error occurred while sending a verification code to your email. Please try again</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
|
||||||
|
<value>Enter the verification code that was sent to your email</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
147
src/App/Utilities/VerificationActionsFlowHelper.cs
Normal file
147
src/App/Utilities/VerificationActionsFlowHelper.cs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Pages.Accounts;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Utilities
|
||||||
|
{
|
||||||
|
public interface IVerificationActionsFlowHelper
|
||||||
|
{
|
||||||
|
IVerificationActionsFlowHelper Configure(VerificationFlowAction action,
|
||||||
|
IActionFlowParmeters parameters = null,
|
||||||
|
string verificatioCodeMainActionText = null,
|
||||||
|
bool isVerificationCodeMainActionStyleDanger = false);
|
||||||
|
IActionFlowParmeters GetParameters();
|
||||||
|
Task ValidateAndExecuteAsync();
|
||||||
|
Task ExecuteAsync(IActionFlowParmeters parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IActionFlowParmeters
|
||||||
|
{
|
||||||
|
string Secret { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DefaultActionFlowParameters : IActionFlowParmeters
|
||||||
|
{
|
||||||
|
public string Secret { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IActionFlowExecutioner
|
||||||
|
{
|
||||||
|
Task Execute(IActionFlowParmeters parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum VerificationFlowAction
|
||||||
|
{
|
||||||
|
ExportVault,
|
||||||
|
DeleteAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies and execute actions on the corresponding order.
|
||||||
|
///
|
||||||
|
/// Use: From the caller
|
||||||
|
/// - Configure it on <see cref="Configure(VerificationFlowAction, IActionFlowParmeters)"/>
|
||||||
|
/// - Call <see cref="ValidateAndExecuteAsync"/>
|
||||||
|
///
|
||||||
|
/// For every <see cref="VerificationFlowAction"/> we need an implementation of <see cref="IActionFlowExecutioner"/>
|
||||||
|
/// and it to be configured in the inner dictionary.
|
||||||
|
/// Also, inherit from <see cref="DefaultActionFlowParameters"/> if more custom parameters are needed for the executioner.
|
||||||
|
/// </summary>
|
||||||
|
public class VerificationActionsFlowHelper : IVerificationActionsFlowHelper
|
||||||
|
{
|
||||||
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
|
|
||||||
|
private VerificationFlowAction? _action;
|
||||||
|
private IActionFlowParmeters _parameters;
|
||||||
|
private string _verificationCodeMainActionText;
|
||||||
|
private bool _isVerificationCodeMainActionStyleDanger;
|
||||||
|
|
||||||
|
private readonly Dictionary<VerificationFlowAction, IActionFlowExecutioner> _actionExecutionerDictionary = new Dictionary<VerificationFlowAction, IActionFlowExecutioner>();
|
||||||
|
|
||||||
|
public VerificationActionsFlowHelper(IKeyConnectorService keyConnectorService,
|
||||||
|
IPasswordRepromptService passwordRepromptService)
|
||||||
|
{
|
||||||
|
_keyConnectorService = keyConnectorService;
|
||||||
|
_passwordRepromptService = passwordRepromptService;
|
||||||
|
|
||||||
|
_actionExecutionerDictionary.Add(VerificationFlowAction.DeleteAccount, ServiceContainer.Resolve<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IVerificationActionsFlowHelper Configure(VerificationFlowAction action,
|
||||||
|
IActionFlowParmeters parameters = null,
|
||||||
|
string verificationCodeMainActionText = null,
|
||||||
|
bool isVerificationCodeMainActionStyleDanger = false)
|
||||||
|
{
|
||||||
|
_action = action;
|
||||||
|
_parameters = parameters;
|
||||||
|
_verificationCodeMainActionText = verificationCodeMainActionText;
|
||||||
|
_isVerificationCodeMainActionStyleDanger = isVerificationCodeMainActionStyleDanger;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionFlowParmeters GetParameters()
|
||||||
|
{
|
||||||
|
if (_parameters is null)
|
||||||
|
{
|
||||||
|
_parameters = new DefaultActionFlowParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ValidateAndExecuteAsync()
|
||||||
|
{
|
||||||
|
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
|
||||||
|
? VerificationType.OTP
|
||||||
|
: VerificationType.MasterPassword;
|
||||||
|
|
||||||
|
switch (verificationType)
|
||||||
|
{
|
||||||
|
case VerificationType.MasterPassword:
|
||||||
|
var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync();
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetParameters().Secret = password;
|
||||||
|
await ExecuteAsync(_parameters);
|
||||||
|
break;
|
||||||
|
case VerificationType.OTP:
|
||||||
|
await Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(
|
||||||
|
new VerificationCodePage(_verificationCodeMainActionText, _isVerificationCodeMainActionStyleDanger)));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException($"There is no implementation for {verificationType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the action with the given parameters after we have gotten the verification secret
|
||||||
|
/// </summary>
|
||||||
|
public async Task ExecuteAsync(IActionFlowParmeters parameters)
|
||||||
|
{
|
||||||
|
if (!_action.HasValue)
|
||||||
|
{
|
||||||
|
// this should never happen
|
||||||
|
throw new InvalidOperationException("A problem occurred while getting the action value after validation");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_actionExecutionerDictionary.TryGetValue(_action.Value, out var executioner))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"There is no executioner for {_action}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await executioner.Execute(GetParameters());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ namespace Bit.Core.Abstractions
|
||||||
Task RefreshIdentityTokenAsync();
|
Task RefreshIdentityTokenAsync();
|
||||||
Task<object> PreValidateSso(string identifier);
|
Task<object> PreValidateSso(string identifier);
|
||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse);
|
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrls urls);
|
||||||
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||||
Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
|
Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
|
||||||
|
|
|
@ -180,13 +180,13 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
public Task PostAccountRequestOTP()
|
public Task PostAccountRequestOTP()
|
||||||
{
|
{
|
||||||
return SendAsync<object, object>(HttpMethod.Post, "/accounts/request-otp", null, true, false);
|
return SendAsync<object, object>(HttpMethod.Post, "/accounts/request-otp", null, true, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PostAccountVerifyOTPAsync(VerifyOTPRequest request)
|
public Task PostAccountVerifyOTPAsync(VerifyOTPRequest request)
|
||||||
{
|
{
|
||||||
return SendAsync<VerifyOTPRequest, object>(HttpMethod.Post, "/accounts/verify-otp", request,
|
return SendAsync<VerifyOTPRequest, object>(HttpMethod.Post, "/accounts/verify-otp", request,
|
||||||
true, false);
|
true, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request)
|
public Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request)
|
||||||
|
@ -579,7 +579,7 @@ namespace Bit.Core.Services
|
||||||
public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) =>
|
public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) =>
|
||||||
SendAsync<object, TResponse>(method, path, null, authed, true);
|
SendAsync<object, TResponse>(method, path, null, authed, true);
|
||||||
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
|
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
|
||||||
bool authed, bool hasResponse)
|
bool authed, bool hasResponse, bool logoutOnUnauthorized = true)
|
||||||
{
|
{
|
||||||
using (var requestMessage = new HttpRequestMessage())
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
{
|
{
|
||||||
|
@ -631,7 +631,7 @@ namespace Bit.Core.Services
|
||||||
}
|
}
|
||||||
else if (!response.IsSuccessStatusCode)
|
else if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var error = await HandleErrorAsync(response, false, authed);
|
var error = await HandleErrorAsync(response, false, authed, logoutOnUnauthorized);
|
||||||
throw new ApiException(error);
|
throw new ApiException(error);
|
||||||
}
|
}
|
||||||
return (TResponse)(object)null;
|
return (TResponse)(object)null;
|
||||||
|
@ -693,10 +693,18 @@ namespace Bit.Core.Services
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ErrorResponse> HandleErrorAsync(HttpResponseMessage response, bool tokenError, bool authed)
|
private async Task<ErrorResponse> HandleErrorAsync(HttpResponseMessage response, bool tokenError,
|
||||||
|
bool authed, bool logoutOnUnauthorized = true)
|
||||||
{
|
{
|
||||||
if (authed && ((tokenError && response.StatusCode == HttpStatusCode.BadRequest) ||
|
if (authed
|
||||||
response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden))
|
&&
|
||||||
|
(
|
||||||
|
(tokenError && response.StatusCode == HttpStatusCode.BadRequest)
|
||||||
|
||
|
||||||
|
(logoutOnUnauthorized && response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
||
|
||||||
|
response.StatusCode == HttpStatusCode.Forbidden
|
||||||
|
))
|
||||||
{
|
{
|
||||||
await _logoutCallbackAsync(true);
|
await _logoutCallbackAsync(true);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Pages;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Services;
|
using Bit.App.Services;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
@ -148,6 +149,21 @@ namespace Bit.iOS.Core.Utilities
|
||||||
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
|
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
|
||||||
Bit.Core.Constants.DisableFaviconKey, disableFavicon);
|
Bit.Core.Constants.DisableFaviconKey, disableFavicon);
|
||||||
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
||||||
|
|
||||||
|
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
|
||||||
|
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
|
||||||
|
ServiceContainer.Resolve<IApiService>("apiService"),
|
||||||
|
ServiceContainer.Resolve<IMessagingService>("messagingService"),
|
||||||
|
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
|
||||||
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
|
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"));
|
||||||
|
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
|
||||||
|
|
||||||
|
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
|
||||||
|
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
|
||||||
|
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"));
|
||||||
|
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
||||||
|
|
||||||
if (postBootstrapFunc != null)
|
if (postBootstrapFunc != null)
|
||||||
{
|
{
|
||||||
await postBootstrapFunc.Invoke();
|
await postBootstrapFunc.Invoke();
|
||||||
|
|
Loading…
Reference in a new issue