1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

[PM-2287][PM-2289][PM-2293] Approval Options (#2608)

* [PM-2293] Add AuthRequestType to PasswordlessLoginPage.

* [PM-2293] Add Actions to ApproveWithDevicePage

* [PM-2293] Change screen text based on AuthRequestType

* [PM-2293] Refactor AuthRequestType enum. Add label. Remove unnecessary actions.

* [PM-2293] Change boolean variable expression.

* [PM-2293] Trust device after admin request login.

* code format

* [PM-2287] Add trust device to master password unlock. Change trust device method. Remove email from SSO login page.

* [PM-2293] Fix state variable get set.

* [PM-2287][PM-2289][PM-2293] Rename method
This commit is contained in:
André Bispo
2023-07-12 19:12:57 +01:00
committed by GitHub
parent 548bd12a8e
commit a5df6c0c65
22 changed files with 261 additions and 29 deletions

View File

@@ -33,7 +33,7 @@ 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 string _email; private string _email;
private string _masterPassword; private string _masterPassword;
private string _pin; private string _pin;
@@ -65,6 +65,7 @@ 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>();
PageTitle = AppResources.VerifyMasterPassword; PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
@@ -454,6 +455,7 @@ namespace Bit.App.Pages
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetKeyAsync(key);
} }
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
await DoContinueAsync(); await DoContinueAsync();
} }

View File

@@ -1,6 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -9,14 +13,22 @@ namespace Bit.App.Pages
{ {
private readonly LoginApproveDeviceViewModel _vm; private readonly LoginApproveDeviceViewModel _vm;
private readonly AppOptions _appOptions;
public LoginApproveDevicePage(AppOptions appOptions = null) public LoginApproveDevicePage(AppOptions appOptions = null)
{ {
InitializeComponent(); InitializeComponent();
_vm = BindingContext as LoginApproveDeviceViewModel; _vm = BindingContext as LoginApproveDeviceViewModel;
_vm.LogInWithMasterPassword = () => StartLogInWithMasterPassword().FireAndForget();
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
_vm.Page = this; _vm.Page = this;
_appOptions = appOptions;
} }
protected override void OnAppearing(){ protected override void OnAppearing()
{
_vm.InitAsync(); _vm.InitAsync();
} }
@@ -27,6 +39,24 @@ namespace Bit.App.Pages
_vm.CloseAction(); _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));
}
} }
} }

View File

@@ -7,9 +7,11 @@ using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
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;
using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -23,29 +25,35 @@ namespace Bit.App.Pages
private string _email; private string _email;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IApiService _apiService; private readonly IApiService _apiService;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
public ICommand ApproveWithMyOtherDeviceCommand { get; } public ICommand ApproveWithMyOtherDeviceCommand { get; }
public ICommand RequestAdminApprovalCommand { get; } public ICommand RequestAdminApprovalCommand { get; }
public ICommand ApproveWithMasterPasswordCommand { get; } public ICommand ApproveWithMasterPasswordCommand { get; }
public ICommand ContinueCommand { 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 Action CloseAction { get; set; }
public LoginApproveDeviceViewModel() public LoginApproveDeviceViewModel()
{ {
_stateService = ServiceContainer.Resolve<IStateService>(); _stateService = ServiceContainer.Resolve<IStateService>();
_apiService = ServiceContainer.Resolve<IApiService>(); _apiService = ServiceContainer.Resolve<IApiService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
PageTitle = AppResources.LoggedIn; PageTitle = AppResources.LoggedIn;
ApproveWithMyOtherDeviceCommand = new AsyncCommand(InitAsync, ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
RequestAdminApprovalCommand = new AsyncCommand(InitAsync, RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
ApproveWithMasterPasswordCommand = new AsyncCommand(InitAsync, ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPassword),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
@@ -97,13 +105,12 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
// Appears if the browser is trusted and shared the key with the app
ContinueEnabled = true;
try try
{ {
Email = await _stateService.GetRememberedEmailAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions(); var decryptOptions = await _stateService.GetAccountDecryptionOptions();
RequestAdminApprovalEnabled = decryptOptions.TrustedDeviceOption.HasAdminApproval; RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
ApproveWithMasterPasswordEnabled = decryptOptions.HasMasterPassword; ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -118,6 +125,15 @@ namespace Bit.App.Pages
{ {
HandleException(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);
} }
} }
} }

View File

@@ -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));
} }

View File

@@ -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

View File

@@ -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());

View File

