mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-25 12:05:59 +01: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
@ -18,6 +18,8 @@ using Plugin.Fingerprint;
|
||||
using Xamarin.Android.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Pages;
|
||||
#if !FDROID
|
||||
using Android.Gms.Security;
|
||||
#endif
|
||||
@ -45,6 +47,20 @@ namespace Bit.Droid
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
|
||||
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 (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
|
@ -20,6 +20,7 @@
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
|
||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -414,5 +415,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Behaviors\" />
|
||||
<None Remove="Xamarin.CommunityToolkit" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
||||
|
||||
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">
|
||||
<value>Sending</value>
|
||||
</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>
|
||||
|
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<object> PreValidateSso(string identifier);
|
||||
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);
|
||||
[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);
|
||||
|
@ -180,13 +180,13 @@ namespace Bit.Core.Services
|
||||
|
||||
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)
|
||||
{
|
||||
return SendAsync<VerifyOTPRequest, object>(HttpMethod.Post, "/accounts/verify-otp", request,
|
||||
true, false);
|
||||
true, false, false);
|
||||
}
|
||||
|
||||
public Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request)
|
||||
@ -579,7 +579,7 @@ namespace Bit.Core.Services
|
||||
public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) =>
|
||||
SendAsync<object, TResponse>(method, path, null, authed, true);
|
||||
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())
|
||||
{
|
||||
@ -631,7 +631,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
else if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = await HandleErrorAsync(response, false, authed);
|
||||
var error = await HandleErrorAsync(response, false, authed, logoutOnUnauthorized);
|
||||
throw new ApiException(error);
|
||||
}
|
||||
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) ||
|
||||
response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden))
|
||||
if (authed
|
||||
&&
|
||||
(
|
||||
(tokenError && response.StatusCode == HttpStatusCode.BadRequest)
|
||||
||
|
||||
(logoutOnUnauthorized && response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
||
|
||||
response.StatusCode == HttpStatusCode.Forbidden
|
||||
))
|
||||
{
|
||||
await _logoutCallbackAsync(true);
|
||||
return null;
|
||||
|
@ -3,6 +3,7 @@ using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Utilities;
|
||||
@ -148,6 +149,21 @@ namespace Bit.iOS.Core.Utilities
|
||||
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
|
||||
Bit.Core.Constants.DisableFaviconKey, disableFavicon);
|
||||
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)
|
||||
{
|
||||
await postBootstrapFunc.Invoke();
|
||||
|
Loading…
Reference in New Issue
Block a user