stub our 2fa page backend

This commit is contained in:
Kyle Spearrin 2019-05-27 10:28:38 -04:00
parent 6d49253ee5
commit e8705d49f2
7 changed files with 243 additions and 53 deletions

View file

@ -1,5 +1,4 @@
using System; using System;
using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -14,25 +13,33 @@ namespace Bit.App.Pages
_vm.Page = this; _vm.Page = this;
} }
protected override async void OnAppearing() protected override void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
await _vm.InitAsync(); _vm.Init();
} }
private void Continue_Clicked(object sender, EventArgs e) private async void Continue_Clicked(object sender, EventArgs e)
{ {
if(DoOnce()) if(DoOnce())
{ {
await _vm.SubmitAsync();
} }
} }
private void Methods_Clicked(object sender, EventArgs e) private async void Methods_Clicked(object sender, EventArgs e)
{ {
if(DoOnce()) if(DoOnce())
{ {
await _vm.AnotherMethodAsync();
}
}
private async void ResendEmail_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
await _vm.SendEmailAsync(true, true);
} }
} }
} }

View file

@ -1,8 +1,11 @@
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
@ -14,8 +17,12 @@ namespace Bit.App.Pages
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly IApiService _apiService;
private readonly IPlatformUtilsService _platformUtilsService;
private string _email; private bool _u2fSupported = false;
private TwoFactorProviderType? _selectedProviderType;
private string _twoFactorEmail;
public TwoFactorPageViewModel() public TwoFactorPageViewModel()
{ {
@ -23,22 +30,178 @@ namespace Bit.App.Pages
_authService = ServiceContainer.Resolve<IAuthService>("authService"); _authService = ServiceContainer.Resolve<IAuthService>("authService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
} }
public string Email public string TwoFactorEmail
{ {
get => _email; get => _twoFactorEmail;
set => SetProperty(ref _email, value); set => SetProperty(ref _twoFactorEmail, value);
} }
public async Task InitAsync() public bool Remember { get; set; }
{
public string Token { get; set; }
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Email;
public bool YubikeyMethod => SelectedProviderType == TwoFactorProviderType.YubiKey;
public bool AuthenticatorMethod => SelectedProviderType == TwoFactorProviderType.Authenticator;
public bool EmailMethod => SelectedProviderType == TwoFactorProviderType.Email;
public TwoFactorProviderType? SelectedProviderType
{
get => _selectedProviderType;
set => SetProperty(ref _selectedProviderType, value, additionalPropertyNames: new string[]
{
nameof(EmailMethod),
nameof(DuoMethod),
nameof(YubikeyMethod),
nameof(AuthenticatorMethod)
});
}
public void Init()
{
if(string.IsNullOrWhiteSpace(_authService.Email) ||
string.IsNullOrWhiteSpace(_authService.MasterPasswordHash) ||
_authService.TwoFactorProvidersData == null)
{
// TODO: dismiss modal?
return;
}
// TODO: init U2F
_u2fSupported = false;
var selectedProviderType = _authService.GetDefaultTwoFactorProvider(_u2fSupported);
Load();
}
public void Load()
{
if(SelectedProviderType == null)
{
PageTitle = AppResources.LoginUnavailable;
return;
}
PageTitle = _authService.TwoFactorProviders[SelectedProviderType.Value].Name;
var providerData = _authService.TwoFactorProvidersData[SelectedProviderType.Value];
switch(SelectedProviderType.Value)
{
case TwoFactorProviderType.U2f:
// TODO
break;
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.OrganizationDuo:
// TODO: init duo
var host = providerData["Host"] as string;
var signature = providerData["Signature"] as string;
break;
case TwoFactorProviderType.Email:
TwoFactorEmail = providerData["Email"] as string;
if(_authService.TwoFactorProvidersData.Count > 1)
{
var emailTask = Task.Run(() => SendEmailAsync(false, false));
}
break;
default:
break;
}
} }
public async Task SubmitAsync() public async Task SubmitAsync()
{ {
if(SelectedProviderType == null)
{
return;
}
if(string.IsNullOrWhiteSpace(Token))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.VerificationCode),
AppResources.AnErrorHasOccurred);
}
if(SelectedProviderType == TwoFactorProviderType.Email ||
SelectedProviderType == TwoFactorProviderType.Authenticator)
{
Token = Token.Replace(" ", string.Empty).Trim();
}
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
await _deviceActionService.HideLoadingAsync();
var task = Task.Run(() => _syncService.FullSyncAsync(true));
Application.Current.MainPage = new TabsPage();
}
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
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());
if(method == AppResources.RecoveryCodeTitle)
{
_platformUtilsService.LaunchUri("https://help.bitwarden.com/article/lost-two-step-device/");
}
else
{
SelectedProviderType = supportedProviders.FirstOrDefault(p => p.Name == method)?.Type;
Load();
}
}
public async Task<bool> SendEmailAsync(bool showLoading, bool doToast)
{
if(SelectedProviderType != TwoFactorProviderType.Email)
{
return false;
}
try
{
if(showLoading)
{
await _deviceActionService.ShowLoadingAsync(AppResources.Submitting);
}
var request = new TwoFactorEmailRequest
{
Email = _authService.Email,
MasterPasswordHash = _authService.MasterPasswordHash
};
await _apiService.PostTwoFactorEmailAsync(request);
if(showLoading)
{
await _deviceActionService.HideLoadingAsync();
}
if(doToast)
{
_platformUtilsService.ShowToast("success", null, AppResources.VerificationEmailSent);
}
return true;
}
catch(ApiException)
{
if(showLoading)
{
await _deviceActionService.HideLoadingAsync();
}
await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent);
return false;
}
} }
} }
} }

