diff --git a/src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs b/src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs index 1509a92f0..849b53b20 100644 --- a/src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs +++ b/src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs @@ -50,13 +50,13 @@ namespace Bit.App.Pages private async Task StartLoginWithDeviceAsync() { - var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions); + var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions, true); await Navigation.PushModalAsync(new NavigationPage(page)); } private async Task RequestAdminApprovalAsync() { - var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions); + var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions, true); await Navigation.PushModalAsync(new NavigationPage(page)); } } diff --git a/src/App/Pages/Accounts/LoginPasswordlessRequestPage.xaml.cs b/src/App/Pages/Accounts/LoginPasswordlessRequestPage.xaml.cs index b1c575080..07c2d3f6d 100644 --- a/src/App/Pages/Accounts/LoginPasswordlessRequestPage.xaml.cs +++ b/src/App/Pages/Accounts/LoginPasswordlessRequestPage.xaml.cs @@ -13,7 +13,7 @@ namespace Bit.App.Pages private LoginPasswordlessRequestViewModel _vm; private readonly AppOptions _appOptions; - public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null) + public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null, bool authingWithSso = false) { InitializeComponent(); _appOptions = appOptions; @@ -21,6 +21,7 @@ namespace Bit.App.Pages _vm.Page = this; _vm.Email = email; _vm.AuthRequestType = authRequestType; + _vm.AuthingWithSso = authingWithSso; _vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync()); _vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync()); _vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); diff --git a/src/App/Pages/Accounts/LoginPasswordlessRequestViewModel.cs b/src/App/Pages/Accounts/LoginPasswordlessRequestViewModel.cs index f9fd48de0..313ae21fc 100644 --- a/src/App/Pages/Accounts/LoginPasswordlessRequestViewModel.cs +++ b/src/App/Pages/Accounts/LoginPasswordlessRequestViewModel.cs @@ -80,6 +80,7 @@ namespace Bit.App.Pages public Action LogInSuccessAction { get; set; } public Action UpdateTempPasswordAction { get; set; } public Action CloseAction { get; set; } + public bool AuthingWithSso { get; set; } public ICommand CreatePasswordlessLoginCommand { get; } public ICommand CloseCommand { get; } @@ -233,7 +234,7 @@ namespace Bit.App.Pages try { PasswordlessLoginResponse response = null; - if (await _stateService.IsAuthenticatedAsync()) + if (AuthingWithSso) { response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId); } @@ -242,14 +243,14 @@ namespace Bit.App.Pages response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode); } - if (response.RequestApproved == null || !response.RequestApproved.Value) + if (response?.RequestApproved != true) { return; } StopCheckLoginRequestStatus(); - var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash); + var authResult = await _authService.LogInPasswordlessAsync(AuthingWithSso, Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash); await AppHelpers.ResetInvalidUnlockAttemptsAsync(); if (authResult == null && await _stateService.IsAuthenticatedAsync()) diff --git a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs index 9fd5a1049..ce07d99b4 100644 --- a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs @@ -240,9 +240,9 @@ namespace Bit.App.Pages else if (pendingRequest != null) { var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id); - if (authRequest != null && authRequest.RequestApproved != null && authRequest.RequestApproved.Value) + if (authRequest?.RequestApproved == true) { - var authResult = await _authService.LogInPasswordlessAsync(await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash); + var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash); if (authResult == null && await _stateService.IsAuthenticatedAsync()) { await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync( diff --git a/src/Core/Abstractions/IAuthService.cs b/src/Core/Abstractions/IAuthService.cs index c27fc3e0c..88c537f4d 100644 --- a/src/Core/Abstractions/IAuthService.cs +++ b/src/Core/Abstractions/IAuthService.cs @@ -27,7 +27,7 @@ namespace Bit.Core.Abstractions Task LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId); Task LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null); Task LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null); - Task LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered); + Task LogInPasswordlessAsync(bool authingWithSso, string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered); Task> GetPasswordlessLoginRequestsAsync(); Task> GetActivePasswordlessLoginRequestsAsync(); diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index 0cb847690..f70d80a60 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -200,12 +200,13 @@ namespace Bit.Core.Services return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy); } - public async Task LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash) + public async Task LogInPasswordlessAsync(bool authingWithSso, string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash) { var decryptedKey = await _cryptoService.RsaDecryptAsync(encryptedAuthRequestKey, decryptionKey); - // On SSO flow user is already AuthN - if (await _stateService.IsAuthenticatedAsync()) + // If the user is already authenticated, we can just set the key + // Note: We can't check for the existance of an access token here because the active user id may not be null + if (authingWithSso) { if (string.IsNullOrEmpty(masterKeyHash)) { @@ -222,10 +223,13 @@ namespace Bit.Core.Services return null; } + // The approval device may not have a master key hash if it authenticated with a passwordless method if (string.IsNullOrEmpty(masterKeyHash) && decryptionKey != null) { + var authResult = await LogInHelperAsync(email, accessCode, null, null, null, null, null, null, null, null, null, authRequestId: authRequestId); + // Only set the user key after the login helper so we have a user id await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey)); - return null; + return authResult; } var decKeyHash = await _cryptoService.RsaDecryptAsync(masterKeyHash, decryptionKey); diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 34d87018d..0495a7096 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -63,7 +63,7 @@ namespace Bit.Core.Services public async Task GetUserKeyWithLegacySupportAsync(string userId = null) { - var userKey = await GetUserKeyAsync(); + var userKey = await GetUserKeyAsync(userId); if (userKey != null) { return userKey; @@ -71,7 +71,7 @@ namespace Bit.Core.Services // Legacy support: encryption used to be done with the master key (derived from master password). // Users who have not migrated will have a null user key and must use the master key instead. - return new UserKey((await GetMasterKeyAsync()).Key); + return new UserKey((await GetMasterKeyAsync(userId)).Key); } public async Task HasUserKeyAsync(string userId = null) diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index 0f00cbe53..be425dd99 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -59,6 +59,13 @@ namespace Bit.Core.Services public async Task IsLockedAsync(string userId = null) { + // When checking if we need to lock inactive account, we don't want to + // set the key, so we only check if the account has an auto unlock key + if (userId != null && await _stateService.GetActiveUserIdAsync() != userId) + { + return await _cryptoService.HasAutoUnlockKeyAsync(userId); + } + var biometricSet = await IsBiometricLockSetAsync(userId); if (biometricSet && await _stateService.GetBiometricLockedAsync(userId)) { @@ -67,14 +74,11 @@ namespace Bit.Core.Services if (!await _cryptoService.HasUserKeyAsync(userId)) { - if (await _cryptoService.HasAutoUnlockKeyAsync(userId)) - { - await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId)); - } - else + if (!await _cryptoService.HasAutoUnlockKeyAsync(userId)) { return true; } + await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), userId); } // Check again to verify auto key was set diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index e214835ea..d9ce6edb6 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -511,11 +511,11 @@ namespace Bit.iOS.Autofill LogoutIfAuthed(); } - private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null) + private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false) { var appOptions = new AppOptions { IosExtension = true }; var app = new App.App(appOptions); - var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions); + var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions, authingWithSso); ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesTo(loginWithDevicePage); if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) @@ -632,8 +632,8 @@ namespace Bit.iOS.Autofill 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.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email, true)); + vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true)); } var navigationPage = new NavigationPage(loginApproveDevicePage); diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs index 8882e1adb..19e8ffb47 100644 --- a/src/iOS.Extension/LoadingViewController.cs +++ b/src/iOS.Extension/LoadingViewController.cs @@ -533,11 +533,11 @@ namespace Bit.iOS.Extension LogoutIfAuthed(); } - private void LaunchLoginWithDevice(AuthRequestType authRequestType,string email = null) + private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false) { var appOptions = new AppOptions { IosExtension = true }; var app = new App.App(appOptions); - var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions); + var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions, authingWithSso); ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesTo(loginWithDevicePage); if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) @@ -654,8 +654,8 @@ namespace Bit.iOS.Extension 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.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email, true)); + vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true)); } var navigationPage = new NavigationPage(loginApproveDevicePage); diff --git a/src/iOS.ShareExtension/LoadingViewController.cs b/src/iOS.ShareExtension/LoadingViewController.cs index 4442bc215..ef049e7ef 100644 --- a/src/iOS.ShareExtension/LoadingViewController.cs +++ b/src/iOS.ShareExtension/LoadingViewController.cs @@ -348,9 +348,9 @@ namespace Bit.iOS.ShareExtension LogoutIfAuthed(); } - private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null) + private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false) { - var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, _appOptions.Value); + var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, _appOptions.Value, authingWithSso); SetupAppAndApplyResources(loginWithDevicePage); if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) { @@ -438,8 +438,8 @@ namespace Bit.iOS.ShareExtension 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.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email, true)); + vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true)); } var navigationPage = new NavigationPage(loginApproveDevicePage);