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:
committed by
GitHub
parent
5a6aec51f3
commit
4e7ceaf5b5
@@ -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
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user