two-factor page UI

This commit is contained in:
Kyle Spearrin 2019-05-27 11:57:10 -04:00
parent e8705d49f2
commit bf4f0bdba0
4 changed files with 131 additions and 9 deletions

View file

@ -100,7 +100,8 @@ namespace Bit.App.Pages
} }
if(response.TwoFactor) if(response.TwoFactor)
{ {
// TODO: 2fa page var page = new TwoFactorPage();
await Page.Navigation.PushModalAsync(new NavigationPage(page));
} }
else else
{ {

View file

@ -16,15 +16,103 @@
<ContentPage.Resources> <ContentPage.Resources>
<ResourceDictionary> <ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />
<u:IsNullConverter x:Key="isNull" />
</ResourceDictionary> </ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Continue}" Clicked="Continue_Clicked" /> <ToolbarItem Text="{u:I18n Continue}" Clicked="Continue_Clicked" x:Name="_continueItem" />
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<ScrollView> <ScrollView>
<StackLayout Spacing="0" Padding="0">
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding TotpMethod, Mode=OneWay}">
<Label
Text="{Binding TotpInstruction, Mode=OneWay}"
Margin="20, 0"
HorizontalTextAlignment="Center" />
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input">
<Label
Text="{u:I18n VerificationCode}"
StyleClass="box-label" />
<Entry
Text="{Binding Token}"
Keyboard="Numeric"
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n RememberMe}"
StyleClass="box-label, box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Remember}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
</StackLayout>
</StackLayout>
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding YubikeyMethod, Mode=OneWay}">
<Label
Text="{Binding YubikeyInstruction, Mode=OneWay}"
Margin="20, 0"
HorizontalTextAlignment="Center" />
<Image
Source="yubikey.png"
Margin="20, 0"
WidthRequest="266"
HeightRequest="160"
HorizontalOptions="Center" />
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input">
<Entry
Text="{Binding Token}"
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n RememberMe}"
StyleClass="box-label, box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Remember}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
</StackLayout>
</StackLayout>
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding DuoMethod, Mode=OneWay}">
<controls:HybridWebView
x:Name="_duoWebView"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
MinimumHeightRequest="400" />
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n RememberMe}"
StyleClass="box-label, box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Remember}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
</StackLayout>
</StackLayout>
<StackLayout
Spacing="0"
Padding="0"
IsVisible="{Binding SelectedProviderType, Mode=OneWay, Converter={StaticResource isNull}}">
<Label
Text="{u:I18n NoTwoStepAvailable}"
Margin="20, 0"
HorizontalTextAlignment="Center" />
</StackLayout>
<Button Text="{u:I18n UseAnotherTwoStepMethod}"
Clicked="Methods_Clicked"></Button>
</StackLayout>
</ScrollView> </ScrollView>
</pages:BaseContentPage> </pages:BaseContentPage>

View file

@ -1,4 +1,5 @@
using System; using Bit.App.Controls;
using System;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -11,8 +12,11 @@ namespace Bit.App.Pages
InitializeComponent(); InitializeComponent();
_vm = BindingContext as TwoFactorPageViewModel; _vm = BindingContext as TwoFactorPageViewModel;
_vm.Page = this; _vm.Page = this;
DuoWebView = _duoWebView;
} }
public HybridWebView DuoWebView { get; set; }
protected override void OnAppearing() protected override void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();

View file

@ -6,6 +6,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Linq; using System.Linq;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
@ -19,10 +20,12 @@ namespace Bit.App.Pages
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly IApiService _apiService; private readonly IApiService _apiService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IEnvironmentService _environmentService;
private bool _u2fSupported = false; private bool _u2fSupported = false;
private TwoFactorProviderType? _selectedProviderType; private TwoFactorProviderType? _selectedProviderType;
private string _twoFactorEmail; private string _twoFactorEmail;
private string _webVaultUrl = "https://vault.bitwarden.com";
public TwoFactorPageViewModel() public TwoFactorPageViewModel()
{ {
@ -32,6 +35,7 @@ namespace Bit.App.Pages
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService"); _apiService = ServiceContainer.Resolve<IApiService>("apiService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
} }
public string TwoFactorEmail public string TwoFactorEmail
@ -52,6 +56,14 @@ namespace Bit.App.Pages
public bool EmailMethod => SelectedProviderType == TwoFactorProviderType.Email; public bool EmailMethod => SelectedProviderType == TwoFactorProviderType.Email;
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
public string TotpInstruction => AuthenticatorMethod ? AppResources.EnterVerificationCodeApp :
AppResources.EnterVerificationCodeEmail;
public string YubikeyInstruction => Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
AppResources.YubiKeyInstruction;
public TwoFactorProviderType? SelectedProviderType public TwoFactorProviderType? SelectedProviderType
{ {
get => _selectedProviderType; get => _selectedProviderType;
@ -60,7 +72,9 @@ namespace Bit.App.Pages
nameof(EmailMethod), nameof(EmailMethod),
nameof(DuoMethod), nameof(DuoMethod),
nameof(YubikeyMethod), nameof(YubikeyMethod),
nameof(AuthenticatorMethod) nameof(AuthenticatorMethod),
nameof(TotpMethod),
nameof(TotpInstruction)
}); });
} }
@ -74,10 +88,19 @@ namespace Bit.App.Pages
return; return;
} }
if(!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
{
_webVaultUrl = _environmentService.BaseUrl;
}
else if(!string.IsNullOrWhiteSpace(_environmentService.WebVaultUrl))
{
_webVaultUrl = _environmentService.WebVaultUrl;
}
// TODO: init U2F // TODO: init U2F
_u2fSupported = false; _u2fSupported = false;
var selectedProviderType = _authService.GetDefaultTwoFactorProvider(_u2fSupported); SelectedProviderType = _authService.GetDefaultTwoFactorProvider(_u2fSupported);
Load(); Load();
} }
@ -97,9 +120,15 @@ namespace Bit.App.Pages
break; break;
case TwoFactorProviderType.Duo: case TwoFactorProviderType.Duo:
case TwoFactorProviderType.OrganizationDuo: case TwoFactorProviderType.OrganizationDuo:
// TODO: init duo var host = WebUtility.UrlEncode(providerData["Host"] as string);
var host = providerData["Host"] as string; var req = WebUtility.UrlEncode(providerData["Signature"] as string);
var signature = providerData["Signature"] as string; var page = Page as TwoFactorPage;
page.DuoWebView.Uri = $"{_webVaultUrl}/duo-connector.html?host={host}&request={req}";
page.DuoWebView.RegisterAction(async sig =>
{
Token = sig;
await SubmitAsync();
});
break; break;
case TwoFactorProviderType.Email: case TwoFactorProviderType.Email:
TwoFactorEmail = providerData["Email"] as string; TwoFactorEmail = providerData["Email"] as string;