@@ -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;
@@ -32,6 +33,7 @@ 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;
protected override II18nService i18nService => _i18nService; protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService; protected override IEnvironmentService environmentService => _environmentService;
@@ -44,6 +46,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 +60,7 @@ 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>();
PageTitle = AppResources.LogInWithAnotherDevice; PageTitle = AppResources.LogInWithAnotherDevice;
@@ -77,6 +81,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 +157,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
@@ -154,6 +237,7 @@ namespace Bit.App.Pages
else else
{ {
_syncService.FullSyncAsync(true).FireAndForget(); _syncService.FullSyncAsync(true).FireAndForget();
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
LogInSuccessAction?.Invoke(); LogInSuccessAction?.Invoke();
} }
} }
@@ -168,7 +252,7 @@ namespace Bit.App.Pages
{ {
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading)); await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email); var response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
if (response != null) if (response != null)
{ {
FingerprintPhrase = response.FingerprintPhrase; FingerprintPhrase = response.FingerprintPhrase;

View File

@@ -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>
@@ -6308,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> /// <summary>
/// Looks up a localized string similar to Try again. /// Looks up a localized string similar to Try again.
/// </summary> /// </summary>
@@ -7208,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> /// <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&apos;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&apos;s USB port, then touch its button..
/// </summary> /// </summary>

View File

@@ -2661,6 +2661,18 @@ 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"> <data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value> <value>Logging in as {0}</value>
</data> </data>

View File

@@ -34,7 +34,7 @@ namespace Bit.Core.Abstractions
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id); Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode); Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(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();

View File

@@ -7,5 +7,8 @@ namespace Bit.Core.Abstractions
{ {
Task<SymmetricCryptoKey> GetDeviceKeyAsync(); Task<SymmetricCryptoKey> GetDeviceKeyAsync();
Task<DeviceResponse> TrustDeviceAsync(); Task<DeviceResponse> TrustDeviceAsync();
Task<DeviceResponse> TrustDeviceIfNeededAsync();
Task<bool> GetShouldTrustDeviceAsync();
Task SetShouldTrustDeviceAsync(bool value);
} }
} }

View File

@@ -179,5 +179,7 @@ namespace Bit.Core.Abstractions
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);
} }
} }

View File

@@ -53,6 +53,7 @@
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;

View File

@@ -0,0 +1,11 @@
using System;
namespace Bit.Core.Enums
{
public enum AuthRequestType : byte
{
AuthenticateAndUnlock = 0,
Unlock = 1,
AdminApproval = 2
}
}

View File

@@ -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
}
} }

View File

@@ -599,7 +599,7 @@ namespace Bit.Core.Services
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);
@@ -607,7 +607,7 @@ 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);
if (response != null) if (response != null)

View File

@@ -77,5 +77,27 @@ namespace Bit.Core.Services
var randomBytes = await _cryptoFunctionService.RandomBytesAsync(DEVICE_KEY_SIZE); var randomBytes = await _cryptoFunctionService.RandomBytesAsync(DEVICE_KEY_SIZE);
return new SymmetricCryptoKey(randomBytes); 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;
}
} }
} }

View File

@@ -1298,6 +1298,16 @@ namespace Bit.Core.Services
))?.Profile?.UserDecryptionOptions; ))?.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 ConfigResponse GetConfigs() public ConfigResponse GetConfigs()
{ {
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey); return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);

View File

@@ -88,6 +88,7 @@ namespace Bit.Core.Utilities
cryptoService); cryptoService);
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService); var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
var configService = new ConfigService(apiService, stateService, logger); var configService = new ConfigService(apiService, stateService, logger);
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
Register<IConditionedAwaiterManager>(conditionedRunner); Register<IConditionedAwaiterManager>(conditionedRunner);
Register<ITokenService>("tokenService", tokenService); Register<ITokenService>("tokenService", tokenService);
@@ -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)

View File

@@ -515,7 +515,7 @@ namespace Bit.iOS.Autofill
{ {
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.AuthenticateAndUnlock, 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)

View File

@@ -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
{ {
@@ -536,7 +537,7 @@ namespace Bit.iOS.Extension
{ {
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.AuthenticateAndUnlock, 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)

View File

@@ -350,7 +350,7 @@ namespace Bit.iOS.ShareExtension
private void LaunchLoginWithDevice(string email = null) private void LaunchLoginWithDevice(string email = null)
{ {
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, _appOptions.Value); var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, _appOptions.Value);
SetupAppAndApplyResources(loginWithDevicePage); SetupAppAndApplyResources(loginWithDevicePage);
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
{ {