1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-07 19:13:19 +00: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:
Federico Maccaroni
2022-01-24 13:25:46 -03:00
committed by GitHub
parent 5a6aec51f3
commit 4e7ceaf5b5
12 changed files with 622 additions and 24 deletions

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
@@ -12,21 +13,13 @@ namespace Bit.App.Pages
{
public class DeleteAccountViewModel : BaseViewModel
{
readonly IApiService _apiService;
readonly IPasswordRepromptService _passwordRepromptService;
readonly IMessagingService _messagingService;
readonly ICryptoService _cryptoService;
readonly IPlatformUtilsService _platformUtilsService;
readonly IDeviceActionService _deviceActionService;
readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
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");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_verificationActionsFlowHelper = ServiceContainer.Resolve<IVerificationActionsFlowHelper>("verificationActionsFlowHelper");
PageTitle = AppResources.DeleteAccount;
}
@@ -42,15 +35,53 @@ namespace Bit.App.Pages
return;
}
var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync();
if (!valid)
{
return;
}
await _verificationActionsFlowHelper
.Configure(VerificationFlowAction.DeleteAccount,
null,
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);
var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(password, null);
var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(parameters.Secret, null);
await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest
{
MasterPasswordHash = masterPasswordHashKey

View 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>

View 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();
}
}
}
}

View 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();
}
}
}
}