mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
21 Commits
add-bio-ke
...
feature/pm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c25906206e | ||
|
|
dfc7c55b77 | ||
|
|
080aabfe82 | ||
|
|
c0688c584e | ||
|
|
c09672ff88 | ||
|
|
635b6bc184 | ||
|
|
da7a1964ef | ||
|
|
73b8d8e6b8 | ||
|
|
c61f9f0357 | ||
|
|
b688b85d0f | ||
|
|
a5df6c0c65 | ||
|
|
c2d4fa4429 | ||
|
|
548bd12a8e | ||
|
|
58542fd255 | ||
|
|
800b4c71de | ||
|
|
3053eaa036 | ||
|
|
6268f0776b | ||
|
|
cbbc41be67 | ||
|
|
e164fb9823 | ||
|
|
87866304a6 | ||
|
|
84a82f0876 |
@@ -33,7 +33,8 @@ namespace Bit.App.Pages
|
|||||||
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
private string _email;
|
private string _email;
|
||||||
private string _masterPassword;
|
private string _masterPassword;
|
||||||
private string _pin;
|
private string _pin;
|
||||||
@@ -64,6 +65,8 @@ namespace Bit.App.Pages
|
|||||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||||
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
||||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
_syncService = ServiceContainer.Resolve<ISyncService>();
|
||||||
|
|
||||||
PageTitle = AppResources.VerifyMasterPassword;
|
PageTitle = AppResources.VerifyMasterPassword;
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
@@ -473,11 +476,13 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _cryptoService.SetUserKeyAsync(key);
|
await _cryptoService.SetUserKeyAsync(key);
|
||||||
}
|
}
|
||||||
|
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
|
||||||
await DoContinueAsync();
|
await DoContinueAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DoContinueAsync()
|
private async Task DoContinueAsync()
|
||||||
{
|
{
|
||||||
|
_syncService.FullSyncAsync(false).FireAndForget();
|
||||||
await _stateService.SetBiometricLockedAsync(false);
|
await _stateService.SetBiometricLockedAsync(false);
|
||||||
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
|
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
|
||||||
_messagingService.Send("unlocked");
|
_messagingService.Send("unlocked");
|
||||||
|
|||||||
76
src/App/Pages/Accounts/LoginApproveDevicePage.xaml
Normal file
76
src/App/Pages/Accounts/LoginApproveDevicePage.xaml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?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.LoginApproveDevicePage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
x:DataType="pages:LoginApproveDeviceViewModel"
|
||||||
|
x:Name="_page"
|
||||||
|
Title="{Binding PageTitle}">
|
||||||
|
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:LoginApproveDeviceViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<StackLayout Padding="10, 10">
|
||||||
|
<StackLayout Padding="5, 10" Orientation="Horizontal">
|
||||||
|
<StackLayout HorizontalOptions="FillAndExpand">
|
||||||
|
<Label
|
||||||
|
StyleClass="text-md"
|
||||||
|
Text="{u:I18n RememberThisDevice}"/>
|
||||||
|
<Label
|
||||||
|
StyleClass="box-sub-label"
|
||||||
|
Text="{u:I18n TurnOffUsingPublicDevice}"/>
|
||||||
|
</StackLayout>
|
||||||
|
<Switch
|
||||||
|
Scale="0.8"
|
||||||
|
IsToggled="{Binding RememberThisDevice}"
|
||||||
|
VerticalOptions="Center"/>
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout Margin="0, 20, 0, 0">
|
||||||
|
<Button
|
||||||
|
x:Name="_continue"
|
||||||
|
Text="{u:I18n Continue}"
|
||||||
|
StyleClass="btn-primary"
|
||||||
|
Command="{Binding ContinueCommand}"
|
||||||
|
IsVisible="{Binding ContinueEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_approveWithMyOtherDevice"
|
||||||
|
Text="{u:I18n ApproveWithMyOtherDevice}"
|
||||||
|
StyleClass="btn-primary"
|
||||||
|
Command="{Binding ApproveWithMyOtherDeviceCommand}"
|
||||||
|
IsVisible="{Binding ApproveWithMyOtherDeviceEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_requestAdminApproval"
|
||||||
|
Text="{u:I18n RequestAdminApproval}"
|
||||||
|
StyleClass="box-button-row"
|
||||||
|
Command="{Binding RequestAdminApprovalCommand}"
|
||||||
|
IsVisible="{Binding RequestAdminApprovalEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_approveWithMasterPassword"
|
||||||
|
Text="{u:I18n ApproveWithMasterPassword}"
|
||||||
|
StyleClass="box-button-row"
|
||||||
|
Command="{Binding ApproveWithMasterPasswordCommand}"
|
||||||
|
IsVisible="{Binding ApproveWithMasterPasswordEnabled}"/>
|
||||||
|
<Label
|
||||||
|
Text="{Binding LoggingInAsText}"
|
||||||
|
StyleClass="text-sm"
|
||||||
|
Margin="0,40,0,0"
|
||||||
|
AutomationId="LoggingInAsLabel"
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NotYou}"
|
||||||
|
StyleClass="text-md"
|
||||||
|
HorizontalOptions="Start"
|
||||||
|
TextColor="{DynamicResource HyperlinkColor}"
|
||||||
|
AutomationId="NotYouLabel">
|
||||||
|
<Label.GestureRecognizers>
|
||||||
|
<TapGestureRecognizer Tapped="Cancel_Clicked" />
|
||||||
|
</Label.GestureRecognizers>
|
||||||
|
</Label>
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</pages:BaseContentPage>
|
||||||
|
|
||||||
|
|
||||||
62
src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs
Normal file
62
src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class LoginApproveDevicePage : BaseContentPage
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly LoginApproveDeviceViewModel _vm;
|
||||||
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
|
public LoginApproveDevicePage(AppOptions appOptions = null)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_vm = BindingContext as LoginApproveDeviceViewModel;
|
||||||
|
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPassword().FireAndForget();
|
||||||
|
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
|
||||||
|
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
|
||||||
|
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
|
||||||
|
_vm.Page = this;
|
||||||
|
_appOptions = appOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAppearing()
|
||||||
|
{
|
||||||
|
_vm.InitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
_vm.CloseAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartLogInWithMasterPassword()
|
||||||
|
{
|
||||||
|
var page = new LockPage(_appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartLoginWithDeviceAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RequestAdminApprovalAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
132
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
132
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class LoginApproveDeviceViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
private bool _rememberThisDevice;
|
||||||
|
private bool _approveWithMyOtherDeviceEnabled;
|
||||||
|
private bool _requestAdminApprovalEnabled;
|
||||||
|
private bool _approveWithMasterPasswordEnabled;
|
||||||
|
private bool _continueEnabled;
|
||||||
|
private string _email;
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
|
||||||
|
public ICommand ApproveWithMyOtherDeviceCommand { get; }
|
||||||
|
public ICommand RequestAdminApprovalCommand { get; }
|
||||||
|
public ICommand ApproveWithMasterPasswordCommand { get; }
|
||||||
|
public ICommand ContinueCommand { get; }
|
||||||
|
|
||||||
|
public Action LogInWithMasterPasswordAction { get; set; }
|
||||||
|
public Action LogInWithDeviceAction { get; set; }
|
||||||
|
public Action RequestAdminApprovalAction { get; set; }
|
||||||
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
|
public LoginApproveDeviceViewModel()
|
||||||
|
{
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
|
_apiService = ServiceContainer.Resolve<IApiService>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
|
||||||
|
PageTitle = AppResources.LoggedIn;
|
||||||
|
|
||||||
|
ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
ContinueCommand = new AsyncCommand(InitAsync,
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
|
||||||
|
|
||||||
|
public bool RememberThisDevice
|
||||||
|
{
|
||||||
|
get => _rememberThisDevice;
|
||||||
|
set => SetProperty(ref _rememberThisDevice, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ApproveWithMyOtherDeviceEnabled
|
||||||
|
{
|
||||||
|
get => _approveWithMyOtherDeviceEnabled;
|
||||||
|
set => SetProperty(ref _approveWithMyOtherDeviceEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RequestAdminApprovalEnabled
|
||||||
|
{
|
||||||
|
get => _requestAdminApprovalEnabled;
|
||||||
|
set => SetProperty(ref _requestAdminApprovalEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ApproveWithMasterPasswordEnabled
|
||||||
|
{
|
||||||
|
get => _approveWithMasterPasswordEnabled;
|
||||||
|
set => SetProperty(ref _approveWithMasterPasswordEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContinueEnabled
|
||||||
|
{
|
||||||
|
get => _continueEnabled;
|
||||||
|
set => SetProperty(ref _continueEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Email
|
||||||
|
{
|
||||||
|
get => _email;
|
||||||
|
set => SetProperty(ref _email, value, additionalPropertyNames:
|
||||||
|
new string[] {
|
||||||
|
nameof(LoggingInAsText)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Email = await _stateService.GetRememberedEmailAsync();
|
||||||
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
|
RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
|
||||||
|
ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
|
||||||
|
ApproveWithMyOtherDeviceEnabled = decryptOptions?.TrustedDeviceOption?.HasLoginApprovingDevice ?? false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
HandleException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change this expression to, Appear if the browser is trusted and shared the key with the app
|
||||||
|
ContinueEnabled = !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled && !ApproveWithMyOtherDeviceEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetDeviceTrustAndInvokeAsync(Action action)
|
||||||
|
{
|
||||||
|
await _deviceTrustCryptoService.SetShouldTrustDeviceAsync(RememberThisDevice);
|
||||||
|
await Device.InvokeOnMainThreadAsync(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ using Bit.App.Models;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -135,7 +136,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task StartLoginWithDeviceAsync()
|
private async Task StartLoginWithDeviceAsync()
|
||||||
{
|
{
|
||||||
var page = new LoginPasswordlessRequestPage(_vm.Email, _appOptions);
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,17 +21,17 @@
|
|||||||
<StackLayout
|
<StackLayout
|
||||||
Padding="7, 0, 7, 20">
|
Padding="7, 0, 7, 20">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n LogInInitiated}"
|
Text="{Binding Tittle}"
|
||||||
FontSize="Title"
|
FontSize="Title"
|
||||||
FontAttributes="Bold"
|
FontAttributes="Bold"
|
||||||
Margin="0,14,0,21"
|
Margin="0,14,0,21"
|
||||||
AutomationId="LogInInitiatedLabel" />
|
AutomationId="LogInInitiatedLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
|
Text="{Binding SubTittle}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,10"/>
|
Margin="0,0,0,10"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
|
Text="{Binding Description}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,24"/>
|
Margin="0,0,0,24"/>
|
||||||
<Label
|
<Label
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
AutomationId="FingerprintPhraseValue" />
|
AutomationId="FingerprintPhraseValue" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ResendNotification}"
|
Text="{u:I18n ResendNotification}"
|
||||||
|
IsVisible="{Binding ResendNotificationVisible}"
|
||||||
StyleClass="text-md"
|
StyleClass="text-md"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
Margin="0,40,0,0"
|
Margin="0,40,0,0"
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Margin="0,30,0,0">
|
Margin="0,30,0,0">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n NeedAnotherOption}"
|
Text="{Binding OtherOptions}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
VerticalTextAlignment="End"/>
|
VerticalTextAlignment="End"/>
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -12,13 +13,14 @@ namespace Bit.App.Pages
|
|||||||
private LoginPasswordlessRequestViewModel _vm;
|
private LoginPasswordlessRequestViewModel _vm;
|
||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null)
|
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.Email = email;
|
_vm.Email = email;
|
||||||
|
_vm.AuthRequestType = authRequestType;
|
||||||
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||||
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -12,6 +13,7 @@ using Bit.Core;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -32,6 +34,9 @@ namespace Bit.App.Pages
|
|||||||
private IPlatformUtilsService _platformUtilsService;
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
private IEnvironmentService _environmentService;
|
private IEnvironmentService _environmentService;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
|
|
||||||
protected override II18nService i18nService => _i18nService;
|
protected override II18nService i18nService => _i18nService;
|
||||||
protected override IEnvironmentService environmentService => _environmentService;
|
protected override IEnvironmentService environmentService => _environmentService;
|
||||||
@@ -44,6 +49,7 @@ namespace Bit.App.Pages
|
|||||||
private string _email;
|
private string _email;
|
||||||
private string _requestId;
|
private string _requestId;
|
||||||
private string _requestAccessCode;
|
private string _requestAccessCode;
|
||||||
|
private AuthRequestType _authRequestType;
|
||||||
// Item1 publicKey, Item2 privateKey
|
// Item1 publicKey, Item2 privateKey
|
||||||
private Tuple<byte[], byte[]> _requestKeyPair;
|
private Tuple<byte[], byte[]> _requestKeyPair;
|
||||||
|
|
||||||
@@ -57,6 +63,9 @@ namespace Bit.App.Pages
|
|||||||
_i18nService = ServiceContainer.Resolve<II18nService>();
|
_i18nService = ServiceContainer.Resolve<II18nService>();
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
_logger = ServiceContainer.Resolve<ILogger>();
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
|
||||||
|
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
||||||
|
|
||||||
PageTitle = AppResources.LogInWithAnotherDevice;
|
PageTitle = AppResources.LogInWithAnotherDevice;
|
||||||
|
|
||||||
@@ -77,6 +86,70 @@ namespace Bit.App.Pages
|
|||||||
public ICommand CreatePasswordlessLoginCommand { get; }
|
public ICommand CreatePasswordlessLoginCommand { get; }
|
||||||
public ICommand CloseCommand { get; }
|
public ICommand CloseCommand { get; }
|
||||||
|
|
||||||
|
public string Tittle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.LogInInitiated;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.AdminApprovalRequested;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SubTittle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.ANotificationHasBeenSentToYourDevice;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.YourRequestHasBeenSentToYourAdmin;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.YouWillBeNotifiedOnceApproved;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string OtherOptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.NeedAnotherOption;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.TroubleLoggingIn;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string FingerprintPhrase
|
public string FingerprintPhrase
|
||||||
{
|
{
|
||||||
get => _fingerprintPhrase;
|
get => _fingerprintPhrase;
|
||||||
@@ -89,6 +162,21 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _email, value);
|
set => SetProperty(ref _email, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthRequestType AuthRequestType
|
||||||
|
{
|
||||||
|
get => _authRequestType;
|
||||||
|
set => SetProperty(ref _authRequestType, value, additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(Tittle),
|
||||||
|
nameof(SubTittle),
|
||||||
|
nameof(Description),
|
||||||
|
nameof(OtherOptions),
|
||||||
|
nameof(ResendNotificationVisible)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ResendNotificationVisible => AuthRequestType == AuthRequestType.AuthenticateAndUnlock;
|
||||||
|
|
||||||
public void StartCheckLoginRequestStatus()
|
public void StartCheckLoginRequestStatus()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -119,14 +207,22 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task CheckLoginRequestStatus()
|
private async Task CheckLoginRequestStatus()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_requestAccessCode))
|
if (string.IsNullOrEmpty(_requestId))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _authService.GetPasswordlessLoginResponseAsync(_requestId, _requestAccessCode);
|
PasswordlessLoginResponse response = null;
|
||||||
|
if (await _stateService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.RequestApproved == null || !response.RequestApproved.Value)
|
if (response.RequestApproved == null || !response.RequestApproved.Value)
|
||||||
{
|
{
|
||||||
@@ -138,6 +234,12 @@ namespace Bit.App.Pages
|
|||||||
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
|
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
|
||||||
|
if (authResult == null && await _stateService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await HandleLoginCompleteAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
|
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -153,8 +255,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
await HandleLoginCompleteAsync();
|
||||||
LogInSuccessAction?.Invoke();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -164,22 +265,66 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleLoginCompleteAsync()
|
||||||
|
{
|
||||||
|
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||||
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
|
LogInSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CreatePasswordlessLoginAsync()
|
private async Task CreatePasswordlessLoginAsync()
|
||||||
{
|
{
|
||||||
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
||||||
|
|
||||||
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
|
PasswordlessLoginResponse response = null;
|
||||||
if (response != null)
|
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
||||||
|
if (pendingRequest != null && _authRequestType == AuthRequestType.AdminApproval)
|
||||||
{
|
{
|
||||||
|
response = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
|
||||||
|
if (response == null || (response.IsAnswered && !response.RequestApproved.Value))
|
||||||
|
{
|
||||||
|
// handle pending auth request not valid remove it from state
|
||||||
|
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||||
|
pendingRequest = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Derive pubKey from privKey in state to avoid MITM attacks
|
||||||
|
// Also generate FingerprintPhrase locally for the same reason
|
||||||
|
var derivedPublicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(pendingRequest.PrivateKey);
|
||||||
|
response.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(Email, derivedPublicKey));
|
||||||
|
response.RequestKeyPair = new Tuple<byte[], byte[]>(derivedPublicKey, pendingRequest.PrivateKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
|
||||||
|
}
|
||||||
|
|
||||||
|
await HandlePasswordlessLoginAsync(response, pendingRequest == null && _authRequestType == AuthRequestType.AdminApproval);
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandlePasswordlessLoginAsync(PasswordlessLoginResponse response, bool createPendingAdminRequest)
|
||||||
|
{
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createPendingAdminRequest)
|
||||||
|
{
|
||||||
|
var pendingAuthRequest = new PendingAdminAuthRequest { Id = response.Id, PrivateKey = response.RequestKeyPair.Item2 };
|
||||||
|
await _stateService.SetPendingAdminAuthRequestAsync(pendingAuthRequest);
|
||||||
|
}
|
||||||
|
|
||||||
FingerprintPhrase = response.FingerprintPhrase;
|
FingerprintPhrase = response.FingerprintPhrase;
|
||||||
_requestId = response.Id;
|
_requestId = response.Id;
|
||||||
_requestAccessCode = response.RequestAccessCode;
|
_requestAccessCode = response.RequestAccessCode;
|
||||||
_requestKeyPair = response.RequestKeyPair;
|
_requestKeyPair = response.RequestKeyPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleException(Exception ex)
|
private void HandleException(Exception ex)
|
||||||
{
|
{
|
||||||
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ namespace Bit.App.Pages
|
|||||||
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
|
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction =
|
_vm.UpdateTempPasswordAction =
|
||||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
|
_vm.StartDeviceApprovalOptionsAction =
|
||||||
|
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
||||||
_vm.CloseAction = async () =>
|
_vm.CloseAction = async () =>
|
||||||
{
|
{
|
||||||
await Navigation.PopModalAsync();
|
await Navigation.PopModalAsync();
|
||||||
@@ -106,10 +108,17 @@ namespace Bit.App.Pages
|
|||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartDeviceApprovalOptionsAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginApproveDevicePage();
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SsoAuthSuccessAsync()
|
private async Task SsoAuthSuccessAsync()
|
||||||
{
|
{
|
||||||
RestoreAppOptionsFromCopy();
|
RestoreAppOptionsFromCopy();
|
||||||
await AppHelpers.ClearPreviousPage();
|
await AppHelpers.ClearPreviousPage();
|
||||||
|
|
||||||
if (await _vaultTimeoutService.IsLockedAsync())
|
if (await _vaultTimeoutService.IsLockedAsync())
|
||||||
{
|
{
|
||||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
@@ -29,6 +30,8 @@ namespace Bit.App.Pages
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
|
|
||||||
private string _orgIdentifier;
|
private string _orgIdentifier;
|
||||||
|
|
||||||
@@ -45,7 +48,8 @@ namespace Bit.App.Pages
|
|||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
||||||
|
|
||||||
PageTitle = AppResources.Bitwarden;
|
PageTitle = AppResources.Bitwarden;
|
||||||
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
|
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
|
||||||
@@ -61,6 +65,7 @@ namespace Bit.App.Pages
|
|||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action StartSetPasswordAction { get; set; }
|
public Action StartSetPasswordAction { get; set; }
|
||||||
public Action SsoAuthSuccessAction { get; set; }
|
public Action SsoAuthSuccessAction { get; set; }
|
||||||
|
public Action StartDeviceApprovalOptionsAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
|
|
||||||
@@ -197,6 +202,7 @@ namespace Bit.App.Pages
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
|
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
|
||||||
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
|
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
@@ -212,9 +218,31 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
UpdateTempPasswordAction?.Invoke();
|
UpdateTempPasswordAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
else if (decryptOptions?.TrustedDeviceOption != null)
|
||||||
|
{
|
||||||
|
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||||
|
if (!decryptOptions.HasMasterPassword &&
|
||||||
|
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
||||||
|
{
|
||||||
|
StartSetPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (response.ForcePasswordReset)
|
||||||
|
{
|
||||||
|
UpdateTempPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
|
||||||
|
{
|
||||||
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
|
SsoAuthSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
StartDeviceApprovalOptionsAction?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
SsoAuthSuccessAction?.Invoke();
|
SsoAuthSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ namespace Bit.App.Pages
|
|||||||
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
|
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction =
|
_vm.UpdateTempPasswordAction =
|
||||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
|
_vm.StartDeviceApprovalOptionsAction =
|
||||||
|
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
||||||
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
||||||
DuoWebView = _duoWebView;
|
DuoWebView = _duoWebView;
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
@@ -180,6 +182,12 @@ namespace Bit.App.Pages
|
|||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartDeviceApprovalOptionsAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginApproveDevicePage();
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TwoFactorAuthSuccessAsync()
|
private async Task TwoFactorAuthSuccessAsync()
|
||||||
{
|
{
|
||||||
if (_authingWithSso)
|
if (_authingWithSso)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -33,7 +34,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly IAppIdService _appIdService;
|
private readonly IAppIdService _appIdService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
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";
|
||||||
@@ -55,6 +56,7 @@ namespace Bit.App.Pages
|
|||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>();
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
|
||||||
PageTitle = AppResources.TwoStepLogin;
|
PageTitle = AppResources.TwoStepLogin;
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
@@ -118,6 +120,7 @@ namespace Bit.App.Pages
|
|||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public ICommand MoreCommand { get; }
|
public ICommand MoreCommand { get; }
|
||||||
public Action TwoFactorAuthSuccessAction { get; set; }
|
public Action TwoFactorAuthSuccessAction { get; set; }
|
||||||
|
public Action StartDeviceApprovalOptionsAction { get; set; }
|
||||||
public Action StartSetPasswordAction { get; set; }
|
public Action StartSetPasswordAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
@@ -315,6 +318,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
_messagingService.Send("listenYubiKeyOTP", false);
|
_messagingService.Send("listenYubiKeyOTP", false);
|
||||||
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
||||||
|
|
||||||
@@ -326,6 +330,27 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
UpdateTempPasswordAction?.Invoke();
|
UpdateTempPasswordAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
else if (decryptOptions?.TrustedDeviceOption != null)
|
||||||
|
{
|
||||||
|
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||||
|
if (!decryptOptions.HasMasterPassword &&
|
||||||
|
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
||||||
|
{
|
||||||
|
StartSetPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (result.ForcePasswordReset)
|
||||||
|
{
|
||||||
|
UpdateTempPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
|
||||||
|
{
|
||||||
|
TwoFactorAuthSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartDeviceApprovalOptionsAction?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TwoFactorAuthSuccessAction?.Invoke();
|
TwoFactorAuthSuccessAction?.Invoke();
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ namespace Bit.App.Pages
|
|||||||
private bool _reportLoggingEnabled;
|
private bool _reportLoggingEnabled;
|
||||||
private bool _approvePasswordlessLoginRequests;
|
private bool _approvePasswordlessLoginRequests;
|
||||||
private bool _shouldConnectToWatch;
|
private bool _shouldConnectToWatch;
|
||||||
|
private bool _hasMasterPassword;
|
||||||
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
|
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
|
||||||
new List<KeyValuePair<string, int?>>
|
new List<KeyValuePair<string, int?>>
|
||||||
{
|
{
|
||||||
@@ -100,12 +101,17 @@ namespace Bit.App.Pages
|
|||||||
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _biometric || _pin;
|
||||||
|
|
||||||
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
||||||
|
|
||||||
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
|
var decryptionOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
|
// set has true for backwards compatibility
|
||||||
|
_hasMasterPassword = decryptionOptions?.HasMasterPassword ?? true;
|
||||||
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
||||||
var lastSync = await _syncService.GetLastSyncAsync();
|
var lastSync = await _syncService.GetLastSyncAsync();
|
||||||
if (lastSync != null)
|
if (lastSync != null)
|
||||||
@@ -124,7 +130,13 @@ namespace Bit.App.Pages
|
|||||||
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
||||||
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
|
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
|
||||||
|
|
||||||
var action = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
|
var savedVaultTimeoutAction = await _vaultTimeoutService.GetVaultTimeoutAction();
|
||||||
|
var action = savedVaultTimeoutAction ?? VaultTimeoutAction.Lock;
|
||||||
|
if (!_hasMasterPassword && savedVaultTimeoutAction == null)
|
||||||
|
{
|
||||||
|
action = VaultTimeoutAction.Logout;
|
||||||
|
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
|
||||||
|
}
|
||||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
|
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
|
||||||
|
|
||||||
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
|
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
|
||||||
@@ -387,8 +399,11 @@ namespace Bit.App.Pages
|
|||||||
// do nothing if we have a policy set
|
// do nothing if we have a policy set
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var options = _vaultTimeoutActionOptions.Select(o =>
|
|
||||||
o.Key == _vaultTimeoutActionDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
|
var options = IsVaultTimeoutActionLockAllowed
|
||||||
|
? _vaultTimeoutActionOptions.Select(o => CreateSelectableOption(o.Key, _vaultTimeoutActionDisplayValue == o.Key)).ToArray()
|
||||||
|
: _vaultTimeoutActionOptions.Where(o => o.Value == VaultTimeoutAction.Logout).Select(v => ToSelectedOption(v.Key)).ToArray();
|
||||||
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
|
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
|
||||||
AppResources.Cancel, null, options);
|
AppResources.Cancel, null, options);
|
||||||
if (selection == null || selection == AppResources.Cancel)
|
if (selection == null || selection == AppResources.Cancel)
|
||||||
@@ -489,6 +504,7 @@ namespace Bit.App.Pages
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _stateService.SetBiometricUnlockAsync(null);
|
await _stateService.SetBiometricUnlockAsync(null);
|
||||||
|
await UpdateVaultTimeoutActionIfNeededAsync();
|
||||||
}
|
}
|
||||||
await _stateService.SetBiometricLockedAsync(false);
|
await _stateService.SetBiometricLockedAsync(false);
|
||||||
await _cryptoService.ToggleKeysAsync();
|
await _cryptoService.ToggleKeysAsync();
|
||||||
@@ -835,9 +851,11 @@ namespace Bit.App.Pages
|
|||||||
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
|
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
private string CreateSelectableOption(string option, bool selected) => selected ? ToSelectedOption(option) : option;
|
||||||
|
|
||||||
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == ToSelectedOption(compareTo);
|
||||||
|
|
||||||
|
private string ToSelectedOption(string option) => $"✓ {option}";
|
||||||
|
|
||||||
public async Task SetScreenCaptureAllowedAsync()
|
public async Task SetScreenCaptureAllowedAsync()
|
||||||
{
|
{
|
||||||
@@ -869,5 +887,17 @@ namespace Bit.App.Pages
|
|||||||
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
|
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateVaultTimeoutActionIfNeededAsync()
|
||||||
|
{
|
||||||
|
if (IsVaultTimeoutActionLockAllowed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.First(o => o.Value == VaultTimeoutAction.Logout).Key;
|
||||||
|
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
|
||||||
|
_deviceActionService.Toast(AppResources.VaultTimeoutActionChangedToLogOut);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
108
src/App/Resources/AppResources.Designer.cs
generated
108
src/App/Resources/AppResources.Designer.cs
generated
@@ -418,6 +418,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Admin approval requested.
|
||||||
|
/// </summary>
|
||||||
|
public static string AdminApprovalRequested {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AdminApprovalRequested", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to All.
|
/// Looks up a localized string similar to All.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -562,6 +571,24 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Approve with master password.
|
||||||
|
/// </summary>
|
||||||
|
public static string ApproveWithMasterPassword {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ApproveWithMasterPassword", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Approve with my other device.
|
||||||
|
/// </summary>
|
||||||
|
public static string ApproveWithMyOtherDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ApproveWithMyOtherDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to April.
|
/// Looks up a localized string similar to April.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3658,6 +3685,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Logged in!.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoggedIn {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoggedIn", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Logged in as {0} on {1}..
|
/// Looks up a localized string similar to Logged in as {0} on {1}..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3676,6 +3712,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Logging in as {0}.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoggingInAsX {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoggingInAsX", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Logging in as {0} on {1}.
|
/// Looks up a localized string similar to Logging in as {0} on {1}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -5273,6 +5318,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Remember this device.
|
||||||
|
/// </summary>
|
||||||
|
public static string RememberThisDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RememberThisDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Remove.
|
/// Looks up a localized string similar to Remove.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -5345,6 +5399,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Request admin approval.
|
||||||
|
/// </summary>
|
||||||
|
public static string RequestAdminApproval {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RequestAdminApproval", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Request one-time password.
|
/// Looks up a localized string similar to Request one-time password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6353,6 +6416,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Trouble logging in?.
|
||||||
|
/// </summary>
|
||||||
|
public static string TroubleLoggingIn {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TroubleLoggingIn", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Try again.
|
/// Looks up a localized string similar to Try again.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6362,6 +6434,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Turn off using a public device.
|
||||||
|
/// </summary>
|
||||||
|
public static string TurnOffUsingPublicDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TurnOffUsingPublicDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to 20 seconds.
|
/// Looks up a localized string similar to 20 seconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6893,6 +6974,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Vault timeout action changed to log out.
|
||||||
|
/// </summary>
|
||||||
|
public static string VaultTimeoutActionChangedToLogOut {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("VaultTimeoutActionChangedToLogOut", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
|
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -7262,6 +7352,24 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Your request has been sent to your admin..
|
||||||
|
/// </summary>
|
||||||
|
public static string YourRequestHasBeenSentToYourAdmin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("YourRequestHasBeenSentToYourAdmin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You will be notified once approved. .
|
||||||
|
/// </summary>
|
||||||
|
public static string YouWillBeNotifiedOnceApproved {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("YouWillBeNotifiedOnceApproved", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device's USB port, then touch its button..
|
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device's USB port, then touch its button..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2628,6 +2628,24 @@ Do you want to switch to this account?</value>
|
|||||||
<data name="CurrentMasterPassword" xml:space="preserve">
|
<data name="CurrentMasterPassword" xml:space="preserve">
|
||||||
<value>Current master password</value>
|
<value>Current master password</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LoggedIn" xml:space="preserve">
|
||||||
|
<value>Logged in!</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
|
||||||
|
<value>Approve with my other device</value>
|
||||||
|
</data>
|
||||||
|
<data name="RequestAdminApproval" xml:space="preserve">
|
||||||
|
<value>Request admin approval</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApproveWithMasterPassword" xml:space="preserve">
|
||||||
|
<value>Approve with master password</value>
|
||||||
|
</data>
|
||||||
|
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
|
||||||
|
<value>Turn off using a public device</value>
|
||||||
|
</data>
|
||||||
|
<data name="RememberThisDevice" xml:space="preserve">
|
||||||
|
<value>Remember this device</value>
|
||||||
|
</data>
|
||||||
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
|
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
|
||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2640,6 +2658,24 @@ Do you want to switch to this account?</value>
|
|||||||
<data name="InvalidAPIToken" xml:space="preserve">
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
<value>Invalid API token</value>
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AdminApprovalRequested" xml:space="preserve">
|
||||||
|
<value>Admin approval requested</value>
|
||||||
|
</data>
|
||||||
|
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
|
||||||
|
<value>Your request has been sent to your admin.</value>
|
||||||
|
</data>
|
||||||
|
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
|
||||||
|
<value>You will be notified once approved. </value>
|
||||||
|
</data>
|
||||||
|
<data name="TroubleLoggingIn" xml:space="preserve">
|
||||||
|
<value>Trouble logging in?</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoggingInAsX" xml:space="preserve">
|
||||||
|
<value>Logging in as {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
|
||||||
|
<value>Vault timeout action changed to log out</value>
|
||||||
|
</data>
|
||||||
<data name="BlockAutoFill" xml:space="preserve">
|
<data name="BlockAutoFill" xml:space="preserve">
|
||||||
<value>Block auto-fill</value>
|
<value>Block auto-fill</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Net;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Models.Response;
|
using Bit.Core.Models.Response;
|
||||||
@@ -90,9 +91,12 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
|
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
|
||||||
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
|
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
|
||||||
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
||||||
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType);
|
||||||
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
||||||
|
Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier);
|
||||||
|
Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest deviceRequest);
|
||||||
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
|
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
|
||||||
|
Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
|
||||||
Task<ConfigResponse> GetConfigsAsync();
|
Task<ConfigResponse> GetConfigsAsync();
|
||||||
Task<string> GetFastmailAccountIdAsync(string apiKey);
|
Task<string> GetFastmailAccountIdAsync(string apiKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,12 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
|
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
|
||||||
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();
|
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();
|
||||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
||||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
/// <summary>
|
||||||
|
/// Gets a passwordless login request by <paramref name="id"/> and <paramref name="accessCode"/>. No authentication required.
|
||||||
|
/// </summary>
|
||||||
|
Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode);
|
||||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||||
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email);
|
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);
|
||||||
|
|
||||||
void LogOut(Action callback);
|
void LogOut(Action callback);
|
||||||
void Init();
|
void Init();
|
||||||
|
|||||||
16
src/Core/Abstractions/IDeviceTrustCryptoService.cs
Normal file
16
src/Core/Abstractions/IDeviceTrustCryptoService.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IDeviceTrustCryptoService
|
||||||
|
{
|
||||||
|
Task<SymmetricCryptoKey> GetDeviceKeyAsync();
|
||||||
|
Task<DeviceResponse> TrustDeviceAsync();
|
||||||
|
Task<DeviceResponse> TrustDeviceIfNeededAsync();
|
||||||
|
Task<bool> GetShouldTrustDeviceAsync();
|
||||||
|
Task SetShouldTrustDeviceAsync(bool value);
|
||||||
|
Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey);
|
||||||
|
Task<bool> IsDeviceTrustedAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,8 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null);
|
Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null);
|
||||||
Task<string> GetPrivateKeyEncryptedAsync(string userId = null);
|
Task<string> GetPrivateKeyEncryptedAsync(string userId = null);
|
||||||
Task SetPrivateKeyEncryptedAsync(string value, string userId = null);
|
Task SetPrivateKeyEncryptedAsync(string value, string userId = null);
|
||||||
|
Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null);
|
||||||
|
Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null);
|
||||||
Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null);
|
Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null);
|
||||||
Task SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
|
Task SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
|
||||||
Task<bool?> GetAutofillTileAddedAsync();
|
Task<bool?> GetAutofillTileAddedAsync();
|
||||||
@@ -180,10 +182,15 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<string> GetAvatarColorAsync(string userId = null);
|
Task<string> GetAvatarColorAsync(string userId = null);
|
||||||
Task<string> GetPreLoginEmailAsync();
|
Task<string> GetPreLoginEmailAsync();
|
||||||
Task SetPreLoginEmailAsync(string value);
|
Task SetPreLoginEmailAsync(string value);
|
||||||
|
Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null);
|
||||||
|
Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null);
|
||||||
|
Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null);
|
||||||
string GetLocale();
|
string GetLocale();
|
||||||
void SetLocale(string locale);
|
void SetLocale(string locale);
|
||||||
ConfigResponse GetConfigs();
|
ConfigResponse GetConfigs();
|
||||||
void SetConfigs(ConfigResponse value);
|
void SetConfigs(ConfigResponse value);
|
||||||
|
Task<bool> GetShouldTrustDeviceAsync();
|
||||||
|
Task SetShouldTrustDeviceAsync(bool value);
|
||||||
[Obsolete("Use GetUserKeyMasterKey instead")]
|
[Obsolete("Use GetUserKeyMasterKey instead")]
|
||||||
Task<string> GetEncKeyEncryptedAsync(string userId = null);
|
Task<string> GetEncKeyEncryptedAsync(string userId = null);
|
||||||
[Obsolete("Use SetUserKeyMasterKey instead")]
|
[Obsolete("Use SetUserKeyMasterKey instead")]
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ namespace Bit.Core
|
|||||||
public const string AppLocaleKey = "appLocale";
|
public const string AppLocaleKey = "appLocale";
|
||||||
public const string ClearSensitiveFields = "clearSensitiveFields";
|
public const string ClearSensitiveFields = "clearSensitiveFields";
|
||||||
public const string ForceUpdatePassword = "forceUpdatePassword";
|
public const string ForceUpdatePassword = "forceUpdatePassword";
|
||||||
|
public const string ShouldTrustDevice = "shouldTrustDevice";
|
||||||
public const int SelectFileRequestCode = 42;
|
public const int SelectFileRequestCode = 42;
|
||||||
public const int SelectFilePermissionRequestCode = 43;
|
public const int SelectFilePermissionRequestCode = 43;
|
||||||
public const int SaveFileRequestCode = 44;
|
public const int SaveFileRequestCode = 44;
|
||||||
@@ -92,6 +93,7 @@ namespace Bit.Core
|
|||||||
public static string PoliciesKey(string userId) => $"policies_{userId}";
|
public static string PoliciesKey(string userId) => $"policies_{userId}";
|
||||||
public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}";
|
public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}";
|
||||||
public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{userId}";
|
public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{userId}";
|
||||||
|
public static string DeviceKeyKey(string userId) => $"deviceKey_{userId}";
|
||||||
public static string KeyHashKey(string userId) => $"keyHash_{userId}";
|
public static string KeyHashKey(string userId) => $"keyHash_{userId}";
|
||||||
public static string UserKeyPinKey(string userId) => $"userKeyPin_{userId}";
|
public static string UserKeyPinKey(string userId) => $"userKeyPin_{userId}";
|
||||||
public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}";
|
public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}";
|
||||||
@@ -122,6 +124,7 @@ namespace Bit.Core
|
|||||||
public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}";
|
public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}";
|
||||||
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
|
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
|
||||||
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
|
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
|
||||||
|
public static string PendingAdminAuthRequest(string userId) => $"pendingAdminAuthRequest_{userId}";
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
public static string KeyKey(string userId) => $"key_{userId}";
|
public static string KeyKey(string userId) => $"key_{userId}";
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
|
|||||||
11
src/Core/Enums/AuthRequestType.cs
Normal file
11
src/Core/Enums/AuthRequestType.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum AuthRequestType : byte
|
||||||
|
{
|
||||||
|
AuthenticateAndUnlock = 0,
|
||||||
|
Unlock = 1,
|
||||||
|
AdminApproval = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
namespace Bit.Core.Enums
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Bit.Core.Enums
|
||||||
{
|
{
|
||||||
public enum DeviceType : byte
|
public enum DeviceType : byte
|
||||||
{
|
{
|
||||||
@@ -24,4 +27,24 @@
|
|||||||
VivaldiExtension = 19,
|
VivaldiExtension = 19,
|
||||||
SafariExtension = 20
|
SafariExtension = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DeviceTypeExtensions
|
||||||
|
{
|
||||||
|
public static List<DeviceType> GetMobileTypes() => new List<DeviceType>
|
||||||
|
{
|
||||||
|
DeviceType.Android,
|
||||||
|
DeviceType.AndroidAmazon,
|
||||||
|
DeviceType.iOS
|
||||||
|
};
|
||||||
|
|
||||||
|
public static List<DeviceType> GetDesktopTypes() => new List<DeviceType>
|
||||||
|
{
|
||||||
|
DeviceType.WindowsDesktop,
|
||||||
|
DeviceType.MacOsDesktop,
|
||||||
|
DeviceType.LinuxDesktop,
|
||||||
|
DeviceType.UWP,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static List<DeviceType> GetDesktopAndMobileTypes() => GetMobileTypes().Concat(GetDesktopTypes()).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
HasPremiumPersonally = copy.HasPremiumPersonally;
|
HasPremiumPersonally = copy.HasPremiumPersonally;
|
||||||
AvatarColor = copy.AvatarColor;
|
AvatarColor = copy.AvatarColor;
|
||||||
ForcePasswordResetReason = copy.ForcePasswordResetReason;
|
ForcePasswordResetReason = copy.ForcePasswordResetReason;
|
||||||
|
UserDecryptionOptions = copy.UserDecryptionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string UserId;
|
public string UserId;
|
||||||
@@ -68,6 +69,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
public bool? EmailVerified;
|
public bool? EmailVerified;
|
||||||
public bool? HasPremiumPersonally;
|
public bool? HasPremiumPersonally;
|
||||||
public ForcePasswordResetReason? ForcePasswordResetReason;
|
public ForcePasswordResetReason? ForcePasswordResetReason;
|
||||||
|
public AccountDecryptionOptions UserDecryptionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AccountTokens
|
public class AccountTokens
|
||||||
|
|||||||
25
src/Core/Models/Domain/AccountDecryptionOptions.cs
Normal file
25
src/Core/Models/Domain/AccountDecryptionOptions.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bit.Core.Models.Domain
|
||||||
|
{
|
||||||
|
public class AccountDecryptionOptions
|
||||||
|
{
|
||||||
|
public bool HasMasterPassword { get; set; }
|
||||||
|
public TrustedDeviceOption TrustedDeviceOption { get; set; }
|
||||||
|
public KeyConnectorOption KeyConnectorOption { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrustedDeviceOption
|
||||||
|
{
|
||||||
|
public bool HasAdminApproval { get; set; }
|
||||||
|
public bool HasLoginApprovingDevice { get; set; }
|
||||||
|
public bool HasManageResetPasswordPermission { get; set; }
|
||||||
|
public string EncryptedPrivateKey { get; set; }
|
||||||
|
public string EncryptedUserKey { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KeyConnectorOption
|
||||||
|
{
|
||||||
|
public string KeyConnectorUrl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
10
src/Core/Models/Domain/PendingAdminAuthRequest.cs
Normal file
10
src/Core/Models/Domain/PendingAdminAuthRequest.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bit.Core.Models.Domain
|
||||||
|
{
|
||||||
|
public class PendingAdminAuthRequest
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public byte[] PrivateKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Request
|
namespace Bit.Core.Models.Request
|
||||||
{
|
{
|
||||||
public class PasswordlessCreateLoginRequest
|
public class PasswordlessCreateLoginRequest
|
||||||
@@ -25,10 +27,4 @@ namespace Bit.Core.Models.Request
|
|||||||
|
|
||||||
public string FingerprintPhrase { get; set; }
|
public string FingerprintPhrase { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AuthRequestType : byte
|
|
||||||
{
|
|
||||||
AuthenticateAndUnlock = 0,
|
|
||||||
Unlock = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/Core/Models/Request/TrustedDeviceKeysRequest.cs
Normal file
10
src/Core/Models/Request/TrustedDeviceKeysRequest.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
namespace Bit.Core.Models.Request
|
||||||
|
{
|
||||||
|
public class TrustedDeviceKeysRequest
|
||||||
|
{
|
||||||
|
public string EncryptedUserKey { get; set; }
|
||||||
|
public string EncryptedPublicKey { get; set; }
|
||||||
|
public string EncryptedPrivateKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Core/Models/Response/DeviceResponse.cs
Normal file
13
src/Core/Models/Response/DeviceResponse.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
public class DeviceResponse
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public DeviceType Type { get; set; }
|
||||||
|
public string CreationDate { get; set; }
|
||||||
|
public string EncryptedUserKey { get; set; }
|
||||||
|
public string EncryptedPublicKey { get; set; }
|
||||||
|
public string EncryptedPrivateKey { get; set; }
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ namespace Bit.Core.Models.Response
|
|||||||
public bool ForcePasswordReset { get; set; }
|
public bool ForcePasswordReset { get; set; }
|
||||||
public string KeyConnectorUrl { get; set; }
|
public string KeyConnectorUrl { get; set; }
|
||||||
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
||||||
|
public AccountDecryptionOptions UserDecryptionOptions { get; set; }
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
|
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,12 +397,38 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
#region Device APIs
|
#region Device APIs
|
||||||
|
|
||||||
|
|
||||||
|
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
|
||||||
|
{
|
||||||
|
return SendAsync<object, bool>(HttpMethod.Get, "/devices/knowndevice", null, false, true, (message) =>
|
||||||
|
{
|
||||||
|
message.Headers.Add("X-Device-Identifier", deviceIdentifier);
|
||||||
|
message.Headers.Add("X-Request-Email", CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes(email)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request)
|
public Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request)
|
||||||
{
|
{
|
||||||
return SendAsync<DeviceTokenRequest, object>(
|
return SendAsync<DeviceTokenRequest, object>(
|
||||||
HttpMethod.Put, $"/devices/identifier/{identifier}/token", request, true, false);
|
HttpMethod.Put, $"/devices/identifier/{identifier}/token", request, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes)
|
||||||
|
{
|
||||||
|
return SendAsync<DeviceType[], bool>(
|
||||||
|
HttpMethod.Post, "/devices/exist-by-types", deviceTypes, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier)
|
||||||
|
{
|
||||||
|
return SendAsync<object, DeviceResponse>(HttpMethod.Get, $"/devices/identifier/{deviceIdentifier}", null, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest trustedDeviceKeysRequest)
|
||||||
|
{
|
||||||
|
return SendAsync<TrustedDeviceKeysRequest, DeviceResponse>(HttpMethod.Put, $"/devices/{deviceIdentifier}/keys", trustedDeviceKeysRequest, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Event APIs
|
#region Event APIs
|
||||||
@@ -565,9 +591,9 @@ namespace Bit.Core.Services
|
|||||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}/response?code={accessCode}", null, false, true);
|
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}/response?code={accessCode}", null, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest)
|
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType)
|
||||||
{
|
{
|
||||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, $"/auth-requests", passwordlessCreateLoginRequest, false, true);
|
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, authRequestType == AuthRequestType.AdminApproval ? "/auth-requests/admin-request" : "/auth-requests", passwordlessCreateLoginRequest, authRequestType == AuthRequestType.AdminApproval, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string encKey, string encMasterPasswordHash, string deviceIdentifier, bool requestApproved)
|
public Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string encKey, string encMasterPasswordHash, string deviceIdentifier, bool requestApproved)
|
||||||
@@ -576,15 +602,6 @@ namespace Bit.Core.Services
|
|||||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Put, $"/auth-requests/{id}", request, true, true);
|
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Put, $"/auth-requests/{id}", request, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
|
|
||||||
{
|
|
||||||
return SendAsync<object, bool>(HttpMethod.Get, "/devices/knowndevice", null, false, true, (message) =>
|
|
||||||
{
|
|
||||||
message.Headers.Add("X-Device-Identifier", deviceIdentifier);
|
|
||||||
message.Headers.Add("X-Request-Email", CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes(email)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Configs
|
#region Configs
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace Bit.Core.Services
|
|||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
|
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
private readonly bool _setCryptoKeys;
|
private readonly bool _setCryptoKeys;
|
||||||
|
|
||||||
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
|
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
|
||||||
@@ -50,6 +51,7 @@ namespace Bit.Core.Services
|
|||||||
IKeyConnectorService keyConnectorService,
|
IKeyConnectorService keyConnectorService,
|
||||||
IPasswordGenerationService passwordGenerationService,
|
IPasswordGenerationService passwordGenerationService,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
|
IDeviceTrustCryptoService deviceTrustCryptoService,
|
||||||
bool setCryptoKeys = true)
|
bool setCryptoKeys = true)
|
||||||
{
|
{
|
||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
@@ -64,6 +66,7 @@ namespace Bit.Core.Services
|
|||||||
_keyConnectorService = keyConnectorService;
|
_keyConnectorService = keyConnectorService;
|
||||||
_passwordGenerationService = passwordGenerationService;
|
_passwordGenerationService = passwordGenerationService;
|
||||||
_policyService = policyService;
|
_policyService = policyService;
|
||||||
|
_deviceTrustCryptoService = deviceTrustCryptoService;
|
||||||
_setCryptoKeys = setCryptoKeys;
|
_setCryptoKeys = setCryptoKeys;
|
||||||
|
|
||||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||||
@@ -195,11 +198,34 @@ namespace Bit.Core.Services
|
|||||||
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
|
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered)
|
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash)
|
||||||
{
|
{
|
||||||
var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey);
|
var decryptedKey = await _cryptoService.RsaDecryptAsync(encryptedAuthRequestKey, decryptionKey);
|
||||||
var decPasswordHash = await _cryptoService.RsaDecryptAsync(localHashedPasswordCiphered, decryptionKey);
|
|
||||||
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new MasterKey(decKey), null, null,
|
// On SSO flow user is already AuthN
|
||||||
|
if (await _stateService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(masterKeyHash))
|
||||||
|
{
|
||||||
|
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(new MasterKey(decryptedKey));
|
||||||
|
await _cryptoService.SetUserKeyAsync(userKey);
|
||||||
|
}
|
||||||
|
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(masterKeyHash) && decryptionKey != null)
|
||||||
|
{
|
||||||
|
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var decKeyHash = await _cryptoService.RsaDecryptAsync(masterKeyHash, decryptionKey);
|
||||||
|
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decKeyHash), null, null, null, new MasterKey(decryptedKey), null, null,
|
||||||
null, null, authRequestId: authRequestId);
|
null, null, authRequestId: authRequestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,6 +485,7 @@ namespace Bit.Core.Services
|
|||||||
ForcePasswordResetReason = result.ForcePasswordReset
|
ForcePasswordResetReason = result.ForcePasswordReset
|
||||||
? ForcePasswordResetReason.AdminForcePasswordReset
|
? ForcePasswordResetReason.AdminForcePasswordReset
|
||||||
: (ForcePasswordResetReason?)null,
|
: (ForcePasswordResetReason?)null,
|
||||||
|
UserDecryptionOptions = tokenResponse.UserDecryptionOptions,
|
||||||
},
|
},
|
||||||
new Account.AccountTokens()
|
new Account.AccountTokens()
|
||||||
{
|
{
|
||||||
@@ -470,27 +497,46 @@ namespace Bit.Core.Services
|
|||||||
_messagingService.Send("accountAdded");
|
_messagingService.Send("accountAdded");
|
||||||
if (_setCryptoKeys)
|
if (_setCryptoKeys)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (localHashedPassword != null)
|
if (localHashedPassword != null)
|
||||||
{
|
{
|
||||||
await _cryptoService.SetPasswordHashAsync(localHashedPassword);
|
await _cryptoService.SetPasswordHashAsync(localHashedPassword);
|
||||||
|
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||||
|
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||||
|
await _cryptoService.SetUserKeyAsync(userKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code == null || tokenResponse.Key != null)
|
if (code == null || tokenResponse.Key != null)
|
||||||
{
|
{
|
||||||
if (tokenResponse.KeyConnectorUrl != null)
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
{
|
|
||||||
await _keyConnectorService.GetAndSetKey(tokenResponse.KeyConnectorUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
||||||
|
|
||||||
|
if (decryptOptions?.TrustedDeviceOption != null)
|
||||||
|
{
|
||||||
|
var key = await _deviceTrustCryptoService.DecryptUserKeyWithDeviceKeyAsync(decryptOptions.TrustedDeviceOption.EncryptedPrivateKey, decryptOptions.TrustedDeviceOption.EncryptedUserKey);
|
||||||
|
if (key != null)
|
||||||
|
{
|
||||||
|
await _cryptoService.SetUserKeyAsync(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
|
||||||
|
{
|
||||||
|
|
||||||
|
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
||||||
if (masterKey != null)
|
if (masterKey != null)
|
||||||
{
|
{
|
||||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||||
await _cryptoService.SetUserKeyAsync(userKey);
|
await _cryptoService.SetUserKeyAsync(userKey);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login with Device
|
||||||
|
if (masterKey != null && !string.IsNullOrEmpty(authRequestId))
|
||||||
|
{
|
||||||
|
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||||
|
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||||
|
await _cryptoService.SetUserKeyAsync(userKey);
|
||||||
|
}
|
||||||
|
|
||||||
// User doesn't have a key pair yet (old account), let's generate one for them.
|
// User doesn't have a key pair yet (old account), let's generate one for them.
|
||||||
if (tokenResponse.PrivateKey == null)
|
if (tokenResponse.PrivateKey == null)
|
||||||
@@ -544,7 +590,6 @@ namespace Bit.Core.Services
|
|||||||
);
|
);
|
||||||
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
|
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_authedUserId = _tokenService.GetUserId();
|
_authedUserId = _tokenService.GetUserId();
|
||||||
@@ -581,14 +626,14 @@ namespace Bit.Core.Services
|
|||||||
var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
|
var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
|
||||||
return await PopulateFingerprintPhrasesAsync(activeRequests);
|
return await PopulateFingerprintPhrasesAsync(activeRequests);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
||||||
{
|
{
|
||||||
var response = await _apiService.GetAuthRequestAsync(id);
|
var response = await _apiService.GetAuthRequestAsync(id);
|
||||||
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode)
|
/// <inheritdoc />
|
||||||
|
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode)
|
||||||
{
|
{
|
||||||
return await _apiService.GetAuthResponseAsync(id, accessCode);
|
return await _apiService.GetAuthResponseAsync(id, accessCode);
|
||||||
}
|
}
|
||||||
@@ -598,13 +643,18 @@ namespace Bit.Core.Services
|
|||||||
var publicKey = CoreHelpers.Base64UrlDecode(pubKey);
|
var publicKey = CoreHelpers.Base64UrlDecode(pubKey);
|
||||||
var masterKey = await _cryptoService.GetMasterKeyAsync();
|
var masterKey = await _cryptoService.GetMasterKeyAsync();
|
||||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey);
|
var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey);
|
||||||
var encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(await _stateService.GetKeyHashAsync()), publicKey);
|
var keyHash = await _stateService.GetKeyHashAsync();
|
||||||
|
EncString encryptedMasterPassword = null;
|
||||||
|
if (!string.IsNullOrEmpty(keyHash))
|
||||||
|
{
|
||||||
|
encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(keyHash), publicKey);
|
||||||
|
}
|
||||||
var deviceId = await _appIdService.GetAppIdAsync();
|
var deviceId = await _appIdService.GetAppIdAsync();
|
||||||
var response = await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword.EncryptedString, deviceId, requestApproved);
|
var response = await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword?.EncryptedString, deviceId, requestApproved);
|
||||||
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email)
|
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType)
|
||||||
{
|
{
|
||||||
var deviceId = await _appIdService.GetAppIdAsync();
|
var deviceId = await _appIdService.GetAppIdAsync();
|
||||||
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||||
@@ -612,8 +662,8 @@ namespace Bit.Core.Services
|
|||||||
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
|
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
|
||||||
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
||||||
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
|
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
|
||||||
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, AuthRequestType.AuthenticateAndUnlock, fingerprintPhrase);
|
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, authRequestType, fingerprintPhrase);
|
||||||
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest);
|
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest, authRequestType);
|
||||||
|
|
||||||
if (response != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
|
|||||||
129
src/Core/Services/DeviceTrustCryptoService.cs
Normal file
129
src/Core/Services/DeviceTrustCryptoService.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services
|
||||||
|
{
|
||||||
|
public class DeviceTrustCryptoService : IDeviceTrustCryptoService
|
||||||
|
{
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private readonly IAppIdService _appIdService;
|
||||||
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
|
||||||
|
private const int DEVICE_KEY_SIZE = 64;
|
||||||
|
|
||||||
|
public DeviceTrustCryptoService(
|
||||||
|
IApiService apiService,
|
||||||
|
IAppIdService appIdService,
|
||||||
|
ICryptoFunctionService cryptoFunctionService,
|
||||||
|
ICryptoService cryptoService,
|
||||||
|
IStateService stateService)
|
||||||
|
{
|
||||||
|
_apiService = apiService;
|
||||||
|
_appIdService = appIdService;
|
||||||
|
_cryptoFunctionService = cryptoFunctionService;
|
||||||
|
_cryptoService = cryptoService;
|
||||||
|
_stateService = stateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync()
|
||||||
|
{
|
||||||
|
return await _stateService.GetDeviceKeyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetDeviceKeyAsync(SymmetricCryptoKey deviceKey)
|
||||||
|
{
|
||||||
|
await _stateService.SetDeviceKeyAsync(deviceKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DeviceResponse> TrustDeviceAsync()
|
||||||
|
{
|
||||||
|
// Attempt to get user key
|
||||||
|
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||||
|
if (userKey == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Generate deviceKey
|
||||||
|
var deviceKey = await MakeDeviceKeyAsync();
|
||||||
|
|
||||||
|
// Generate asymmetric RSA key pair: devicePrivateKey, devicePublicKey
|
||||||
|
var (devicePublicKey, devicePrivateKey) = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||||
|
|
||||||
|
// Send encrypted keys to server
|
||||||
|
var deviceIdentifier = await _appIdService.GetAppIdAsync();
|
||||||
|
var deviceRequest = new TrustedDeviceKeysRequest
|
||||||
|
{
|
||||||
|
EncryptedUserKey = (await _cryptoService.RsaEncryptAsync(userKey.Key, devicePublicKey)).EncryptedString,
|
||||||
|
EncryptedPublicKey = (await _cryptoService.EncryptAsync(devicePublicKey, userKey)).EncryptedString,
|
||||||
|
EncryptedPrivateKey = (await _cryptoService.EncryptAsync(devicePrivateKey, deviceKey)).EncryptedString,
|
||||||
|
};
|
||||||
|
|
||||||
|
var deviceResponse = await _apiService.UpdateTrustedDeviceKeysAsync(deviceIdentifier, deviceRequest);
|
||||||
|
|
||||||
|
// Store device key if successful
|
||||||
|
await SetDeviceKeyAsync(deviceKey);
|
||||||
|
return deviceResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<SymmetricCryptoKey> MakeDeviceKeyAsync()
|
||||||
|
{
|
||||||
|
// Create 512-bit device key
|
||||||
|
var randomBytes = await _cryptoFunctionService.RandomBytesAsync(DEVICE_KEY_SIZE);
|
||||||
|
return new SymmetricCryptoKey(randomBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> GetShouldTrustDeviceAsync()
|
||||||
|
{
|
||||||
|
return await _stateService.GetShouldTrustDeviceAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetShouldTrustDeviceAsync(bool value)
|
||||||
|
{
|
||||||
|
await _stateService.SetShouldTrustDeviceAsync(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DeviceResponse> TrustDeviceIfNeededAsync()
|
||||||
|
{
|
||||||
|
if (!await GetShouldTrustDeviceAsync())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await TrustDeviceAsync();
|
||||||
|
await SetShouldTrustDeviceAsync(false);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsDeviceTrustedAsync()
|
||||||
|
{
|
||||||
|
var existingDeviceKey = await GetDeviceKeyAsync();
|
||||||
|
return existingDeviceKey != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey)
|
||||||
|
{
|
||||||
|
var existingDeviceKey = await GetDeviceKeyAsync();
|
||||||
|
if (existingDeviceKey == null)
|
||||||
|
{
|
||||||
|
// User doesn't have a device key anymore so device is untrusted
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to decrypt encryptedDevicePrivateKey with device key
|
||||||
|
var devicePrivateKeyBytes = await _cryptoService.DecryptToBytesAsync(
|
||||||
|
new EncString(encryptedDevicePrivateKey),
|
||||||
|
existingDeviceKey
|
||||||
|
);
|
||||||
|
|
||||||
|
// Attempt to decrypt encryptedUserDataKey with devicePrivateKey
|
||||||
|
var userKeyBytes = await _cryptoService.RsaDecryptAsync(encryptedUserKey, devicePrivateKeyBytes);
|
||||||
|
return new UserKey(userKeyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -336,12 +336,16 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<string> GetUserKeyMasterKeyAsync(string userId = null)
|
public async Task<string> GetUserKeyMasterKeyAsync(string userId = null)
|
||||||
{
|
{
|
||||||
return await _storageMediatorService.GetAsync<string>(Constants.UserKeyKey(userId), false);
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultInMemoryOptionsAsync());
|
||||||
|
return await _storageMediatorService.GetAsync<string>(Constants.UserKeyKey(reconciledOptions.UserId), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetUserKeyMasterKeyAsync(string value, string userId = null)
|
public async Task SetUserKeyMasterKeyAsync(string value, string userId = null)
|
||||||
{
|
{
|
||||||
await _storageMediatorService.SaveAsync(Constants.UserKeyKey(userId), value, false);
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultInMemoryOptionsAsync());
|
||||||
|
await _storageMediatorService.SaveAsync(Constants.UserKeyKey(reconciledOptions.UserId), value, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CanAccessPremiumAsync(string userId = null)
|
public async Task<bool> CanAccessPremiumAsync(string userId = null)
|
||||||
@@ -513,6 +517,25 @@ namespace Bit.Core.Services
|
|||||||
await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null)
|
||||||
|
{
|
||||||
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
var deviceKeyB64 = await _storageMediatorService.GetAsync<string>(Constants.DeviceKeyKey(reconciledOptions.UserId), true);
|
||||||
|
if (string.IsNullOrEmpty(deviceKeyB64))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null)
|
||||||
|
{
|
||||||
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(reconciledOptions.UserId), value.KeyB64, true);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)
|
public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)
|
||||||
{
|
{
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
@@ -1311,6 +1334,37 @@ namespace Bit.Core.Services
|
|||||||
await SetValueAsync(Constants.PreLoginEmailKey, value, options);
|
await SetValueAsync(Constants.PreLoginEmailKey, value, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null)
|
||||||
|
{
|
||||||
|
return (await GetAccountAsync(
|
||||||
|
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
|
||||||
|
))?.Profile?.UserDecryptionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> GetShouldTrustDeviceAsync()
|
||||||
|
{
|
||||||
|
return await _storageMediatorService.GetAsync<bool>(Constants.ShouldTrustDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetShouldTrustDeviceAsync(bool value)
|
||||||
|
{
|
||||||
|
await _storageMediatorService.SaveAsync(Constants.ShouldTrustDevice, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null)
|
||||||
|
{
|
||||||
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
return await _storageMediatorService.GetAsync<PendingAdminAuthRequest>(Constants.PendingAdminAuthRequest(reconciledOptions.UserId), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null)
|
||||||
|
{
|
||||||
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
await _storageMediatorService.SaveAsync(Constants.PendingAdminAuthRequest(reconciledOptions.UserId), value, true);
|
||||||
|
}
|
||||||
|
|
||||||
public ConfigResponse GetConfigs()
|
public ConfigResponse GetConfigs()
|
||||||
{
|
{
|
||||||
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);
|
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);
|
||||||
|
|||||||
@@ -77,9 +77,10 @@ namespace Bit.Core.Utilities
|
|||||||
});
|
});
|
||||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
|
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
|
||||||
var totpService = new TotpService(cryptoFunctionService);
|
var totpService = new TotpService(cryptoFunctionService);
|
||||||
|
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
|
||||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
||||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||||
keyConnectorService, passwordGenerationService, policyService);
|
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService);
|
||||||
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
||||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||||
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
|
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
|
||||||
@@ -114,6 +115,7 @@ namespace Bit.Core.Utilities
|
|||||||
Register<IUserVerificationService>("userVerificationService", userVerificationService);
|
Register<IUserVerificationService>("userVerificationService", userVerificationService);
|
||||||
Register<IUsernameGenerationService>(usernameGenerationService);
|
Register<IUsernameGenerationService>(usernameGenerationService);
|
||||||
Register<IConfigService>(configService);
|
Register<IConfigService>(configService);
|
||||||
|
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Register<T>(string serviceName, T obj)
|
public static void Register<T>(string serviceName, T obj)
|
||||||
|
|||||||
@@ -498,7 +498,7 @@ namespace Bit.iOS.Autofill
|
|||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email));
|
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
|
||||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
}
|
}
|
||||||
@@ -511,11 +511,11 @@ namespace Bit.iOS.Autofill
|
|||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchLoginWithDevice(string email = null)
|
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null)
|
||||||
{
|
{
|
||||||
var appOptions = new AppOptions { IosExtension = true };
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
var app = new App.App(appOptions);
|
var app = new App.App(appOptions);
|
||||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions);
|
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
ThemeManager.SetTheme(app.Resources);
|
||||||
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
||||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||||
@@ -545,6 +545,7 @@ namespace Bit.iOS.Autofill
|
|||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
||||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||||
|
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
}
|
}
|
||||||
@@ -567,6 +568,7 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||||
|
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||||
if (authingWithSso)
|
if (authingWithSso)
|
||||||
{
|
{
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
@@ -621,6 +623,26 @@ namespace Bit.iOS.Autofill
|
|||||||
PresentViewController(updateTempPasswordController, true, null);
|
PresentViewController(updateTempPasswordController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LaunchDeviceApprovalOptionsFlow()
|
||||||
|
{
|
||||||
|
var loginApproveDevicePage = new LoginApproveDevicePage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
|
||||||
|
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
|
||||||
|
{
|
||||||
|
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
|
||||||
|
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
|
||||||
|
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(loginApproveDevicePage);
|
||||||
|
var loginApproveDeviceController = navigationPage.CreateViewController();
|
||||||
|
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginApproveDeviceController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
|
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
|
||||||
public Task UpdateThemeAsync() => Task.CompletedTask;
|
public Task UpdateThemeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ using Bit.App.Pages;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.iOS.Core.Views;
|
using Bit.iOS.Core.Views;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.iOS.Extension
|
namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
@@ -519,7 +520,7 @@ namespace Bit.iOS.Extension
|
|||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email));
|
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
|
||||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
}
|
}
|
||||||
@@ -532,11 +533,11 @@ namespace Bit.iOS.Extension
|
|||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchLoginWithDevice(string email = null)
|
private void LaunchLoginWithDevice(AuthRequestType authRequestType,string email = null)
|
||||||
{
|
{
|
||||||
var appOptions = new AppOptions { IosExtension = true };
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
var app = new App.App(appOptions);
|
var app = new App.App(appOptions);
|
||||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions);
|
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
ThemeManager.SetTheme(app.Resources);
|
||||||
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
||||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||||
@@ -566,6 +567,7 @@ namespace Bit.iOS.Extension
|
|||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
||||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||||
|
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
}
|
}
|
||||||
@@ -588,6 +590,7 @@ namespace Bit.iOS.Extension
|
|||||||
{
|
{
|
||||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||||
|
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||||
if (authingWithSso)
|
if (authingWithSso)
|
||||||
{
|
{
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
@@ -641,5 +644,25 @@ namespace Bit.iOS.Extension
|
|||||||
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
PresentViewController(updateTempPasswordController, true, null);
|
PresentViewController(updateTempPasswordController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LaunchDeviceApprovalOptionsFlow()
|
||||||
|
{
|
||||||
|
var loginApproveDevicePage = new LoginApproveDevicePage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
|
||||||
|
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
|
||||||
|
{
|
||||||
|
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
|
||||||
|
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
|
||||||
|
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(loginApproveDevicePage);
|
||||||
|
var loginApproveDeviceController = navigationPage.CreateViewController();
|
||||||
|
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginApproveDeviceController, true, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
|
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
|
||||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||||
vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(email));
|
vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
|
||||||
vm.LogInSuccessAction = () => { DismissLockAndContinue(); };
|
vm.LogInSuccessAction = () => { DismissLockAndContinue(); };
|
||||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
@@ -348,9 +348,9 @@ namespace Bit.iOS.ShareExtension
|
|||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchLoginWithDevice(string email = null)
|
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null)
|
||||||
{
|
{
|
||||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, _appOptions.Value);
|
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, _appOptions.Value);
|
||||||
SetupAppAndApplyResources(loginWithDevicePage);
|
SetupAppAndApplyResources(loginWithDevicePage);
|
||||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||||
{
|
{
|
||||||
@@ -373,6 +373,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
|
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
|
||||||
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
|
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
@@ -389,6 +390,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
{
|
{
|
||||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||||
|
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||||
if (authingWithSso)
|
if (authingWithSso)
|
||||||
{
|
{
|
||||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||||
@@ -427,6 +429,26 @@ namespace Bit.iOS.ShareExtension
|
|||||||
NavigateToPage(updateTempPasswordPage);
|
NavigateToPage(updateTempPasswordPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LaunchDeviceApprovalOptionsFlow()
|
||||||
|
{
|
||||||
|
var loginApproveDevicePage = new LoginApproveDevicePage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
|
||||||
|
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
|
||||||
|
{
|
||||||
|
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
|
||||||
|
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
|
||||||
|
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
|
||||||
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(loginApproveDevicePage);
|
||||||
|
var loginApproveDeviceController = navigationPage.CreateViewController();
|
||||||
|
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginApproveDeviceController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
||||||
{
|
{
|
||||||
if (ExtNavigationController?.ViewControllers?.Any() ?? false)
|
if (ExtNavigationController?.ViewControllers?.Any() ?? false)
|
||||||
|
|||||||
Reference in New Issue
Block a user