mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-25 12:05:59 +01:00
FIDO2 WebAuthn support for mobile (#1519)
* FIDO2 / WebAuthn support for mobile * fixes
This commit is contained in:
parent
d050215ebc
commit
307a5a5843
@ -776,6 +776,11 @@ namespace Bit.Droid.Services
|
|||||||
_messagingService.Send("finishMainActivity");
|
_messagingService.Send("finishMainActivity");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SupportsFido2()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private bool DeleteDir(Java.IO.File dir)
|
private bool DeleteDir(Java.IO.File dir)
|
||||||
{
|
{
|
||||||
if (dir != null && dir.IsDirectory)
|
if (dir != null && dir.IsDirectory)
|
||||||
|
@ -45,5 +45,6 @@ namespace Bit.App.Abstractions
|
|||||||
bool UsingDarkTheme();
|
bool UsingDarkTheme();
|
||||||
long GetActiveTime();
|
long GetActiveTime();
|
||||||
void CloseMainApp();
|
void CloseMainApp();
|
||||||
|
bool SupportsFido2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Padding="10, 0">
|
<StackLayout Padding="10, 0">
|
||||||
<Button Text="{u:I18n LogIn}" Clicked="LogIn_Clicked" IsEnabled="{Binding LoginEnabled}"/>
|
<Button Text="{u:I18n LogIn}" Clicked="LogIn_Clicked" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -16,6 +16,8 @@ namespace Bit.App.Pages
|
|||||||
private readonly LoginPageViewModel _vm;
|
private readonly LoginPageViewModel _vm;
|
||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
|
private bool _inputFocused;
|
||||||
|
|
||||||
public LoginPage(string email = null, AppOptions appOptions = null)
|
public LoginPage(string email = null, AppOptions appOptions = null)
|
||||||
{
|
{
|
||||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
@ -58,13 +60,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
await _vm.InitAsync();
|
await _vm.InitAsync();
|
||||||
if (string.IsNullOrWhiteSpace(_vm.Email))
|
if (!_inputFocused)
|
||||||
{
|
{
|
||||||
RequestFocus(_email);
|
RequestFocus(string.IsNullOrWhiteSpace(_vm.Email) ? _email : _masterPassword);
|
||||||
}
|
_inputFocused = true;
|
||||||
else
|
|
||||||
{
|
|
||||||
RequestFocus(_masterPassword);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ namespace Bit.App.Pages
|
|||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private string _email;
|
private string _email;
|
||||||
private string _masterPassword;
|
private string _masterPassword;
|
||||||
private bool _loginEnabled = true;
|
|
||||||
|
|
||||||
public LoginPageViewModel()
|
public LoginPageViewModel()
|
||||||
{
|
{
|
||||||
@ -73,16 +72,6 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _masterPassword, value);
|
set => SetProperty(ref _masterPassword, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoginEnabled {
|
|
||||||
get => _loginEnabled;
|
|
||||||
set => SetProperty(ref _loginEnabled, value);
|
|
||||||
}
|
|
||||||
public bool Loading
|
|
||||||
{
|
|
||||||
get => !LoginEnabled;
|
|
||||||
set => LoginEnabled = !value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Command LogInCommand { get; }
|
public Command LogInCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
@ -106,7 +95,7 @@ namespace Bit.App.Pages
|
|||||||
RememberEmail = rememberEmail.GetValueOrDefault(true);
|
RememberEmail = rememberEmail.GetValueOrDefault(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogInAsync()
|
public async Task LogInAsync(bool showLoading = true)
|
||||||
{
|
{
|
||||||
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
||||||
{
|
{
|
||||||
@ -140,10 +129,9 @@ namespace Bit.App.Pages
|
|||||||
ShowPassword = false;
|
ShowPassword = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Loading)
|
if (showLoading)
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||||
Loading = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken);
|
var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken);
|
||||||
@ -156,25 +144,21 @@ namespace Bit.App.Pages
|
|||||||
await _storageService.RemoveAsync(Keys_RememberedEmail);
|
await _storageService.RemoveAsync(Keys_RememberedEmail);
|
||||||
}
|
}
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
|
|
||||||
if (response.CaptchaNeeded)
|
if (response.CaptchaNeeded)
|
||||||
{
|
{
|
||||||
if (await HandleCaptchaAsync(response.CaptchaSiteKey))
|
if (await HandleCaptchaAsync(response.CaptchaSiteKey))
|
||||||
{
|
{
|
||||||
await LogInAsync();
|
await LogInAsync(false);
|
||||||
_captchaToken = null;
|
_captchaToken = null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Loading = false;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
MasterPassword = string.Empty;
|
MasterPassword = string.Empty;
|
||||||
_captchaToken = null;
|
_captchaToken = null;
|
||||||
|
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|
||||||
if (response.TwoFactor)
|
if (response.TwoFactor)
|
||||||
{
|
{
|
||||||
StartTwoFactorAction?.Invoke();
|
StartTwoFactorAction?.Invoke();
|
||||||
@ -198,7 +182,6 @@ namespace Bit.App.Pages
|
|||||||
AppResources.AnErrorHasOccurred);
|
AppResources.AnErrorHasOccurred);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
|
@ -123,43 +123,28 @@ namespace Bit.App.Pages
|
|||||||
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
|
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
|
||||||
|
|
||||||
WebAuthenticatorResult authResult = null;
|
WebAuthenticatorResult authResult = null;
|
||||||
bool cancelled = false;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
|
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
|
||||||
new Uri(redirectUri));
|
new Uri(redirectUri));
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException taskCanceledException)
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// user canceled
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var code = GetResultCode(authResult, state);
|
||||||
|
if (!string.IsNullOrEmpty(code))
|
||||||
|
{
|
||||||
|
await LogIn(code, codeVerifier, redirectUri);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
cancelled = true;
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
||||||
}
|
AppResources.AnErrorHasOccurred);
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// WebAuthenticator throws NSErrorException if iOS flow is cancelled - by setting cancelled to true
|
|
||||||
// here we maintain the appearance of a clean cancellation (we don't want to do this across the board
|
|
||||||
// because we still want to present legitimate errors). If/when this is fixed, we can remove this
|
|
||||||
// particular catch block (catching taskCanceledException above must remain)
|
|
||||||
// https://github.com/xamarin/Essentials/issues/1240
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
|
||||||
{
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
cancelled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!cancelled)
|
|
||||||
{
|
|
||||||
var code = GetResultCode(authResult, state);
|
|
||||||
if (!string.IsNullOrEmpty(code))
|
|
||||||
{
|
|
||||||
await LogIn(code, codeVerifier, redirectUri);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
|
||||||
AppResources.AnErrorHasOccurred);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" IsEnabled="{Binding SubmitEnabled}"/>
|
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
|
@ -11,6 +11,8 @@ namespace Bit.App.Pages
|
|||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly RegisterPageViewModel _vm;
|
private readonly RegisterPageViewModel _vm;
|
||||||
|
|
||||||
|
private bool _inputFocused;
|
||||||
|
|
||||||
public RegisterPage(HomePage homePage)
|
public RegisterPage(HomePage homePage)
|
||||||
{
|
{
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
@ -45,7 +47,11 @@ namespace Bit.App.Pages
|
|||||||
protected override void OnAppearing()
|
protected override void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
RequestFocus(_email);
|
if (!_inputFocused)
|
||||||
|
{
|
||||||
|
RequestFocus(_email);
|
||||||
|
_inputFocused = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Submit_Clicked(object sender, EventArgs e)
|
private async void Submit_Clicked(object sender, EventArgs e)
|
||||||
|
@ -22,7 +22,6 @@ namespace Bit.App.Pages
|
|||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private bool _acceptPolicies;
|
private bool _acceptPolicies;
|
||||||
private bool _submitEnabled = true;
|
|
||||||
|
|
||||||
public RegisterPageViewModel()
|
public RegisterPageViewModel()
|
||||||
{
|
{
|
||||||
@ -60,16 +59,6 @@ namespace Bit.App.Pages
|
|||||||
get => _acceptPolicies;
|
get => _acceptPolicies;
|
||||||
set => SetProperty(ref _acceptPolicies, value);
|
set => SetProperty(ref _acceptPolicies, value);
|
||||||
}
|
}
|
||||||
public bool SubmitEnabled
|
|
||||||
{
|
|
||||||
get => _submitEnabled;
|
|
||||||
set => SetProperty(ref _submitEnabled, value);
|
|
||||||
}
|
|
||||||
public bool Loading
|
|
||||||
{
|
|
||||||
get => !SubmitEnabled;
|
|
||||||
set => SubmitEnabled = !value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Thickness SwitchMargin
|
public Thickness SwitchMargin
|
||||||
{
|
{
|
||||||
@ -96,7 +85,7 @@ namespace Bit.App.Pages
|
|||||||
protected override IDeviceActionService deviceActionService => _deviceActionService;
|
protected override IDeviceActionService deviceActionService => _deviceActionService;
|
||||||
protected override IPlatformUtilsService platformUtilsService => _platformUtilsService;
|
protected override IPlatformUtilsService platformUtilsService => _platformUtilsService;
|
||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync(bool showLoading = true)
|
||||||
{
|
{
|
||||||
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
||||||
{
|
{
|
||||||
@ -143,6 +132,11 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Password strength check?
|
// TODO: Password strength check?
|
||||||
|
|
||||||
|
if (showLoading)
|
||||||
|
{
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
|
||||||
|
}
|
||||||
|
|
||||||
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
|
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
|
||||||
Email = Email.Trim().ToLower();
|
Email = Email.Trim().ToLower();
|
||||||
@ -172,14 +166,8 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Loading)
|
|
||||||
{
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
|
|
||||||
Loading = true;
|
|
||||||
}
|
|
||||||
await _apiService.PostRegisterAsync(request);
|
await _apiService.PostRegisterAsync(request);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
Loading = false;
|
|
||||||
_platformUtilsService.ShowToast("success", null, AppResources.AccountCreated,
|
_platformUtilsService.ShowToast("success", null, AppResources.AccountCreated,
|
||||||
new System.Collections.Generic.Dictionary<string, object>
|
new System.Collections.Generic.Dictionary<string, object>
|
||||||
{
|
{
|
||||||
@ -193,19 +181,12 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (await HandleCaptchaAsync(e.Error.CaptchaSiteKey))
|
if (await HandleCaptchaAsync(e.Error.CaptchaSiteKey))
|
||||||
{
|
{
|
||||||
await SubmitAsync();
|
await SubmitAsync(false);
|
||||||
_captchaToken = null;
|
_captchaToken = null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else
|
return;
|
||||||
{
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
Loading = false;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
Loading = false;
|
|
||||||
if (e?.Error != null)
|
if (e?.Error != null)
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
|
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
|
||||||
|
@ -102,6 +102,30 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
<StackLayout Spacing="20" Padding="0" IsVisible="{Binding Fido2Method, Mode=OneWay}">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Fido2Instruction}"
|
||||||
|
Margin="10, 20, 10, 0"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
<Image
|
||||||
|
Source="yubikey.png"
|
||||||
|
Margin="10, 0"
|
||||||
|
WidthRequest="266"
|
||||||
|
HeightRequest="160"
|
||||||
|
HorizontalOptions="Center" />
|
||||||
|
<StackLayout StyleClass="box">
|
||||||
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n RememberMe}"
|
||||||
|
StyleClass="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}"
|
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding DuoMethod, Mode=OneWay}"
|
||||||
VerticalOptions="FillAndExpand">
|
VerticalOptions="FillAndExpand">
|
||||||
<controls:HybridWebView
|
<controls:HybridWebView
|
||||||
|
@ -168,11 +168,15 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryAgain_Clicked(object sender, EventArgs e)
|
private async void TryAgain_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
if (_vm.YubikeyMethod)
|
if (_vm.Fido2Method)
|
||||||
|
{
|
||||||
|
await _vm.Fido2AuthenticateAsync();
|
||||||
|
}
|
||||||
|
else if (_vm.YubikeyMethod)
|
||||||
{
|
{
|
||||||
_messagingService.Send("listenYubiKeyOTP", true);
|
_messagingService.Send("listenYubiKeyOTP", true);
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,13 @@ using Bit.Core.Exceptions;
|
|||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@ -27,7 +31,6 @@ namespace Bit.App.Pages
|
|||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
|
|
||||||
private bool _u2fSupported = false;
|
|
||||||
private TwoFactorProviderType? _selectedProviderType;
|
private TwoFactorProviderType? _selectedProviderType;
|
||||||
private string _totpInstruction;
|
private string _totpInstruction;
|
||||||
private string _webVaultUrl = "https://vault.bitwarden.com";
|
private string _webVaultUrl = "https://vault.bitwarden.com";
|
||||||
@ -65,6 +68,8 @@ namespace Bit.App.Pages
|
|||||||
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
|
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
|
||||||
SelectedProviderType == TwoFactorProviderType.OrganizationDuo;
|
SelectedProviderType == TwoFactorProviderType.OrganizationDuo;
|
||||||
|
|
||||||
|
public bool Fido2Method => SelectedProviderType == TwoFactorProviderType.Fido2WebAuthn;
|
||||||
|
|
||||||
public bool YubikeyMethod => SelectedProviderType == TwoFactorProviderType.YubiKey;
|
public bool YubikeyMethod => SelectedProviderType == TwoFactorProviderType.YubiKey;
|
||||||
|
|
||||||
public bool AuthenticatorMethod => SelectedProviderType == TwoFactorProviderType.Authenticator;
|
public bool AuthenticatorMethod => SelectedProviderType == TwoFactorProviderType.Authenticator;
|
||||||
@ -73,7 +78,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
|
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
|
||||||
|
|
||||||
public bool ShowTryAgain => YubikeyMethod && Device.RuntimePlatform == Device.iOS;
|
public bool ShowTryAgain => (YubikeyMethod && Device.RuntimePlatform == Device.iOS) || Fido2Method;
|
||||||
|
|
||||||
public bool ShowContinue
|
public bool ShowContinue
|
||||||
{
|
{
|
||||||
@ -97,6 +102,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
nameof(EmailMethod),
|
nameof(EmailMethod),
|
||||||
nameof(DuoMethod),
|
nameof(DuoMethod),
|
||||||
|
nameof(Fido2Method),
|
||||||
nameof(YubikeyMethod),
|
nameof(YubikeyMethod),
|
||||||
nameof(AuthenticatorMethod),
|
nameof(AuthenticatorMethod),
|
||||||
nameof(TotpMethod),
|
nameof(TotpMethod),
|
||||||
@ -128,10 +134,7 @@ namespace Bit.App.Pages
|
|||||||
_webVaultUrl = _environmentService.WebVaultUrl;
|
_webVaultUrl = _environmentService.WebVaultUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: init U2F
|
SelectedProviderType = _authService.GetDefaultTwoFactorProvider(_platformUtilsService.SupportsFido2());
|
||||||
_u2fSupported = false;
|
|
||||||
|
|
||||||
SelectedProviderType = _authService.GetDefaultTwoFactorProvider(_u2fSupported);
|
|
||||||
Load();
|
Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +150,8 @@ namespace Bit.App.Pages
|
|||||||
var providerData = _authService.TwoFactorProvidersData[SelectedProviderType.Value];
|
var providerData = _authService.TwoFactorProvidersData[SelectedProviderType.Value];
|
||||||
switch (SelectedProviderType.Value)
|
switch (SelectedProviderType.Value)
|
||||||
{
|
{
|
||||||
case TwoFactorProviderType.U2f:
|
case TwoFactorProviderType.Fido2WebAuthn:
|
||||||
// TODO
|
Fido2AuthenticateAsync(providerData);
|
||||||
break;
|
break;
|
||||||
case TwoFactorProviderType.YubiKey:
|
case TwoFactorProviderType.YubiKey:
|
||||||
_messagingService.Send("listenYubiKeyOTP", true);
|
_messagingService.Send("listenYubiKeyOTP", true);
|
||||||
@ -183,10 +186,73 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_messagingService.Send("listenYubiKeyOTP", false);
|
_messagingService.Send("listenYubiKeyOTP", false);
|
||||||
}
|
}
|
||||||
ShowContinue = !(SelectedProviderType == null || DuoMethod);
|
ShowContinue = !(SelectedProviderType == null || DuoMethod || Fido2Method);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task Fido2AuthenticateAsync(Dictionary<string, object> providerData = null)
|
||||||
|
{
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
||||||
|
|
||||||
|
if (providerData == null)
|
||||||
|
{
|
||||||
|
providerData = _authService.TwoFactorProvidersData[TwoFactorProviderType.Fido2WebAuthn];
|
||||||
|
}
|
||||||
|
|
||||||
|
var callbackUri = "bitwarden://webauthn-callback";
|
||||||
|
var data = AppHelpers.EncodeDataParameter(new
|
||||||
|
{
|
||||||
|
callbackUri = callbackUri,
|
||||||
|
data = JsonConvert.SerializeObject(providerData),
|
||||||
|
btnText = AppResources.Fido2AuthenticateWebAuthn,
|
||||||
|
});
|
||||||
|
|
||||||
|
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
|
||||||
|
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=2";
|
||||||
|
|
||||||
|
WebAuthenticatorResult authResult = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var options = new WebAuthenticatorOptions
|
||||||
|
{
|
||||||
|
Url = new Uri(url),
|
||||||
|
CallbackUrl = new Uri(callbackUri),
|
||||||
|
PrefersEphemeralWebBrowserSession = true,
|
||||||
|
};
|
||||||
|
authResult = await WebAuthenticator.AuthenticateAsync(options);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// user canceled
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string response = null;
|
||||||
|
if (authResult != null && authResult.Properties.TryGetValue("data", out var resultData))
|
||||||
|
{
|
||||||
|
response = Uri.UnescapeDataString(resultData);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(response))
|
||||||
|
{
|
||||||
|
Token = response;
|
||||||
|
await SubmitAsync(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError))
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(resultError, AppResources.AnErrorHasOccurred);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2SomethingWentWrong,
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SubmitAsync(bool showLoading = true)
|
||||||
{
|
{
|
||||||
if (SelectedProviderType == null)
|
if (SelectedProviderType == null)
|
||||||
{
|
{
|
||||||
@ -213,7 +279,10 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
if (showLoading)
|
||||||
|
{
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
||||||
|
}
|
||||||
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
|
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
|
||||||
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@ -22,7 +19,7 @@ namespace Bit.App.Pages
|
|||||||
protected async Task<bool> HandleCaptchaAsync(string CaptchaSiteKey)
|
protected async Task<bool> HandleCaptchaAsync(string CaptchaSiteKey)
|
||||||
{
|
{
|
||||||
var callbackUri = "bitwarden://captcha-callback";
|
var callbackUri = "bitwarden://captcha-callback";
|
||||||
var data = EncodeDataParameter(new
|
var data = AppHelpers.EncodeDataParameter(new
|
||||||
{
|
{
|
||||||
siteKey = CaptchaSiteKey,
|
siteKey = CaptchaSiteKey,
|
||||||
locale = i18nService.Culture.TwoLetterISOLanguageName,
|
locale = i18nService.Culture.TwoLetterISOLanguageName,
|
||||||
@ -37,27 +34,19 @@ namespace Bit.App.Pages
|
|||||||
bool cancelled = false;
|
bool cancelled = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
|
var options = new WebAuthenticatorOptions
|
||||||
new Uri(callbackUri));
|
{
|
||||||
|
Url = new Uri(url),
|
||||||
|
CallbackUrl = new Uri(callbackUri),
|
||||||
|
PrefersEphemeralWebBrowserSession = true,
|
||||||
|
};
|
||||||
|
authResult = await WebAuthenticator.AuthenticateAsync(options);
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
await deviceActionService.HideLoadingAsync();
|
await deviceActionService.HideLoadingAsync();
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// WebAuthenticator throws NSErrorException if iOS flow is cancelled - by setting cancelled to true
|
|
||||||
// here we maintain the appearance of a clean cancellation (we don't want to do this across the board
|
|
||||||
// because we still want to present legitimate errors). If/when this is fixed, we can remove this
|
|
||||||
// particular catch block (catching taskCanceledException above must remain)
|
|
||||||
// https://github.com/xamarin/Essentials/issues/1240
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
|
||||||
{
|
|
||||||
await deviceActionService.HideLoadingAsync();
|
|
||||||
cancelled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancelled == false && authResult != null &&
|
if (cancelled == false && authResult != null &&
|
||||||
authResult.Properties.TryGetValue("token", out _captchaToken))
|
authResult.Properties.TryGetValue("token", out _captchaToken))
|
||||||
@ -71,18 +60,5 @@ namespace Bit.App.Pages
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string EncodeDataParameter(object obj)
|
|
||||||
{
|
|
||||||
string EncodeMultibyte(Match match)
|
|
||||||
{
|
|
||||||
return Convert.ToChar(Convert.ToUInt32($"0x{match.Groups[1].Value}", 16)).ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
var escaped = Uri.EscapeDataString(JsonConvert.SerializeObject(obj));
|
|
||||||
var multiByteEscaped = Regex.Replace(escaped, "%([0-9A-F]{2})", EncodeMultibyte);
|
|
||||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(multiByteEscaped));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,8 +137,6 @@
|
|||||||
AutomationProperties.Name="{u:I18n File}"
|
AutomationProperties.Name="{u:I18n File}"
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
<VisualStateManager.VisualStateGroups>
|
<VisualStateManager.VisualStateGroups>
|
||||||
<!-- Rider users, if the x:Name values below are red, it's a known issue: -->
|
|
||||||
<!-- https://youtrack.jetbrains.com/issue/RSRP-479388 -->
|
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal">
|
<VisualState x:Name="Normal">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
@ -163,8 +161,6 @@
|
|||||||
AutomationProperties.Name="{u:I18n Text}"
|
AutomationProperties.Name="{u:I18n Text}"
|
||||||
Grid.Column="1">
|
Grid.Column="1">
|
||||||
<VisualStateManager.VisualStateGroups>
|
<VisualStateManager.VisualStateGroups>
|
||||||
<!-- Rider users, if the x:Name values below are red, it's a known issue: -->
|
|
||||||
<!-- https://youtrack.jetbrains.com/issue/RSRP-479388 -->
|
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal">
|
<VisualState x:Name="Normal">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
|
32
src/App/Resources/AppResources.Designer.cs
generated
32
src/App/Resources/AppResources.Designer.cs
generated
@ -1,7 +1,6 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// This code was generated by a tool.
|
||||||
// Runtime Version:4.0.30319.42000
|
|
||||||
//
|
//
|
||||||
// Changes to this file may cause incorrect behavior and will be lost if
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// the code is regenerated.
|
// the code is regenerated.
|
||||||
@ -10,7 +9,6 @@
|
|||||||
|
|
||||||
namespace Bit.App.Resources {
|
namespace Bit.App.Resources {
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
|
|
||||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||||
@ -3562,5 +3560,35 @@ namespace Bit.App.Resources {
|
|||||||
return ResourceManager.GetString("CaptchaFailed", resourceCulture);
|
return ResourceManager.GetString("CaptchaFailed", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string Fido2Title {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Fido2Title", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Fido2Instruction {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Fido2Instruction", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Fido2Desc {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Fido2Desc", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Fido2AuthenticateWebAuthn {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Fido2AuthenticateWebAuthn", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Fido2SomethingWentWrong {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Fido2SomethingWentWrong", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2016,4 +2016,19 @@
|
|||||||
<data name="CaptchaFailed" xml:space="preserve">
|
<data name="CaptchaFailed" xml:space="preserve">
|
||||||
<value>Captcha Failed. Please try again.</value>
|
<value>Captcha Failed. Please try again.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Fido2Title" xml:space="preserve">
|
||||||
|
<value>FIDO2 WebAuthn</value>
|
||||||
|
</data>
|
||||||
|
<data name="Fido2Instruction" xml:space="preserve">
|
||||||
|
<value>To continue, have your FIDO2 WebAuthn enabled security key ready, then follow the instructions after clicking 'Authenticate WebAuthn' on the next screen.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Fido2Desc" xml:space="preserve">
|
||||||
|
<value>Authentication using FIDO2 WebAuthn, you can authenticate using an external security key.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Fido2AuthenticateWebAuthn" xml:space="preserve">
|
||||||
|
<value>Authenticate WebAuthn</value>
|
||||||
|
</data>
|
||||||
|
<data name="Fido2SomethingWentWrong" xml:space="preserve">
|
||||||
|
<value>Something Went Wrong. Try again.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
@ -129,9 +129,9 @@ namespace Bit.App.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SupportsU2f()
|
public bool SupportsFido2()
|
||||||
{
|
{
|
||||||
return false;
|
return _deviceActionService.SupportsFido2();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowToast(string type, string title, string text, Dictionary<string, object> options = null)
|
public void ShowToast(string type, string title, string text, Dictionary<string, object> options = null)
|
||||||
|
@ -8,10 +8,13 @@ using Bit.Core.Models.View;
|
|||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@ -470,5 +473,17 @@ namespace Bit.App.Utilities
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string EncodeDataParameter(object obj)
|
||||||
|
{
|
||||||
|
string EncodeMultibyte(Match match)
|
||||||
|
{
|
||||||
|
return Convert.ToChar(Convert.ToUInt32($"0x{match.Groups[1].Value}", 16)).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaped = Uri.EscapeDataString(JsonConvert.SerializeObject(obj));
|
||||||
|
var multiByteEscaped = Regex.Replace(escaped, "%([0-9A-F]{2})", EncodeMultibyte);
|
||||||
|
return Convert.ToBase64String(Encoding.UTF8.GetBytes(multiByteEscaped));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
|
Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
|
||||||
Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
|
Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
|
||||||
|
|
||||||
TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported);
|
TwoFactorProviderType? GetDefaultTwoFactorProvider(bool fido2Supported);
|
||||||
bool AuthingWithSso();
|
bool AuthingWithSso();
|
||||||
bool AuthingWithPassword();
|
bool AuthingWithPassword();
|
||||||
List<TwoFactorProvider> GetSupportedTwoFactorProviders();
|
List<TwoFactorProvider> GetSupportedTwoFactorProviders();
|
||||||
|
@ -25,7 +25,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<bool> ShowPasswordDialogAsync(string title, string body, Func<string, Task<bool>> validator);
|
Task<bool> ShowPasswordDialogAsync(string title, string body, Func<string, Task<bool>> validator);
|
||||||
void ShowToast(string type, string title, string text, Dictionary<string, object> options = null);
|
void ShowToast(string type, string title, string text, Dictionary<string, object> options = null);
|
||||||
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
|
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
|
||||||
bool SupportsU2f();
|
bool SupportsFido2();
|
||||||
bool SupportsDuo();
|
bool SupportsDuo();
|
||||||
Task<bool> SupportsBiometricAsync();
|
Task<bool> SupportsBiometricAsync();
|
||||||
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null);
|
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null);
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
YubiKey = 3,
|
YubiKey = 3,
|
||||||
U2f = 4,
|
U2f = 4,
|
||||||
Remember = 5,
|
Remember = 5,
|
||||||
OrganizationDuo = 6
|
OrganizationDuo = 6,
|
||||||
|
Fido2WebAuthn = 7,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,9 +76,9 @@ namespace Bit.Core.Services
|
|||||||
Priority = 10,
|
Priority = 10,
|
||||||
Sort = 4
|
Sort = 4
|
||||||
});
|
});
|
||||||
TwoFactorProviders.Add(TwoFactorProviderType.U2f, new TwoFactorProvider
|
TwoFactorProviders.Add(TwoFactorProviderType.Fido2WebAuthn, new TwoFactorProvider
|
||||||
{
|
{
|
||||||
Type = TwoFactorProviderType.U2f,
|
Type = TwoFactorProviderType.Fido2WebAuthn,
|
||||||
Priority = 4,
|
Priority = 4,
|
||||||
Sort = 5,
|
Sort = 5,
|
||||||
Premium = true
|
Premium = true
|
||||||
@ -114,8 +114,8 @@ namespace Bit.Core.Services
|
|||||||
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.Fido2WebAuthn].Name = _i18nService.T("Fido2Title");
|
||||||
TwoFactorProviders[TwoFactorProviderType.U2f].Description = _i18nService.T("U2fDesc");
|
TwoFactorProviders[TwoFactorProviderType.Fido2WebAuthn].Description = _i18nService.T("Fido2Desc");
|
||||||
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");
|
||||||
}
|
}
|
||||||
@ -192,9 +192,10 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
providers.Add(TwoFactorProviders[TwoFactorProviderType.Duo]);
|
providers.Add(TwoFactorProviders[TwoFactorProviderType.Duo]);
|
||||||
}
|
}
|
||||||
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.U2f) && _platformUtilsService.SupportsU2f())
|
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Fido2WebAuthn) &&
|
||||||
|
_platformUtilsService.SupportsFido2())
|
||||||
{
|
{
|
||||||
providers.Add(TwoFactorProviders[TwoFactorProviderType.U2f]);
|
providers.Add(TwoFactorProviders[TwoFactorProviderType.Fido2WebAuthn]);
|
||||||
}
|
}
|
||||||
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Email))
|
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Email))
|
||||||
{
|
{
|
||||||
@ -203,7 +204,7 @@ namespace Bit.Core.Services
|
|||||||
return providers;
|
return providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported)
|
public TwoFactorProviderType? GetDefaultTwoFactorProvider(bool fido2Supported)
|
||||||
{
|
{
|
||||||
if (TwoFactorProvidersData == null)
|
if (TwoFactorProvidersData == null)
|
||||||
{
|
{
|
||||||
@ -223,7 +224,7 @@ namespace Bit.Core.Services
|
|||||||
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.Fido2WebAuthn && !fido2Supported)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,8 @@ namespace Bit.iOS.Core.Services
|
|||||||
|
|
||||||
var loadingIndicator = new UIActivityIndicatorView(new CGRect(10, 5, 50, 50));
|
var loadingIndicator = new UIActivityIndicatorView(new CGRect(10, 5, 50, 50));
|
||||||
loadingIndicator.HidesWhenStopped = true;
|
loadingIndicator.HidesWhenStopped = true;
|
||||||
loadingIndicator.ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray;
|
loadingIndicator.ActivityIndicatorViewStyle = ThemeHelpers.LightTheme ? UIActivityIndicatorViewStyle.Gray :
|
||||||
|
UIActivityIndicatorViewStyle.White;
|
||||||
loadingIndicator.StartAnimating();
|
loadingIndicator.StartAnimating();
|
||||||
|
|
||||||
_progressAlert = UIAlertController.Create(null, text, UIAlertControllerStyle.Alert);
|
_progressAlert = UIAlertController.Create(null, text, UIAlertControllerStyle.Alert);
|
||||||
@ -446,6 +447,27 @@ namespace Bit.iOS.Core.Services
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SupportsFido2()
|
||||||
|
{
|
||||||
|
// FIDO2 WebAuthn supported on 13.3+
|
||||||
|
var versionParts = UIDevice.CurrentDevice.SystemVersion.Split('.');
|
||||||
|
if (versionParts.Length > 0 && int.TryParse(versionParts[0], out var version))
|
||||||
|
{
|
||||||
|
if (version == 13)
|
||||||
|
{
|
||||||
|
if (versionParts.Length > 1 && int.TryParse(versionParts[1], out var minorVersion))
|
||||||
|
{
|
||||||
|
return minorVersion >= 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (version > 13)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
|
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is UIImagePickerController picker)
|
if (sender is UIImagePickerController picker)
|
||||||
|
@ -239,6 +239,16 @@ namespace Bit.iOS
|
|||||||
return base.OpenUrl(app, url, options);
|
return base.OpenUrl(app, url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
|
||||||
|
UIApplicationRestorationHandler completionHandler)
|
||||||
|
{
|
||||||
|
if (Xamarin.Essentials.Platform.ContinueUserActivity(application, userActivity, completionHandler))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return base.ContinueUserActivity(application, userActivity, completionHandler);
|
||||||
|
}
|
||||||
|
|
||||||
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
|
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
|
||||||
{
|
{
|
||||||
_pushHandler?.OnErrorReceived(error);
|
_pushHandler?.OnErrorReceived(error);
|
||||||
|
Loading…
Reference in New Issue
Block a user