mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
19 Commits
v2024.5.1
...
feature/pm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2014d7f562 | ||
|
|
8a399235f4 | ||
|
|
ce55750e60 | ||
|
|
a15269bafe | ||
|
|
8a59e17fc9 | ||
|
|
548bd12a8e | ||
|
|
58542fd255 | ||
|
|
fc300f3e3f | ||
|
|
800b4c71de | ||
|
|
7bcf1c377f | ||
|
|
3053eaa036 | ||
|
|
109a84607a | ||
|
|
d2b6c73a75 | ||
|
|
9dc6a725cf | ||
|
|
6268f0776b | ||
|
|
cbbc41be67 | ||
|
|
e164fb9823 | ||
|
|
87866304a6 | ||
|
|
84a82f0876 |
@@ -33,7 +33,7 @@ namespace Bit.App.Pages
|
||||
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
|
||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
private string _email;
|
||||
private string _masterPassword;
|
||||
private string _pin;
|
||||
@@ -65,6 +65,7 @@ namespace Bit.App.Pages
|
||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||
|
||||
PageTitle = AppResources.VerifyMasterPassword;
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
@@ -454,6 +455,11 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
}
|
||||
if (await _deviceTrustCryptoService.GetUserTrustDeviceChoiceForDecryptionAsync())
|
||||
{
|
||||
await _deviceTrustCryptoService.TrustDeviceAsync();
|
||||
await _deviceTrustCryptoService.SetUserTrustDeviceChoiceForDecryptionAsync(false);
|
||||
}
|
||||
await DoContinueAsync();
|
||||
}
|
||||
|
||||
|
||||
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.LogInWithMasterPassword = () => 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
140
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
140
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
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 LogInWithMasterPassword { 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(() => CheckDeviceTrustAndInvoke(LogInWithDeviceAction),
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
RequestAdminApprovalCommand = new AsyncCommand(() => CheckDeviceTrustAndInvoke(RequestAdminApprovalAction),
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
ApproveWithMasterPasswordCommand = new AsyncCommand(() => CheckDeviceTrustAndInvoke(LogInWithMasterPassword),
|
||||
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 != null && decryptOptions.TrustedDeviceOption != null && decryptOptions.TrustedDeviceOption.HasAdminApproval;
|
||||
ApproveWithMasterPasswordEnabled = decryptOptions != null && decryptOptions.HasMasterPassword;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ApproveWithMyOtherDeviceEnabled = await _apiService.GetDevicesExistenceByTypes(DeviceTypeExtensions.GetDesktopAndMobileTypes().ToArray());
|
||||
}
|
||||
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 CheckDeviceTrustAndInvoke(Action action)
|
||||
{
|
||||
await _deviceTrustCryptoService.SetUserTrustDeviceChoiceForDecryptionAsync(RememberThisDevice);
|
||||
await Device.InvokeOnMainThreadAsync(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
@@ -135,7 +136,7 @@ namespace Bit.App.Pages
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,17 +21,17 @@
|
||||
<StackLayout
|
||||
Padding="7, 0, 7, 20">
|
||||
<Label
|
||||
Text="{u:I18n LogInInitiated}"
|
||||
Text="{Binding Tittle}"
|
||||
FontSize="Title"
|
||||
FontAttributes="Bold"
|
||||
Margin="0,14,0,21"
|
||||
AutomationId="LogInInitiatedLabel" />
|
||||
<Label
|
||||
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
|
||||
Text="{Binding SubTittle}"
|
||||
FontSize="Small"
|
||||
Margin="0,0,0,10"/>
|
||||
<Label
|
||||
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
|
||||
Text="{Binding Description}"
|
||||
FontSize="Small"
|
||||
Margin="0,0,0,24"/>
|
||||
<Label
|
||||
@@ -45,6 +45,7 @@
|
||||
AutomationId="FingerprintPhraseValue" />
|
||||
<Label
|
||||
Text="{u:I18n ResendNotification}"
|
||||
IsVisible="{Binding ResendNotificationVisible}"
|
||||
StyleClass="text-md"
|
||||
HorizontalOptions="Start"
|
||||
Margin="0,40,0,0"
|
||||
@@ -58,7 +59,7 @@
|
||||
Orientation="Horizontal"
|
||||
Margin="0,30,0,0">
|
||||
<Label
|
||||
Text="{u:I18n NeedAnotherOption}"
|
||||
Text="{Binding OtherOptions}"
|
||||
FontSize="Small"
|
||||
VerticalTextAlignment="End"/>
|
||||
<Label
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Enums;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
@@ -12,13 +13,14 @@ namespace Bit.App.Pages
|
||||
private LoginPasswordlessRequestViewModel _vm;
|
||||
private readonly AppOptions _appOptions;
|
||||
|
||||
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null)
|
||||
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
_appOptions = appOptions;
|
||||
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.Email = email;
|
||||
_vm.AuthRequestType = authRequestType;
|
||||
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
||||
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -32,6 +33,7 @@ namespace Bit.App.Pages
|
||||
private IPlatformUtilsService _platformUtilsService;
|
||||
private IEnvironmentService _environmentService;
|
||||
private ILogger _logger;
|
||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
|
||||
protected override II18nService i18nService => _i18nService;
|
||||
protected override IEnvironmentService environmentService => _environmentService;
|
||||
@@ -44,6 +46,7 @@ namespace Bit.App.Pages
|
||||
private string _email;
|
||||
private string _requestId;
|
||||
private string _requestAccessCode;
|
||||
private AuthRequestType _authRequestType;
|
||||
// Item1 publicKey, Item2 privateKey
|
||||
private Tuple<byte[], byte[]> _requestKeyPair;
|
||||
|
||||
@@ -57,6 +60,7 @@ namespace Bit.App.Pages
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>();
|
||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||
_logger = ServiceContainer.Resolve<ILogger>();
|
||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||
|
||||
PageTitle = AppResources.LogInWithAnotherDevice;
|
||||
|
||||
@@ -77,6 +81,70 @@ namespace Bit.App.Pages
|
||||
public ICommand CreatePasswordlessLoginCommand { 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
|
||||
{
|
||||
get => _fingerprintPhrase;
|
||||
@@ -89,6 +157,21 @@ namespace Bit.App.Pages
|
||||
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()
|
||||
{
|
||||
try
|
||||
@@ -154,6 +237,11 @@ namespace Bit.App.Pages
|
||||
else
|
||||
{
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
if (await _deviceTrustCryptoService.GetUserTrustDeviceChoiceForDecryptionAsync())
|
||||
{
|
||||
await _deviceTrustCryptoService.TrustDeviceAsync();
|
||||
await _deviceTrustCryptoService.SetUserTrustDeviceChoiceForDecryptionAsync(false);
|
||||
}
|
||||
LogInSuccessAction?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -168,7 +256,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
||||
|
||||
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
|
||||
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
|
||||
if (response != null)
|
||||
{
|
||||
FingerprintPhrase = response.FingerprintPhrase;
|
||||
|
||||
@@ -110,6 +110,11 @@ namespace Bit.App.Pages
|
||||
{
|
||||
RestoreAppOptionsFromCopy();
|
||||
await AppHelpers.ClearPreviousPage();
|
||||
|
||||
// Just for testing the screen
|
||||
Application.Current.MainPage = new NavigationPage(new LoginApproveDevicePage(_appOptions));
|
||||
return;
|
||||
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
|
||||
99
src/App/Resources/AppResources.Designer.cs
generated
99
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>
|
||||
/// Looks up a localized string similar to All.
|
||||
/// </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>
|
||||
/// Looks up a localized string similar to April.
|
||||
/// </summary>
|
||||
@@ -3586,6 +3613,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>
|
||||
/// Looks up a localized string similar to Logged in as {0} on {1}..
|
||||
/// </summary>
|
||||
@@ -3604,6 +3640,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>
|
||||
/// Looks up a localized string similar to Logging in as {0} on {1}.
|
||||
/// </summary>
|
||||
@@ -5192,6 +5237,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>
|
||||
/// Looks up a localized string similar to Remove.
|
||||
/// </summary>
|
||||
@@ -5264,6 +5318,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>
|
||||
/// Looks up a localized string similar to Request one-time password.
|
||||
/// </summary>
|
||||
@@ -6254,6 +6317,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>
|
||||
/// Looks up a localized string similar to Try again.
|
||||
/// </summary>
|
||||
@@ -6263,6 +6335,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>
|
||||
/// Looks up a localized string similar to 20 seconds.
|
||||
/// </summary>
|
||||
@@ -7145,6 +7226,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>
|
||||
/// 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>
|
||||
|
||||
@@ -2631,6 +2631,24 @@ Do you want to switch to this account?</value>
|
||||
<data name="CurrentMasterPassword" xml:space="preserve">
|
||||
<value>Current master password</value>
|
||||
</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">
|
||||
<value>Master password re-prompt help</value>
|
||||
</data>
|
||||
@@ -2643,4 +2661,19 @@ Do you want to switch to this account?</value>
|
||||
<data name="InvalidAPIToken" xml:space="preserve">
|
||||
<value>Invalid API token</value>
|
||||
</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>
|
||||
</root>
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
@@ -92,7 +93,10 @@ namespace Bit.Core.Abstractions
|
||||
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
||||
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
||||
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<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
|
||||
Task<ConfigResponse> GetConfigsAsync();
|
||||
Task<string> GetFastmailAccountIdAsync(string apiKey);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
||||
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 Init();
|
||||
|
||||
13
src/Core/Abstractions/IDeviceTrustCryptoService.cs
Normal file
13
src/Core/Abstractions/IDeviceTrustCryptoService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IDeviceTrustCryptoService
|
||||
{
|
||||
Task<SymmetricCryptoKey> GetDeviceKeyAsync();
|
||||
Task<DeviceResponse> TrustDeviceAsync();
|
||||
Task<bool> GetUserTrustDeviceChoiceForDecryptionAsync();
|
||||
Task SetUserTrustDeviceChoiceForDecryptionAsync(bool value);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,8 @@ namespace Bit.Core.Abstractions
|
||||
Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null);
|
||||
Task<string> GetPrivateKeyEncryptedAsync(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 SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
|
||||
Task<bool?> GetAutofillTileAddedAsync();
|
||||
@@ -172,9 +174,12 @@ namespace Bit.Core.Abstractions
|
||||
Task<string> GetAvatarColorAsync(string userId = null);
|
||||
Task<string> GetPreLoginEmailAsync();
|
||||
Task SetPreLoginEmailAsync(string value);
|
||||
Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null);
|
||||
string GetLocale();
|
||||
void SetLocale(string locale);
|
||||
ConfigResponse GetConfigs();
|
||||
void SetConfigs(ConfigResponse value);
|
||||
Task<bool> GetUserTrustDeviceChoiceForDecryptionAsync();
|
||||
Task SetUserTrustDeviceChoiceForDecryptionAsync(bool value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
public const string AppLocaleKey = "appLocale";
|
||||
public const string ClearSensitiveFields = "clearSensitiveFields";
|
||||
public const string ForceUpdatePassword = "forceUpdatePassword";
|
||||
public const string RememberDeviceTde = "rememberDeviceTde";
|
||||
public const int SelectFileRequestCode = 42;
|
||||
public const int SelectFilePermissionRequestCode = 43;
|
||||
public const int SaveFileRequestCode = 44;
|
||||
@@ -91,6 +92,7 @@
|
||||
public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}";
|
||||
public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{userId}";
|
||||
public static string EncKeyKey(string userId) => $"encKey_{userId}";
|
||||
public static string DeviceKeyKey(string userId) => $"deviceKey_{userId}";
|
||||
public static string KeyHashKey(string userId) => $"keyHash_{userId}";
|
||||
public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}";
|
||||
public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}";
|
||||
|
||||
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
|
||||
{
|
||||
@@ -24,4 +27,24 @@
|
||||
VivaldiExtension = 19,
|
||||
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;
|
||||
AvatarColor = copy.AvatarColor;
|
||||
ForcePasswordResetReason = copy.ForcePasswordResetReason;
|
||||
UserDecryptionOptions = copy.UserDecryptionOptions;
|
||||
}
|
||||
|
||||
public string UserId;
|
||||
@@ -68,6 +69,7 @@ namespace Bit.Core.Models.Domain
|
||||
public bool? EmailVerified;
|
||||
public bool? HasPremiumPersonally;
|
||||
public ForcePasswordResetReason? ForcePasswordResetReason;
|
||||
public AccountDecryptionOptions UserDecryptionOptions;
|
||||
}
|
||||
|
||||
public class AccountTokens
|
||||
|
||||
21
src/Core/Models/Domain/AccountDecryptionOptions.cs
Normal file
21
src/Core/Models/Domain/AccountDecryptionOptions.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
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 class KeyConnectorOption
|
||||
{
|
||||
public bool KeyConnectorUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class PasswordlessCreateLoginRequest
|
||||
@@ -25,10 +27,4 @@ namespace Bit.Core.Models.Request
|
||||
|
||||
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 int 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 string KeyConnectorUrl { get; set; }
|
||||
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
||||
public AccountDecryptionOptions UserDecryptionOptions { get; set; }
|
||||
[JsonIgnore]
|
||||
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
|
||||
}
|
||||
|
||||
@@ -397,12 +397,38 @@ namespace Bit.Core.Services
|
||||
|
||||
#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)
|
||||
{
|
||||
return SendAsync<DeviceTokenRequest, object>(
|
||||
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
|
||||
|
||||
#region Event APIs
|
||||
@@ -576,15 +602,6 @@ namespace Bit.Core.Services
|
||||
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
|
||||
|
||||
#region Configs
|
||||
|
||||
@@ -459,6 +459,7 @@ namespace Bit.Core.Services
|
||||
ForcePasswordResetReason = result.ForcePasswordReset
|
||||
? ForcePasswordResetReason.AdminForcePasswordReset
|
||||
: (ForcePasswordResetReason?)null,
|
||||
UserDecryptionOptions = tokenResponse.UserDecryptionOptions,
|
||||
},
|
||||
new Account.AccountTokens()
|
||||
{
|
||||
@@ -598,7 +599,7 @@ namespace Bit.Core.Services
|
||||
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 keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||
@@ -606,7 +607,7 @@ namespace Bit.Core.Services
|
||||
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
|
||||
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
||||
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);
|
||||
|
||||
if (response != null)
|
||||
|
||||
91
src/Core/Services/DeviceTrustCryptoService.cs
Normal file
91
src/Core/Services/DeviceTrustCryptoService.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
|
||||
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.GetEncKeyAsync();
|
||||
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.EncKey, 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> GetUserTrustDeviceChoiceForDecryptionAsync()
|
||||
{
|
||||
return await _stateService.GetUserTrustDeviceChoiceForDecryptionAsync();
|
||||
}
|
||||
|
||||
public async Task SetUserTrustDeviceChoiceForDecryptionAsync(bool value)
|
||||
{
|
||||
await _stateService.SetUserTrustDeviceChoiceForDecryptionAsync(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,6 +482,17 @@ namespace Bit.Core.Services
|
||||
await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null)
|
||||
{
|
||||
var deviceKeyB64 = await _storageMediatorService.GetAsync<string>(Constants.DeviceKeyKey(userId), true);
|
||||
return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64));
|
||||
}
|
||||
|
||||
public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(userId), value.KeyB64, true);
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
@@ -1280,6 +1291,23 @@ namespace Bit.Core.Services
|
||||
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> GetUserTrustDeviceChoiceForDecryptionAsync()
|
||||
{
|
||||
return await _storageMediatorService.GetAsync<bool>(Constants.RememberDeviceTde);
|
||||
}
|
||||
|
||||
public async Task SetUserTrustDeviceChoiceForDecryptionAsync(bool value)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(Constants.RememberDeviceTde, value);
|
||||
}
|
||||
|
||||
public ConfigResponse GetConfigs()
|
||||
{
|
||||
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace Bit.Core.Utilities
|
||||
cryptoService);
|
||||
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
|
||||
var configService = new ConfigService(apiService, stateService, logger);
|
||||
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
|
||||
|
||||
Register<IConditionedAwaiterManager>(conditionedRunner);
|
||||
Register<ITokenService>("tokenService", tokenService);
|
||||
@@ -114,6 +115,7 @@ namespace Bit.Core.Utilities
|
||||
Register<IUserVerificationService>("userVerificationService", userVerificationService);
|
||||
Register<IUsernameGenerationService>(usernameGenerationService);
|
||||
Register<IConfigService>(configService);
|
||||
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
|
||||
}
|
||||
|
||||
public static void Register<T>(string serviceName, T obj)
|
||||
|
||||
@@ -515,7 +515,7 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
var appOptions = new AppOptions { IosExtension = true };
|
||||
var app = new App.App(appOptions);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, appOptions);
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||
|
||||
@@ -20,6 +20,7 @@ using Bit.App.Pages;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.iOS.Core.Views;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.iOS.Extension
|
||||
{
|
||||
@@ -536,7 +537,7 @@ namespace Bit.iOS.Extension
|
||||
{
|
||||
var appOptions = new AppOptions { IosExtension = true };
|
||||
var app = new App.App(appOptions);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, appOptions);
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||
|
||||
@@ -350,7 +350,7 @@ namespace Bit.iOS.ShareExtension
|
||||
|
||||
private void LaunchLoginWithDevice(string email = null)
|
||||
{
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, _appOptions.Value);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, _appOptions.Value);
|
||||
SetupAppAndApplyResources(loginWithDevicePage);
|
||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user