2019-05-24 04:19:45 +03:00
|
|
|
|
using Bit.App.Abstractions;
|
|
|
|
|
using Bit.App.Resources;
|
2019-07-02 15:05:34 +03:00
|
|
|
|
using Bit.Core;
|
2019-05-24 04:19:45 +03:00
|
|
|
|
using Bit.Core.Abstractions;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
using Bit.Core.Enums;
|
2019-05-24 04:19:45 +03:00
|
|
|
|
using Bit.Core.Exceptions;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
using Bit.Core.Models.Request;
|
2019-05-24 04:19:45 +03:00
|
|
|
|
using Bit.Core.Utilities;
|
2020-04-13 19:32:23 +03:00
|
|
|
|
using System;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
using System.Linq;
|
2019-05-27 18:57:10 +03:00
|
|
|
|
using System.Net;
|
2019-05-24 04:19:45 +03:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Xamarin.Forms;
|
|
|
|
|
|
|
|
|
|
namespace Bit.App.Pages
|
|
|
|
|
{
|
|
|
|
|
public class TwoFactorPageViewModel : BaseViewModel
|
|
|
|
|
{
|
|
|
|
|
private readonly IDeviceActionService _deviceActionService;
|
|
|
|
|
private readonly IAuthService _authService;
|
|
|
|
|
private readonly ISyncService _syncService;
|
|
|
|
|
private readonly IStorageService _storageService;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
private readonly IApiService _apiService;
|
|
|
|
|
private readonly IPlatformUtilsService _platformUtilsService;
|
2019-05-27 18:57:10 +03:00
|
|
|
|
private readonly IEnvironmentService _environmentService;
|
2019-05-28 16:54:08 +03:00
|
|
|
|
private readonly IMessagingService _messagingService;
|
|
|
|
|
private readonly IBroadcasterService _broadcasterService;
|
2019-07-02 15:05:34 +03:00
|
|
|
|
private readonly IStateService _stateService;
|
2019-05-24 04:19:45 +03:00
|
|
|
|
|
2019-05-27 17:28:38 +03:00
|
|
|
|
private bool _u2fSupported = false;
|
|
|
|
|
private TwoFactorProviderType? _selectedProviderType;
|
2019-05-28 17:12:51 +03:00
|
|
|
|
private string _totpInstruction;
|
2019-05-27 18:57:10 +03:00
|
|
|
|
private string _webVaultUrl = "https://vault.bitwarden.com";
|
2019-05-24 04:19:45 +03:00
|
|
|
|
|
|
|
|
|
public TwoFactorPageViewModel()
|
|
|
|
|
{
|
|
|
|
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
|
|
|
|
_authService = ServiceContainer.Resolve<IAuthService>("authService");
|
|
|
|
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
|
|
|
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
2019-05-27 17:28:38 +03:00
|
|
|
|
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
|
|
|
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
2019-05-27 18:57:10 +03:00
|
|
|
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
2019-05-28 16:54:08 +03:00
|
|
|
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
|
|
|
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
2019-07-02 15:05:34 +03:00
|
|
|
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
2019-05-28 16:04:20 +03:00
|
|
|
|
|
|
|
|
|
PageTitle = AppResources.TwoStepLogin;
|
2019-05-31 19:13:14 +03:00
|
|
|
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
2019-05-24 04:19:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 17:12:51 +03:00
|
|
|
|
public string TotpInstruction
|
2019-05-24 04:19:45 +03:00
|
|
|
|
{
|
2019-05-28 17:12:51 +03:00
|
|
|
|
get => _totpInstruction;
|
|
|
|
|
set => SetProperty(ref _totpInstruction, value);
|
2019-05-24 04:19:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-27 17:28:38 +03:00
|
|
|
|
public bool Remember { get; set; }
|
|
|
|
|
|
|
|
|
|
public string Token { get; set; }
|
|
|
|
|
|
2019-05-28 16:04:20 +03:00
|
|
|
|
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
|
|
|
|
|
SelectedProviderType == TwoFactorProviderType.OrganizationDuo;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
|
|
|
|
|
public bool YubikeyMethod => SelectedProviderType == TwoFactorProviderType.YubiKey;
|
|
|
|
|
|
|
|
|
|
public bool AuthenticatorMethod => SelectedProviderType == TwoFactorProviderType.Authenticator;
|
|
|
|
|
|
|
|
|
|
public bool EmailMethod => SelectedProviderType == TwoFactorProviderType.Email;
|
|
|
|
|
|
2019-05-27 18:57:10 +03:00
|
|
|
|
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
|
|
|
|
|
|
2019-07-07 04:59:13 +03:00
|
|
|
|
public bool ShowTryAgain => YubikeyMethod && Device.RuntimePlatform == Device.iOS;
|
|
|
|
|
|
2019-05-27 18:57:10 +03:00
|
|
|
|
public string YubikeyInstruction => Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
|
|
|
|
|
AppResources.YubiKeyInstruction;
|
|
|
|
|
|
2019-05-27 17:28:38 +03:00
|
|
|
|
public TwoFactorProviderType? SelectedProviderType
|
2019-05-24 04:19:45 +03:00
|
|
|
|
{
|
2019-05-27 17:28:38 +03:00
|
|
|
|
get => _selectedProviderType;
|
|
|
|
|
set => SetProperty(ref _selectedProviderType, value, additionalPropertyNames: new string[]
|
|
|
|
|
{
|
|
|
|
|
nameof(EmailMethod),
|
|
|
|
|
nameof(DuoMethod),
|
|
|
|
|
nameof(YubikeyMethod),
|
2019-05-27 18:57:10 +03:00
|
|
|
|
nameof(AuthenticatorMethod),
|
|
|
|
|
nameof(TotpMethod),
|
2019-07-07 04:59:13 +03:00
|
|
|
|
nameof(ShowTryAgain),
|
2019-05-27 17:28:38 +03:00
|
|
|
|
});
|
|
|
|
|
}
|
2019-05-31 19:13:14 +03:00
|
|
|
|
public Command SubmitCommand { get; }
|
2020-04-13 19:32:23 +03:00
|
|
|
|
public Action TwoFactorAction { get; set; }
|
2019-05-27 17:28:38 +03:00
|
|
|
|
|
|
|
|
|
public void Init()
|
|
|
|
|
{
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (string.IsNullOrWhiteSpace(_authService.Email) ||
|
2019-05-27 17:28:38 +03:00
|
|
|
|
string.IsNullOrWhiteSpace(_authService.MasterPasswordHash) ||
|
|
|
|
|
_authService.TwoFactorProvidersData == null)
|
|
|
|
|
{
|
|
|
|
|
// TODO: dismiss modal?
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
|
2019-05-27 18:57:10 +03:00
|
|
|
|
{
|
|
|
|
|
_webVaultUrl = _environmentService.BaseUrl;
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
else if (!string.IsNullOrWhiteSpace(_environmentService.WebVaultUrl))
|
2019-05-27 18:57:10 +03:00
|
|
|
|
{
|
|
|
|
|
_webVaultUrl = _environmentService.WebVaultUrl;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-27 17:28:38 +03:00
|
|
|
|
// TODO: init U2F
|
|
|
|
|
_u2fSupported = false;
|
2019-05-24 04:19:45 +03:00
|
|
|
|
|
2019-05-27 18:57:10 +03:00
|
|
|
|
SelectedProviderType = _authService.GetDefaultTwoFactorProvider(_u2fSupported);
|
2019-05-27 17:28:38 +03:00
|
|
|
|
Load();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Load()
|
|
|
|
|
{
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (SelectedProviderType == null)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
PageTitle = AppResources.LoginUnavailable;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-05-28 16:12:05 +03:00
|
|
|
|
var page = Page as TwoFactorPage;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
PageTitle = _authService.TwoFactorProviders[SelectedProviderType.Value].Name;
|
|
|
|
|
var providerData = _authService.TwoFactorProvidersData[SelectedProviderType.Value];
|
2020-03-28 16:16:28 +03:00
|
|
|
|
switch (SelectedProviderType.Value)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
case TwoFactorProviderType.U2f:
|
|
|
|
|
// TODO
|
|
|
|
|
break;
|
2019-05-28 16:54:08 +03:00
|
|
|
|
case TwoFactorProviderType.YubiKey:
|
|
|
|
|
_messagingService.Send("listenYubiKeyOTP", true);
|
|
|
|
|
break;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
case TwoFactorProviderType.Duo:
|
|
|
|
|
case TwoFactorProviderType.OrganizationDuo:
|
2019-05-27 18:57:10 +03:00
|
|
|
|
var host = WebUtility.UrlEncode(providerData["Host"] as string);
|
|
|
|
|
var req = WebUtility.UrlEncode(providerData["Signature"] as string);
|
|
|
|
|
page.DuoWebView.Uri = $"{_webVaultUrl}/duo-connector.html?host={host}&request={req}";
|
2020-02-24 16:57:36 +03:00
|
|
|
|
page.DuoWebView.RegisterAction(sig =>
|
2019-05-27 18:57:10 +03:00
|
|
|
|
{
|
|
|
|
|
Token = sig;
|
2020-02-24 16:58:15 +03:00
|
|
|
|
Device.BeginInvokeOnMainThread(async () => await SubmitAsync());
|
2019-05-27 18:57:10 +03:00
|
|
|
|
});
|
2019-05-27 17:28:38 +03:00
|
|
|
|
break;
|
|
|
|
|
case TwoFactorProviderType.Email:
|
2019-05-28 17:12:51 +03:00
|
|
|
|
TotpInstruction = string.Format(AppResources.EnterVerificationCodeEmail,
|
|
|
|
|
providerData["Email"] as string);
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (_authService.TwoFactorProvidersData.Count > 1)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
var emailTask = Task.Run(() => SendEmailAsync(false, false));
|
|
|
|
|
}
|
|
|
|
|
break;
|
2019-05-28 17:12:51 +03:00
|
|
|
|
case TwoFactorProviderType.Authenticator:
|
|
|
|
|
TotpInstruction = AppResources.EnterVerificationCodeApp;
|
|
|
|
|
break;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-28 16:54:08 +03:00
|
|
|
|
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (!YubikeyMethod)
|
2019-05-28 16:54:08 +03:00
|
|
|
|
{
|
|
|
|
|
_messagingService.Send("listenYubiKeyOTP", false);
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (SelectedProviderType == null || DuoMethod)
|
2019-05-28 16:54:08 +03:00
|
|
|
|
{
|
|
|
|
|
page.RemoveContinueButton();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
page.AddContinueButton();
|
|
|
|
|
}
|
2019-05-24 04:19:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task SubmitAsync()
|
|
|
|
|
{
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (SelectedProviderType == null)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
2019-06-04 05:43:52 +03:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
|
|
|
|
AppResources.InternetConnectionRequiredTitle);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (string.IsNullOrWhiteSpace(Token))
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(
|
|
|
|
|
string.Format(AppResources.ValidationFieldRequired, AppResources.VerificationCode),
|
|
|
|
|
AppResources.AnErrorHasOccurred);
|
2019-06-04 05:43:52 +03:00
|
|
|
|
return;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (SelectedProviderType == TwoFactorProviderType.Email ||
|
2019-05-27 17:28:38 +03:00
|
|
|
|
SelectedProviderType == TwoFactorProviderType.Authenticator)
|
|
|
|
|
{
|
|
|
|
|
Token = Token.Replace(" ", string.Empty).Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2019-06-01 07:13:36 +03:00
|
|
|
|
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
2019-05-27 17:28:38 +03:00
|
|
|
|
await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
|
|
|
|
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
2019-05-28 16:54:08 +03:00
|
|
|
|
_messagingService.Send("listenYubiKeyOTP", false);
|
|
|
|
|
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
2019-07-02 15:05:34 +03:00
|
|
|
|
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
|
|
|
|
|
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
|
2020-04-13 19:32:23 +03:00
|
|
|
|
TwoFactorAction?.Invoke();
|
2019-05-27 17:28:38 +03:00
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
catch (ApiException e)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (e?.Error != null)
|
2019-10-22 23:37:40 +03:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
|
|
|
|
|
AppResources.AnErrorHasOccurred);
|
|
|
|
|
}
|
2019-05-27 17:28:38 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-24 04:19:45 +03:00
|
|
|
|
|
2019-05-27 17:28:38 +03:00
|
|
|
|
public async Task AnotherMethodAsync()
|
|
|
|
|
{
|
|
|
|
|
var supportedProviders = _authService.GetSupportedTwoFactorProviders();
|
|
|
|
|
var options = supportedProviders.Select(p => p.Name).ToList();
|
|
|
|
|
options.Add(AppResources.RecoveryCodeTitle);
|
|
|
|
|
var method = await Page.DisplayActionSheet(AppResources.TwoStepLoginOptions, AppResources.Cancel,
|
|
|
|
|
null, options.ToArray());
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (method == AppResources.RecoveryCodeTitle)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
_platformUtilsService.LaunchUri("https://help.bitwarden.com/article/lost-two-step-device/");
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
else if (method != AppResources.Cancel)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
2019-05-28 17:12:51 +03:00
|
|
|
|
var selected = supportedProviders.FirstOrDefault(p => p.Name == method)?.Type;
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (selected == SelectedProviderType)
|
2019-05-28 17:12:51 +03:00
|
|
|
|
{
|
|
|
|
|
// Nothing changed
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
SelectedProviderType = selected;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
Load();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<bool> SendEmailAsync(bool showLoading, bool doToast)
|
|
|
|
|
{
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (!EmailMethod)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
2019-06-04 05:43:52 +03:00
|
|
|
|
{
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
|
|
|
|
AppResources.InternetConnectionRequiredTitle);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-05-27 17:28:38 +03:00
|
|
|
|
try
|
|
|
|
|
{
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (showLoading)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.ShowLoadingAsync(AppResources.Submitting);
|
|
|
|
|
}
|
|
|
|
|
var request = new TwoFactorEmailRequest
|
|
|
|
|
{
|
|
|
|
|
Email = _authService.Email,
|
|
|
|
|
MasterPasswordHash = _authService.MasterPasswordHash
|
|
|
|
|
};
|
|
|
|
|
await _apiService.PostTwoFactorEmailAsync(request);
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (showLoading)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (doToast)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
_platformUtilsService.ShowToast("success", null, AppResources.VerificationEmailSent);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
catch (ApiException)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (showLoading)
|
2019-05-27 17:28:38 +03:00
|
|
|
|
{
|
|
|
|
|
await _deviceActionService.HideLoadingAsync();
|
|
|
|
|
}
|
|
|
|
|
await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-05-24 04:19:45 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|