mirror of
https://github.com/bitwarden/mobile
synced 2025-12-26 21:23:46 +00:00
Added account deletion feature on settings (#1621)
* Added account deletion feature on settings * Disabled using Microsoft.AppCenter.Crashes for FDroid * Moved drawable on Android.csproj to be with the others Co-authored-by: Federico Maccaroni <fmaccaroni@bitwarden.com>
This commit is contained in:
committed by
GitHub
parent
833103b2a0
commit
9fdf2ada6f
@@ -7,6 +7,8 @@ namespace Bit.App.Abstractions
|
||||
string[] ProtectedFields { get; }
|
||||
|
||||
Task<bool> ShowPasswordPromptAsync();
|
||||
|
||||
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
||||
|
||||
Task<bool> Enabled();
|
||||
}
|
||||
|
||||
56
src/App/Pages/Accounts/DeleteAccountPage.xaml
Normal file
56
src/App/Pages/Accounts/DeleteAccountPage.xaml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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.DeleteAccountPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="pages:DeleteAccountViewModel"
|
||||
Title="{Binding PageTitle}">
|
||||
<ContentPage.BindingContext>
|
||||
<pages:DeleteAccountViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ContentPage.Content>
|
||||
<StackLayout Padding="20, 30" Spacing="0">
|
||||
<Image
|
||||
Source="ic_warning"
|
||||
WidthRequest="28"
|
||||
HeightRequest="25"
|
||||
HorizontalOptions="Start" />
|
||||
<Label
|
||||
Text="{u:I18n DeletingYourAccountIsPermanent}"
|
||||
HorizontalOptions="Start"
|
||||
StyleClass="text-body"
|
||||
Margin="0,15,0,0"/>
|
||||
<Label
|
||||
Text="{u:I18n DeleteAccountExplanation}"
|
||||
HorizontalOptions="Start"
|
||||
Margin="0,6,50,0"
|
||||
Opacity="0.6" />
|
||||
<Button
|
||||
Text="{u:I18n Cancel}"
|
||||
StyleClass="btn-primary"
|
||||
HorizontalOptions="Start"
|
||||
Margin="0,26,0,0"
|
||||
Padding="16,6"
|
||||
CornerRadius="2"
|
||||
TextTransform="Uppercase"
|
||||
Clicked="Close_Clicked" />
|
||||
<Button
|
||||
Text="{u:I18n DeleteAccount}"
|
||||
StyleClass="btn-secondary"
|
||||
TextColor="#99000000"
|
||||
HorizontalOptions="Start"
|
||||
Margin="0,12,0,0"
|
||||
Padding="16,6"
|
||||
CornerRadius="2"
|
||||
TextTransform="Uppercase"
|
||||
Clicked="DeleteAccount_Clicked"/>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</pages:BaseContentPage>
|
||||
33
src/App/Pages/Accounts/DeleteAccountPage.xaml.cs
Normal file
33
src/App/Pages/Accounts/DeleteAccountPage.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages.Accounts
|
||||
{
|
||||
public partial class DeleteAccountPage : BaseContentPage
|
||||
{
|
||||
DeleteAccountViewModel _vm;
|
||||
|
||||
public DeleteAccountPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = BindingContext as DeleteAccountViewModel;
|
||||
_vm.Page = this;
|
||||
}
|
||||
|
||||
private async void Close_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void DeleteAccount_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await _vm.DeleteAccountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/App/Pages/Accounts/DeleteAccountViewModel.cs
Normal file
84
src/App/Pages/Accounts/DeleteAccountViewModel.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Utilities;
|
||||
#if !FDROID
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
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");
|
||||
|
||||
PageTitle = AppResources.DeleteAccount;
|
||||
}
|
||||
|
||||
public async Task DeleteAccountAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync();
|
||||
if (!valid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.DeletingYourAccount);
|
||||
|
||||
var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(password, null);
|
||||
await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest
|
||||
{
|
||||
MasterPasswordHash = masterPasswordHashKey
|
||||
});
|
||||
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
_messagingService.Send("logout");
|
||||
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.YourAccountHasBeenPermanentlyDeleted);
|
||||
}
|
||||
catch (ApiException apiEx)
|
||||
{
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if (apiEx?.Error != null)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(apiEx.Error.GetSingleMessage(), AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
#if !FDROID
|
||||
Crashes.TrackError(ex);
|
||||
#endif
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.ComponentModel;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Pages.Accounts;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
@@ -134,6 +135,10 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await _vm.LogOutAsync();
|
||||
}
|
||||
else if (item.Name == AppResources.DeleteAccount)
|
||||
{
|
||||
await Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()));
|
||||
}
|
||||
else if (item.Name == AppResources.LockNow)
|
||||
{
|
||||
await _vm.LockAsync();
|
||||
|
||||
@@ -490,7 +490,8 @@ namespace Bit.App.Pages
|
||||
new SettingsPageListItem { Name = AppResources.Options },
|
||||
new SettingsPageListItem { Name = AppResources.About },
|
||||
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
|
||||
new SettingsPageListItem { Name = AppResources.RateTheApp }
|
||||
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
||||
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
||||
};
|
||||
GroupedItems.ResetWithRange(new List<SettingsPageListGroup>
|
||||
{
|
||||
|
||||
30
src/App/Resources/AppResources.Designer.cs
generated
30
src/App/Resources/AppResources.Designer.cs
generated
@@ -3719,6 +3719,36 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string DeleteAccount {
|
||||
get {
|
||||
return ResourceManager.GetString("DeleteAccount", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DeletingYourAccountIsPermanent {
|
||||
get {
|
||||
return ResourceManager.GetString("DeletingYourAccountIsPermanent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DeleteAccountExplanation {
|
||||
get {
|
||||
return ResourceManager.GetString("DeleteAccountExplanation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DeletingYourAccount {
|
||||
get {
|
||||
return ResourceManager.GetString("DeletingYourAccount", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string YourAccountHasBeenPermanentlyDeleted {
|
||||
get {
|
||||
return ResourceManager.GetString("YourAccountHasBeenPermanentlyDeleted", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string InvalidVerificationCode {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidVerificationCode", resourceCulture);
|
||||
|
||||
@@ -2093,6 +2093,21 @@
|
||||
<data name="DisablePersonalVaultExportPolicyInEffect">
|
||||
<value>One or more organization policies prevents your from exporting your personal vault.</value>
|
||||
</data>
|
||||
<data name="DeleteAccount" xml:space="preserve">
|
||||
<value>Delete Account</value>
|
||||
</data>
|
||||
<data name="DeletingYourAccountIsPermanent" xml:space="preserve">
|
||||
<value>Deleting your account is permanent</value>
|
||||
</data>
|
||||
<data name="DeleteAccountExplanation" xml:space="preserve">
|
||||
<value>Your account and all associated data will be erased and unrecoverable. Are you sure you want to continue?</value>
|
||||
</data>
|
||||
<data name="DeletingYourAccount" xml:space="preserve">
|
||||
<value>Deleting your account</value>
|
||||
</data>
|
||||
<data name="YourAccountHasBeenPermanentlyDeleted" xml:space="preserve">
|
||||
<value>Your account has been permanently deleted</value>
|
||||
</data>
|
||||
<data name="InvalidVerificationCode" xml:space="preserve">
|
||||
<value>Invalid Verification Code.</value>
|
||||
</data>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using System;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
@@ -22,23 +22,23 @@ namespace Bit.App.Services
|
||||
|
||||
public async Task<bool> ShowPasswordPromptAsync()
|
||||
{
|
||||
if (!await Enabled())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return await _platformUtilsService.ShowPasswordDialogAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, ValidatePasswordAsync);
|
||||
}
|
||||
|
||||
Func<string, Task<bool>> validator = async (string password) =>
|
||||
{
|
||||
// Assume user has canceled.
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return false;
|
||||
};
|
||||
public async Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync()
|
||||
{
|
||||
return await _platformUtilsService.ShowPasswordDialogAndGetItAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, ValidatePasswordAsync);
|
||||
}
|
||||
|
||||
return await _cryptoService.CompareAndUpdateKeyHashAsync(password, null);
|
||||
private async Task<bool> ValidatePasswordAsync(string password)
|
||||
{
|
||||
// Assume user has canceled.
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
return await _platformUtilsService.ShowPasswordDialogAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, validator);
|
||||
return await _cryptoService.CompareAndUpdateKeyHashAsync(password, null);
|
||||
}
|
||||
|
||||
public async Task<bool> Enabled()
|
||||
|
||||
@@ -167,13 +167,18 @@ namespace Bit.App.Services
|
||||
}
|
||||
|
||||
public async Task<bool> ShowPasswordDialogAsync(string title, string body, Func<string, Task<bool>> validator)
|
||||
{
|
||||
return (await ShowPasswordDialogAndGetItAsync(title, body, validator)).valid;
|
||||
}
|
||||
|
||||
public async Task<(string password, bool valid)> ShowPasswordDialogAndGetItAsync(string title, string body, Func<string, Task<bool>> validator)
|
||||
{
|
||||
var password = await _deviceActionService.DisplayPromptAync(AppResources.PasswordConfirmation,
|
||||
AppResources.PasswordConfirmationDesc, null, AppResources.Submit, AppResources.Cancel, password: true);
|
||||
|
||||
if (password == null)
|
||||
{
|
||||
return false;
|
||||
return (password, false);
|
||||
}
|
||||
|
||||
var valid = await validator(password);
|
||||
@@ -183,7 +188,7 @@ namespace Bit.App.Services
|
||||
await ShowDialogAsync(AppResources.InvalidMasterPassword, null, AppResources.Ok);
|
||||
}
|
||||
|
||||
return valid;
|
||||
return (password, valid);
|
||||
}
|
||||
|
||||
public bool IsDev()
|
||||
|
||||
@@ -151,6 +151,39 @@
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button"
|
||||
Class="btn-secondary">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderColor"
|
||||
Value="{DynamicResource ButtonBorderColor}" />
|
||||
<Setter Property="BorderWidth"
|
||||
Value="1" />
|
||||
<Setter Property="TextColor"
|
||||
Value="{DynamicResource ButtonTextColor}" />
|
||||
<Setter Property="FontSize"
|
||||
Value="Medium" />
|
||||
<Setter Property="CornerRadius"
|
||||
Value="5" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor"
|
||||
Value="{DynamicResource ButtonTextColorDisabled}" />
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{DynamicResource ButtonBackgroundColorDisabled}" />
|
||||
<Setter Property="BorderColor"
|
||||
Value="{DynamicResource ButtonBackgroundColorDisabled}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="Button"
|
||||
ApplyToDerivedTypes="True"
|
||||
Class="btn-icon-platform">
|
||||
|
||||
@@ -67,6 +67,13 @@
|
||||
<Setter Property="TextType"
|
||||
Value="Html" />
|
||||
</Style>
|
||||
<Style TargetType="Label"
|
||||
Class="text-body">
|
||||
<Setter Property="FontSize"
|
||||
Value="Body" />
|
||||
<Setter Property="TextColor"
|
||||
Value="{DynamicResource TextColor}" />
|
||||
</Style>
|
||||
|
||||
<!-- Pages -->
|
||||
<Style TargetType="TabbedPage"
|
||||
|
||||
@@ -172,6 +172,44 @@
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="Button"
|
||||
Class="btn-secondary">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderColor"
|
||||
Value="{DynamicResource ButtonBorderColor}" />
|
||||
<Setter Property="BorderWidth"
|
||||
Value="1" />
|
||||
<Setter Property="TextColor"
|
||||
Value="{DynamicResource ButtonTextColor}" />
|
||||
<Setter Property="FontSize"
|
||||
Value="Medium" />
|
||||
<Setter Property="CornerRadius"
|
||||
Value="5" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{DynamicResource ButtonBackgroundColorPressed}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor"
|
||||
Value="{DynamicResource ButtonTextColorDisabled}" />
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{DynamicResource ButtonBackgroundColorDisabled}" />
|
||||
<Setter Property="BorderColor"
|
||||
Value="{DynamicResource ButtonBackgroundColorDisabled}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="Button"
|
||||
ApplyToDerivedTypes="True"
|
||||
Class="btn-icon-platform">
|
||||
|
||||
Reference in New Issue
Block a user