View file

@ -44,5 +44,6 @@ namespace Bit.Core.Abstractions
Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data, Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
string organizationId); string organizationId);
Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username); Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username);
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
} }
} }

View file

@ -11,7 +11,8 @@ namespace Bit.Core.Abstractions
string Email { get; set; } string Email { get; set; }
string MasterPasswordHash { get; set; } string MasterPasswordHash { get; set; }
TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; } TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; }
Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; } Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported); TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported);
List<TwoFactorProvider> GetSupportedTwoFactorProviders(); List<TwoFactorProvider> GetSupportedTwoFactorProviders();

View file

@ -0,0 +1,8 @@
namespace Bit.Core.Models.Request
{
public class TwoFactorEmailRequest
{
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
}
}

View file

@ -271,6 +271,16 @@ namespace Bit.Core.Services
#endregion #endregion
#region Two Factor APIs
public Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request)
{
return SendAsync<TwoFactorEmailRequest, object>(
HttpMethod.Post, "/two-factor/send-email-login", request, false, false);
}
#endregion
#region HIBP APIs #region HIBP APIs
public Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username) public Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username)

View file

@ -24,7 +24,6 @@ namespace Bit.Core.Services
private SymmetricCryptoKey _key; private SymmetricCryptoKey _key;
private KdfType? _kdf; private KdfType? _kdf;
private int? _kdfIterations; private int? _kdfIterations;
private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders;
public AuthService( public AuthService(
ICryptoService cryptoService, ICryptoService cryptoService,
@ -47,21 +46,21 @@ namespace Bit.Core.Services
_messagingService = messagingService; _messagingService = messagingService;
_setCryptoKeys = setCryptoKeys; _setCryptoKeys = setCryptoKeys;
_twoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>(); TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
_twoFactorProviders.Add(TwoFactorProviderType.Authenticator, new TwoFactorProvider TwoFactorProviders.Add(TwoFactorProviderType.Authenticator, new TwoFactorProvider
{ {
Type = TwoFactorProviderType.Authenticator, Type = TwoFactorProviderType.Authenticator,
Priority = 1, Priority = 1,
Sort = 1 Sort = 1
}); });
_twoFactorProviders.Add(TwoFactorProviderType.YubiKey, new TwoFactorProvider TwoFactorProviders.Add(TwoFactorProviderType.YubiKey, new TwoFactorProvider
{ {
Type = TwoFactorProviderType.YubiKey, Type = TwoFactorProviderType.YubiKey,
Priority = 3, Priority = 3,
Sort = 2, Sort = 2,
Premium = true Premium = true
}); });
_twoFactorProviders.Add(TwoFactorProviderType.Duo, new TwoFactorProvider TwoFactorProviders.Add(TwoFactorProviderType.Duo, new TwoFactorProvider
{ {
Type = TwoFactorProviderType.Duo, Type = TwoFactorProviderType.Duo,
Name = "Duo", Name = "Duo",
@ -69,21 +68,21 @@ namespace Bit.Core.Services
Sort = 3, Sort = 3,
Premium = true Premium = true
}); });
_twoFactorProviders.Add(TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider TwoFactorProviders.Add(TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider
{ {
Type = TwoFactorProviderType.OrganizationDuo, Type = TwoFactorProviderType.OrganizationDuo,
Name = "Duo (Organization)", Name = "Duo (Organization)",
Priority = 10, Priority = 10,
Sort = 4 Sort = 4
}); });
_twoFactorProviders.Add(TwoFactorProviderType.U2f, new TwoFactorProvider TwoFactorProviders.Add(TwoFactorProviderType.U2f, new TwoFactorProvider
{ {
Type = TwoFactorProviderType.U2f, Type = TwoFactorProviderType.U2f,
Priority = 4, Priority = 4,
Sort = 5, Sort = 5,
Premium = true Premium = true
}); });
_twoFactorProviders.Add(TwoFactorProviderType.Email, new TwoFactorProvider TwoFactorProviders.Add(TwoFactorProviderType.Email, new TwoFactorProvider
{ {
Type = TwoFactorProviderType.Email, Type = TwoFactorProviderType.Email,
Priority = 0, Priority = 0,
@ -93,25 +92,26 @@ namespace Bit.Core.Services
public string Email { get; set; } public string Email { get; set; }
public string MasterPasswordHash { get; set; } public string MasterPasswordHash { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; } public Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
public TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; } public TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; }
public void Init() public void Init()
{ {
_twoFactorProviders[TwoFactorProviderType.Email].Name = _i18nService.T("EmailTitle"); TwoFactorProviders[TwoFactorProviderType.Email].Name = _i18nService.T("EmailTitle");
_twoFactorProviders[TwoFactorProviderType.Email].Description = _i18nService.T("EmailDesc"); TwoFactorProviders[TwoFactorProviderType.Email].Description = _i18nService.T("EmailDesc");
_twoFactorProviders[TwoFactorProviderType.Authenticator].Name = _i18nService.T("AuthenticatorAppTitle"); TwoFactorProviders[TwoFactorProviderType.Authenticator].Name = _i18nService.T("AuthenticatorAppTitle");
_twoFactorProviders[TwoFactorProviderType.Authenticator].Description = TwoFactorProviders[TwoFactorProviderType.Authenticator].Description =
_i18nService.T("AuthenticatorAppDesc"); _i18nService.T("AuthenticatorAppDesc");
_twoFactorProviders[TwoFactorProviderType.Duo].Description = _i18nService.T("DuoDesc"); TwoFactorProviders[TwoFactorProviderType.Duo].Description = _i18nService.T("DuoDesc");
_twoFactorProviders[TwoFactorProviderType.OrganizationDuo].Name = TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].Name =
string.Format("Duo ({0})", _i18nService.T("Organization")); string.Format("Duo ({0})", _i18nService.T("Organization"));
_twoFactorProviders[TwoFactorProviderType.OrganizationDuo].Description = TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].Description =
_i18nService.T("DuoOrganizationDesc"); _i18nService.T("DuoOrganizationDesc");
_twoFactorProviders[TwoFactorProviderType.U2f].Name = _i18nService.T("U2fTitle"); TwoFactorProviders[TwoFactorProviderType.U2f].Name = _i18nService.T("U2fTitle");
_twoFactorProviders[TwoFactorProviderType.U2f].Description = _i18nService.T("U2fDesc"); TwoFactorProviders[TwoFactorProviderType.U2f].Description = _i18nService.T("U2fDesc");
_twoFactorProviders[TwoFactorProviderType.YubiKey].Name = _i18nService.T("YubiKeyTitle"); TwoFactorProviders[TwoFactorProviderType.YubiKey].Name = _i18nService.T("YubiKeyTitle");
_twoFactorProviders[TwoFactorProviderType.YubiKey].Description = _i18nService.T("YubiKeyDesc"); TwoFactorProviders[TwoFactorProviderType.YubiKey].Description = _i18nService.T("YubiKeyDesc");
} }
public async Task<AuthResult> LogInAsync(string email, string masterPassword) public async Task<AuthResult> LogInAsync(string email, string masterPassword)
@ -146,56 +146,56 @@ namespace Bit.Core.Services
public List<TwoFactorProvider> GetSupportedTwoFactorProviders() public List<TwoFactorProvider> GetSupportedTwoFactorProviders()
{ {
var providers = new List<TwoFactorProvider>(); var providers = new List<TwoFactorProvider>();
if(TwoFactorProviders == null) if(TwoFactorProvidersData == null)
{ {
return providers; return providers;
} }
if(TwoFactorProviders.ContainsKey(TwoFactorProviderType.OrganizationDuo) && if(TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.OrganizationDuo) &&
_platformUtilsService.SupportsDuo()) _platformUtilsService.SupportsDuo())
{ {
providers.Add(_twoFactorProviders[TwoFactorProviderType.OrganizationDuo]); providers.Add(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]);
} }
if(TwoFactorProviders.ContainsKey(TwoFactorProviderType.Authenticator)) if(TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Authenticator))
{ {
providers.Add(_twoFactorProviders[TwoFactorProviderType.Authenticator]); providers.Add(TwoFactorProviders[TwoFactorProviderType.Authenticator]);
} }
if(TwoFactorProviders.ContainsKey(TwoFactorProviderType.YubiKey)) if(TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.YubiKey))
{ {
providers.Add(_twoFactorProviders[TwoFactorProviderType.YubiKey]); providers.Add(TwoFactorProviders[TwoFactorProviderType.YubiKey]);
} }
if(TwoFactorProviders.ContainsKey(TwoFactorProviderType.Duo) && _platformUtilsService.SupportsDuo()) if(TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Duo) && _platformUtilsService.SupportsDuo())
{ {
providers.Add(_twoFactorProviders[TwoFactorProviderType.Duo]); providers.Add(TwoFactorProviders[TwoFactorProviderType.Duo]);
} }
if(TwoFactorProviders.ContainsKey(TwoFactorProviderType.U2f) && _platformUtilsService.SupportsU2f()) if(TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.U2f) && _platformUtilsService.SupportsU2f())
{ {
providers.Add(_twoFactorProviders[TwoFactorProviderType.U2f]); providers.Add(TwoFactorProviders[TwoFactorProviderType.U2f]);
} }
if(TwoFactorProviders.ContainsKey(TwoFactorProviderType.Email)) if(TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Email))
{ {
providers.Add(_twoFactorProviders[TwoFactorProviderType.Email]); providers.Add(TwoFactorProviders[TwoFactorProviderType.Email]);
} }
return providers; return providers;
} }
public TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported) public TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported)
{ {
if(TwoFactorProviders == null) if(TwoFactorProvidersData == null)
{ {
return null; return null;
} }
if(SelectedTwoFactorProviderType != null && if(SelectedTwoFactorProviderType != null &&
TwoFactorProviders.ContainsKey(SelectedTwoFactorProviderType.Value)) TwoFactorProvidersData.ContainsKey(SelectedTwoFactorProviderType.Value))
{ {
return SelectedTwoFactorProviderType.Value; return SelectedTwoFactorProviderType.Value;
} }
TwoFactorProviderType? providerType = null; TwoFactorProviderType? providerType = null;
var providerPriority = -1; var providerPriority = -1;
foreach(var providerKvp in TwoFactorProviders) foreach(var providerKvp in TwoFactorProvidersData)
{ {
if(_twoFactorProviders.ContainsKey(providerKvp.Key)) if(TwoFactorProviders.ContainsKey(providerKvp.Key))
{ {
var provider = _twoFactorProviders[providerKvp.Key]; var provider = TwoFactorProviders[providerKvp.Key];
if(provider.Priority > providerPriority) if(provider.Priority > providerPriority)
{ {
if(providerKvp.Key == TwoFactorProviderType.U2f && !u2fSupported) if(providerKvp.Key == TwoFactorProviderType.U2f && !u2fSupported)
@ -274,7 +274,7 @@ namespace Bit.Core.Services
Email = email; Email = email;
MasterPasswordHash = hashedPassword; MasterPasswordHash = hashedPassword;
_key = _setCryptoKeys ? key : null; _key = _setCryptoKeys ? key : null;
TwoFactorProviders = twoFactorResponse.TwoFactorProviders2; TwoFactorProvidersData = twoFactorResponse.TwoFactorProviders2;
result.TwoFactorProviders = twoFactorResponse.TwoFactorProviders2; result.TwoFactorProviders = twoFactorResponse.TwoFactorProviders2;
return result; return result;
} }
@ -320,7 +320,7 @@ namespace Bit.Core.Services
{ {
Email = null; Email = null;
MasterPasswordHash = null; MasterPasswordHash = null;
TwoFactorProviders = null; TwoFactorProvidersData = null;
SelectedTwoFactorProviderType = null; SelectedTwoFactorProviderType = null;
} }
} }