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

Compare commits

..

89 Commits

Author SHA1 Message Date
Jacob Fink
a9368be106 Merge commit 'c34d1da6' into temp/tde-master-merge 2023-08-13 15:36:25 -04:00
André Bispo
b2df06a7a1 [PM-3379] Fix key rotation on trusted device. (#2680) 2023-08-11 19:10:11 +01:00
André Bispo
3f4892fcc8 [PM-3381] Fix TDE login 2FA flow (#2678)
* [PM-3381] Check for vault lock on 2FA screen

* [PM-3381] Move logic to ViewModel

* [PM-3381] Fix null vm error
2023-08-11 17:32:51 +01:00
André Bispo
5f4cd62f66 [PM-3303] Fix biometric login after key migration (#2679)
* [PM-3303] Add condition to biometric unlock
2023-08-11 15:16:01 +01:00
André Bispo
9b5cde6a46 Fix app fresh install user login with master password. (#2676) 2023-08-10 20:10:02 +01:00
Jake Fink
62b6d21371 [PM-2713] Final merge from Key Migration branch to TDE Feature branch (#2667)
* [PM-2713] add async to key connector service methods

* [PM-2713] rename ephemeral pin key

* add state for biometric key and accept UserKey instead of string for auto key

* Get UserKey from bio state on unlock

* PM-2713 Fix auto-migrating EncKeyEncrypted into MasterKey encrypted UserKey when requesting DecryptUserKeyWithMasterKeyAsync is called

* renaming bio key and fix build

* PM-3194 Fix biometrics button to be shown on upgrade when no UserKey is present yet

* revert removal of key connector service from auth service

* PM-2713 set user key when using KC

* clear enc user key after migration

* use is true for nullable bool

* PR feedback, refactor kc service

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2023-08-10 14:09:53 -04:00
Federico Maccaroni
9001fa1ccf [PM-3102] Update Master password reprompt to be based on MP instead of Key Connector (#2653)
* PM-3102 Added check to see if a user has master password set replacing previous usage of key connector.

* PM-3102 Fix formatting
2023-08-10 13:23:04 -03:00
André Bispo
af016cd13c [PM-3362] Fix auth request approval (#2675)
* [PM-3362] Fix auth request approval

* [PM-3362] Add new exception type
2023-08-10 16:48:03 +01:00
André Bispo
3b9a9fc049 [PM-3357] Fix copy for Login Initiated (#2674) 2023-08-09 14:41:55 +01:00
André Bispo
8205c0763c [PM-3341] Vault Timeout Action not persisted correctly (#2673)
* [PM-3341] Fix timeout action change when navigating
2023-08-08 20:16:07 +01:00
André Bispo
e9afc75f0a [PM-3333] Check for purged admin auth requests (#2671)
* [PM-3333] Check for purged admin auth requests

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2023-08-08 20:15:35 +01:00
André Bispo
446b8c2d35 [PM-2289] Change header title based on auth request type (#2670)
* [PM-2289] Change header title based on auth request type
2023-08-08 20:15:14 +01:00
André Bispo
24378d937e [PM-3319] Check for admin request in Lock page (#2668)
* [PM-3319] Ignore admin auth request when choosing mp as decryption option.
2023-08-08 20:12:01 +01:00
André Bispo
6b2700bcaa [PM-3342] Not you button logs user out. (#2672) 2023-08-08 19:36:49 +01:00
André Bispo
aac66b23bd [PM-3337] Fix admin request deny error (#2669) 2023-08-08 14:15:38 -04:00
André Bispo
8221d55647 [PM-3320][PM-3321] Fix labels and UI tweaks (#2666)
* [PM-3320] Fix UI copy and remember me default ON.

* [PM-3321] Fix UI on Log in with device screen.
2023-08-07 15:16:52 +01:00
André Bispo
53e3429f6d [PM-3313] Null email in Approval Options screen (#2664)
* [PM-3313] Fix null email in approval options screen
2023-08-05 11:39:06 +01:00
André Bispo
faa9b1a9f7 [PM-3313] Fix Android SSO Login (#2663)
* [PM-3313] Catch exception on AuthPendingRequest

* [PM-3313] Fix lock timeout action if user doesn't have a master password.

* code format
2023-08-04 20:37:02 -04:00
Jacob Fink
4f62f5e5bc Fix build after merge 2023-08-03 22:03:28 -04:00
Jacob Fink
9bddc1d456 Merge branch 'auth/pm-2713/drop-master-key-dependency' into feature/pm-1029-tde-login 2023-08-03 22:01:38 -04:00
Jacob Fink
78004dbdb9 [PM-2713] clear service cache when adding new account 2023-08-03 16:28:04 -04:00
Jacob Fink
e820408a64 [PM-2713] make method async again
- returning null from a task thats not async throws
2023-08-03 15:44:41 -04:00
Jacob Fink
c595b1626e fix formatting 2023-08-03 14:34:50 -04:00
Jacob Fink
270a395d9f [PM-2713] set enc user key during kc onboarding 2023-08-03 13:41:19 -04:00
Jacob Fink
4fa8d2ba28 [PM-2713] set user key on set password page 2023-08-03 13:30:45 -04:00
André Bispo
c75bcccf20 [PM-1208] Add TDE flows for new users (#2655)
* [PM-1208] Create new user on SSO. Logout if not password is setup or has pending admin auth request.

* [PM-1208] Fix new user UserKey decryption.

* [PM-1208] Add new user continue to vault logic. Auto enrol user on continue.

* [PM-1208] Trust device only if needed

* [PM-1208] Add logic for New User SSO.

* [PM-1208] Add logic for New User SSO (missing file).
2023-08-03 18:20:51 +01:00
Jacob Fink
e076c9fe04 [PM-2713] add auto unlock key to mobile 2023-08-03 10:49:55 -04:00
André Bispo
78788276ef Merge branch 'master' into feature/pm-1029-tde-login 2023-08-03 14:50:15 +01:00
Jacob Fink
ee0dcd23f5 rename account keys to be more descriptive 2023-08-01 20:08:41 -04:00
Jacob Fink
1e8ed1b5ce [PM-2713] replace generic with inherited class 2023-08-01 10:47:02 -04:00
Jacob Fink
7fb89fa1a5 [PM-2713] consolidate attachment key creation
- also fix ios files missed during symbol rename
2023-08-01 09:30:00 -04:00
Jacob Fink
b1eb263fef [PM-2713] combine makeDataEncKey methods 2023-08-01 08:54:19 -04:00
Jacob Fink
61aac20555 [PM-2713] rename state methods 2023-08-01 08:46:02 -04:00
Jacob Fink
3e87d74061 [PM-2713] revert feedback for build 2023-07-31 17:09:47 -04:00
Jacob Fink
89a9185b20 [PM-2713] rename get pin lock type method 2023-07-31 16:49:41 -04:00
Jacob Fink
e323e196c0 [PM-2713] PR feedback 2023-07-31 15:31:46 -04:00
Jacob Fink
c793260689 [PM-2713] pr feedback 2023-07-31 13:13:29 -04:00
Jacob Fink
c2ddbb7eff [PM-2713] rename toggle method, don't reset enc user key 2023-07-31 12:57:50 -04:00
Jacob Fink
bb5a7383a8 [PM-2713] don't pass user key as param when encrypting 2023-07-31 12:42:56 -04:00
André Bispo
6fe8fc39ab Merge branch 'master' into feature/pm-1029-tde-login
# Conflicts:
#	src/App/Resources/AppResources.resx
2023-07-28 07:33:54 +01:00
Jacob Fink
de5113ede7 [PM-2713] rename PinLockEnum to PinLockType 2023-07-27 16:26:23 -04:00
Jacob Fink
ba6d260565 [PM-2713] rename PrivateKey methods to UserPrivateKey on crypto service 2023-07-27 16:24:31 -04:00
Jacob Fink
7562c688c5 [PM-2713] deconstruct new key pair 2023-07-27 16:20:33 -04:00
André Bispo
c25906206e [PM-2289] [PM-2293] TDE Login with device Admin Request (#2642) 2023-07-27 21:18:36 +01:00
André Bispo
dfc7c55b77 [PM-2297] Login with trusted device (Flow 2) (#2623)
* [PM-2297] Add DecryptUserKeyWithDeviceKey method

* [PM-2297] Add methods to DeviceTrustCryptoService update decryption options model

* [PM-2297] Update account decryption options model

* [PM-2297] Fix TrustedDeviceOption and DeviceResponse model. Change StateService device key get set to have default user id

* [PM-2297] Update navigation to decryption options

* [PM-2297] Add missing action navigations to iOS extensions

* [PM-2297] Fix trust device bug/typo

* [PM-2297] Fix model bug

* [PM-2297] Fix state var crash

* [PM-2297] Add trust device login logic to auth service

* [PM-2297] Refactor auth service key connector code

* [PM-2297] Remove reconciledOptions for deviceKey in state service

* [PM-2297] Remove unnecessary user id params
2023-07-27 16:55:06 +01:00
Jacob Fink
10574a7117 [PM-2713] remove broken casting 2023-07-20 16:17:30 -04:00
Jacob Fink
a2f1ca583a [PM-2713] remove extra comment 2023-07-20 16:14:40 -04:00
Jacob Fink
813ac841c6 [PM-2713] fix casting issues and pin 2023-07-20 13:57:32 -04:00
Jacob Fink
0da3d25955 [PM-2713] rename password hash to master key hash 2023-07-20 12:30:02 -04:00
Jacob Fink
f8c9cde2ed [PM-2713] optimize async code in crypto service 2023-07-20 11:17:57 -04:00
André Bispo
080aabfe82 [PM-1208] Fix merge 2023-07-20 15:39:03 +01:00
André Bispo
c0688c584e [PM-1208] Fix merge 2023-07-20 15:37:08 +01:00
André Bispo
c09672ff88 [PM-1208] Fix app resource file 2023-07-20 15:30:29 +01:00
André Bispo
635b6bc184 Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs
#	src/App/Resources/AppResources.resx
#	src/Core/Abstractions/IStateService.cs
2023-07-20 14:56:40 +01:00
Jacob Fink
da7a1964ef Update crypto service api call to fix build 2023-07-20 09:28:56 -04:00
Jacob Fink
73b8d8e6b8 fix bad merge 2023-07-20 09:20:31 -04:00
Jacob Fink
c61f9f0357 Merge branch 'auth/pm-2713/drop-master-key-dependency' into feature/pm-1029-tde-login 2023-07-20 09:19:48 -04:00
Jacob Fink
a3183857b9 [PM-2713] set decrypt and set user key in login helper 2023-07-19 21:23:21 -04:00
Jacob Fink
bedbca841d [PM-2713] remove unused cached values in crypto service 2023-07-19 21:23:21 -04:00
Jacob Fink
0ff314f076 [PM-2713] use new crypto service api in auth service 2023-07-19 21:23:21 -04:00
Jacob Fink
c9a7c29190 [PM-2713] More conversions to crypto api 2023-07-19 21:23:20 -04:00
Jacob Fink
546bf8dcb1 [PM-2713] convert cipher service and others to crypto service api 2023-07-19 21:23:20 -04:00
Jacob Fink
7fdc5597fc [PM-2713] more conversions to new crypto service api 2023-07-19 21:23:20 -04:00
Jacob Fink
7c664f58b3 [PM-2713] add migration for pin on lock screens 2023-07-19 21:23:20 -04:00
Jacob Fink
bdfe806846 [PM-2713] converting calls to new crypto service api 2023-07-19 21:23:19 -04:00
Jacob Fink
5ed567ab90 [PM-2713] add toggle method to crypto service for keys 2023-07-19 21:23:19 -04:00
Jacob Fink
cd4f44e6f6 [PM-2713] use new MakeMasterKey method 2023-07-19 21:23:19 -04:00
Jacob Fink
d58f0b281b [PM-2713] refresh pin key when setting user key 2023-07-19 21:23:19 -04:00
Jacob Fink
5ba3fac0c0 [PM-2713] add make user key method to crypto service 2023-07-19 21:23:19 -04:00
Jacob Fink
1e30524985 [PM-2713] fix signature of GetUserKeyPin 2023-07-19 21:23:18 -04:00
Jacob Fink
515decb4c9 [PM-2713] add new pin methods to state service 2023-07-19 21:23:18 -04:00
Jacob Fink
bf28d373e9 [PM-2713] more updates to crypto service 2023-07-19 21:23:18 -04:00
Jacob Fink
69d38d4d75 [PM-2713] continue organizing crypto service 2023-07-19 21:23:18 -04:00
Jacob Fink
c1619536aa [PM-2713] rename key hash to password hash & begin add methods to crypto service 2023-07-19 21:23:17 -04:00
Jacob Fink
079e02e4e5 [PM-271] add UserKey and MasterKey support to crypto service 2023-07-19 21:23:17 -04:00
Jacob Fink
15d3da607b [PM-2713] add new state for new keys and obsolete old ones
- UserKey
- MasterKey
- UserKeyMasterKey (enc UserKey from User Table)
2023-07-19 21:23:17 -04:00
Jacob Fink
b5cf9fd79d [PM-2731] add user key and master key types 2023-07-19 21:23:17 -04:00
André Bispo
b688b85d0f [PM-1201] Change timeout actions available based on hasMasterPassword (#2610)
* [PM-1201] Change timeout actions available based on hasMasterPassword
2023-07-12 20:42:21 +01:00
André Bispo
a5df6c0c65 [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
2023-07-12 19:12:57 +01:00
André Bispo
c2d4fa4429 [PM-2583] Answer auth request with mp field as null if doesn't have it. (#2609) 2023-07-11 23:05:35 +01:00
André Bispo
548bd12a8e Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options 2023-07-10 12:37:13 +01:00
André Bispo
58542fd255 Merge branch 'master' into feature/pm-1029-tde-login 2023-07-10 12:36:59 +01:00
André Bispo
800b4c71de Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/Core/Models/Response/DeviceResponse.cs
#	src/Core/Services/ApiService.cs
2023-07-10 12:32:27 +01:00
Jake Fink
3053eaa036 [PM-1379] add DeviceTrustCryptoService with establish trust logic (#2535)
* [PM-1379] add DeviceCryptoService with establish trust logic

* PM-1379 update api location and other minor refactors

* pm-1379 fix encoding

* update trusted device keys api call to Put

* [PM-1379] rename DeviceCryptoService to DeviceTrustCryptoService
- refactors to prevent side effects

* [PM-1379] rearrange methods in DeviceTrustCryptoService

* [PM-1379] rearrange methods in abstraction

* [PM-1379] deconstruct tuples

* [PM-1379] remove extra tasks
2023-07-05 16:13:20 -04:00
André Bispo
6268f0776b Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options 2023-07-03 10:34:53 +01:00
André Bispo
cbbc41be67 [PM-1208] Add continue button and not you option 2023-07-03 10:34:02 +01:00
André Bispo
e164fb9823 Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/App/Resources/AppResources.resx
#	src/Core/Abstractions/IApiService.cs
#	src/Core/Services/StateService.cs
2023-06-29 14:36:46 +01:00
André Bispo
87866304a6 [PM-1208] Add device related api endpoint. Add AccoundDecryptOptions model and property to user Account. 2023-06-28 22:37:08 +01:00
André Bispo
84a82f0876 [PM-1208] Add Device approval options screen. View model waiting for additional logic to be added. 2023-05-17 17:46:45 +01:00
49 changed files with 192 additions and 295 deletions

View File

@@ -68,6 +68,7 @@ namespace Bit.Droid
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
ServiceContainer.Resolve<IUserVerificationService>());

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.8.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.7.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />

View File

@@ -1,5 +1,4 @@
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.App.Abstractions
{
@@ -7,7 +6,7 @@ namespace Bit.App.Abstractions
{
string[] ProtectedFields { get; }
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
Task<bool> ShowPasswordPromptAsync();
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
}

View File

@@ -50,13 +50,13 @@ namespace Bit.App.Pages
private async Task StartLoginWithDeviceAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions, true);
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, true);
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}

View File

@@ -13,7 +13,7 @@ namespace Bit.App.Pages
private LoginPasswordlessRequestViewModel _vm;
private readonly AppOptions _appOptions;
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null, bool authingWithSso = false)
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null)
{
InitializeComponent();
_appOptions = appOptions;
@@ -21,7 +21,6 @@ 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());

View File

@@ -12,7 +12,6 @@ using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Services;
@@ -81,7 +80,6 @@ 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; }
@@ -235,7 +233,7 @@ namespace Bit.App.Pages
try
{
PasswordlessLoginResponse response = null;
if (AuthingWithSso)
if (await _stateService.IsAuthenticatedAsync())
{
response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId);
}
@@ -244,14 +242,14 @@ namespace Bit.App.Pages
response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode);
}
if (response?.RequestApproved != true)
if (response.RequestApproved == null || !response.RequestApproved.Value)
{
return;
}
StopCheckLoginRequestStatus();
var authResult = await _authService.LogInPasswordlessAsync(AuthingWithSso, Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (authResult == null && await _stateService.IsAuthenticatedAsync())
@@ -278,10 +276,6 @@ namespace Bit.App.Pages
await HandleLoginCompleteAsync();
}
}
catch (ApiException ex) when (ex.Error?.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
HandleException(ex);
}
catch (Exception ex)
{
StartCheckLoginRequestStatus();
@@ -349,6 +343,16 @@ namespace Bit.App.Pages
_requestAccessCode = response.RequestAccessCode;
_requestKeyPair = response.RequestKeyPair;
}
private void HandleException(Exception ex)
{
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
}).FireAndForget();
_logger.Exception(ex);
}
}
}

View File

@@ -114,7 +114,7 @@ namespace Bit.App.Pages
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
var response = await _apiService.PreValidateSsoAsync(OrgIdentifier);
var response = await _apiService.PreValidateSso(OrgIdentifier);
if (string.IsNullOrWhiteSpace(response?.Token))
{
@@ -211,46 +211,38 @@ namespace Bit.App.Pages
return;
}
// Trusted device option is sent regardless if this is a trusted device or not
// If it is trusted, it will have the necessary keys
if (decryptOptions?.TrustedDeviceOption != null)
{
if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
// If we have a device key but no keys on server, we need to remove the device key
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
{
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
StartDeviceApprovalOptionsAction?.Invoke();
return;
}
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
else
{
StartSetPasswordAction?.Invoke();
return;
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
// Device is trusted and has keys, so we can decrypt
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
return;
}
// Check for pending Admin Auth requests before navigating to device approval options
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null)
else if (pendingRequest != null)
{
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
if (authRequest?.RequestApproved == true)
if (authRequest != null && authRequest.RequestApproved != null && authRequest.RequestApproved.Value)
{
var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
var authResult = await _authService.LogInPasswordlessAsync(await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
if (authResult == null && await _stateService.IsAuthenticatedAsync())
{
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
@@ -276,14 +268,18 @@ namespace Bit.App.Pages
// In the standard, non TDE case, a user must set password if they don't
// have one and they aren't using key connector.
// Note: TDE & Key connector are mutually exclusive org config options.
if (response.ResetMasterPassword || (decryptOptions?.RequireSetPassword == true))
if (response.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false))
{
// TODO: We need to look into how to handle this when Org removes TDE
// Will we have the User Key by now to set a new password?
StartSetPasswordAction?.Invoke();
return;
}
if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}

View File

@@ -178,10 +178,7 @@ namespace Bit.App.Pages
Email = Email.Trim().ToLower();
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
newMasterKey,
await _cryptoService.MakeUserKeyAsync()
);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
var hashedPassword = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey);
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new RegisterRequest

View File

@@ -47,7 +47,7 @@ namespace Bit.App.Pages
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _apiService.PostLeaveOrganizationAsync(Organization.Id);
await _apiService.PostLeaveOrganization(Organization.Id);
await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync();

View File

@@ -169,8 +169,7 @@ namespace Bit.App.Pages
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new SetPasswordRequest

View File

@@ -18,6 +18,7 @@ namespace Bit.App.Pages
private TwoFactorPageViewModel _vm;
private bool _inited;
private bool _authingWithSso;
private string _orgIdentifier;
public TwoFactorPage(bool? authingWithSso = false, AppOptions appOptions = null, string orgIdentifier = null)

View File

@@ -39,6 +39,7 @@ namespace Bit.App.Pages
private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction;
private string _webVaultUrl = "https://vault.bitwarden.com";
private bool _authingWithSso = false;
private bool _enableContinue = false;
private bool _showContinue = true;
@@ -143,6 +144,8 @@ namespace Bit.App.Pages
return;
}
_authingWithSso = _authService.AuthingWithSso();
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
{
_webVaultUrl = _environmentService.BaseUrl;
@@ -324,80 +327,47 @@ namespace Bit.App.Pages
_messagingService.Send("listenYubiKeyOTP", false);
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
if (decryptOptions?.TrustedDeviceOption != null)
if (_authingWithSso && result.ResetMasterPassword)
{
if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
StartSetPasswordAction?.Invoke();
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (decryptOptions?.TrustedDeviceOption != null)
{
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
// If we have a device key but no keys on server, we need to remove the device key
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
{
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
StartDeviceApprovalOptionsAction?.Invoke();
return;
}
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
return;
}
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
// Device is trusted and has keys, so we can decrypt
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
return;
}
// Check for pending Admin Auth requests before navigating to device approval options
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null)
{
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
if (authRequest?.RequestApproved == true)
{
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(
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
await _stateService.SetPendingAdminAuthRequestAsync(null);
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
}
}
else
{
await _stateService.SetPendingAdminAuthRequestAsync(null);
StartDeviceApprovalOptionsAction?.Invoke();
await TwoFactorAuthSuccessAsync();
}
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
return;
}
// In the standard, non TDE case, a user must set password if they don't
// have one and they aren't using key connector.
// Note: TDE & Key connector are mutually exclusive org config options.
if (result.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false))
else
{
// TODO: We need to look into how to handle this when Org removes TDE
// Will we have the User Key by now to set a new password?
StartSetPasswordAction?.Invoke();
return;
await TwoFactorAuthSuccessAsync();
}
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
}
catch (ApiException e)
{

View File

@@ -65,7 +65,7 @@ namespace Bit.App.Pages
_initialized = true;
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
UseOTPVerification = !await _userVerificationService.HasMasterPasswordAsync(true);
UseOTPVerification = !await _userVerificationService.HasMasterPasswordAsync();
if (UseOTPVerification)
{
@@ -163,7 +163,7 @@ namespace Bit.App.Pages
return;
}
var verificationType = await _userVerificationService.HasMasterPasswordAsync(true)
var verificationType = await _userVerificationService.HasMasterPasswordAsync()
? VerificationType.MasterPassword
: VerificationType.OTP;
if (!await _userVerificationService.VerifyUser(Secret, verificationType))

View File

@@ -82,7 +82,7 @@ namespace Bit.App.Pages
return;
}
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}

View File

@@ -698,12 +698,12 @@ namespace Bit.App.Pages
public async Task<bool> PromptPasswordAsync()
{
if (_passwordReprompted)
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
{
return true;
}
return _passwordReprompted = await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(Cipher.Reprompt);
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
}
private async Task<bool> CanCloneAsync()

View File

@@ -191,7 +191,7 @@ namespace Bit.App.Pages
if (_appOptions?.OtpData != null)
{
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}
@@ -208,7 +208,7 @@ namespace Bit.App.Pages
}
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
{
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}

View File

@@ -60,7 +60,7 @@ namespace Bit.App.Pages
var cipher = listItem.Cipher;
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}

View File

@@ -2,7 +2,6 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
namespace Bit.App.Services
{
@@ -19,13 +18,8 @@ namespace Bit.App.Services
public string[] ProtectedFields { get; } = { "LoginTotp", "LoginPassword", "H_FieldValue", "CardNumber", "CardCode" };
public async Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password)
public async Task<bool> ShowPasswordPromptAsync()
{
if (repromptType == CipherRepromptType.None || await ShouldByPassMasterPasswordRepromptAsync())
{
return true;
}
return await _platformUtilsService.ShowPasswordDialogAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, ValidatePasswordAsync);
}
@@ -44,10 +38,5 @@ namespace Bit.App.Services
return await _cryptoService.CompareAndUpdateKeyHashAsync(password, null);
}
private async Task<bool> ShouldByPassMasterPasswordRepromptAsync()
{
return await _cryptoService.GetMasterKeyHashAsync() is null;
}
}
}

View File

@@ -99,55 +99,60 @@ namespace Bit.App.Utilities
{
await page.Navigation.PushModalAsync(new NavigationPage(new CipherDetailsPage(cipher.Id)));
}
else if (selection == AppResources.Edit
&&
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
else if (selection == AppResources.Edit)
{
await page.Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(cipher.Id)));
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await page.Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(cipher.Id)));
}
}
else if (selection == AppResources.CopyUsername)
{
await clipboardService.CopyTextAsync(cipher.Type == CipherType.Login ? cipher.Login.Username : cipher.Fido2Key.UserName);
platformUtilsService.ShowToastForCopiedValue(AppResources.Username);
}
else if (selection == AppResources.CopyPassword
&&
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
else if (selection == AppResources.CopyPassword)
{
await clipboardService.CopyTextAsync(cipher.Login.Password);
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
}
else if (selection == AppResources.CopyTotp
&&
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
{
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (!string.IsNullOrWhiteSpace(totp))
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await clipboardService.CopyTextAsync(totp);
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
await clipboardService.CopyTextAsync(cipher.Login.Password);
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
}
}
else if (selection == AppResources.CopyTotp)
{
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (!string.IsNullOrWhiteSpace(totp))
{
await clipboardService.CopyTextAsync(totp);
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
}
}
}
else if (selection == AppResources.Launch && cipher.CanLaunch)
{
platformUtilsService.LaunchUri(cipher.LaunchUri);
}
else if (selection == AppResources.CopyNumber
&&
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
else if (selection == AppResources.CopyNumber)
{
await clipboardService.CopyTextAsync(cipher.Card.Number);
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await clipboardService.CopyTextAsync(cipher.Card.Number);
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
}
}
else if (selection == AppResources.CopySecurityCode
&&
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
else if (selection == AppResources.CopySecurityCode)
{
await clipboardService.CopyTextAsync(cipher.Card.Code);
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
eventService.CollectAsync(EventType.Cipher_ClientCopiedCardCode, cipher.Id).FireAndForget();
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await clipboardService.CopyTextAsync(cipher.Card.Code);
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
}
}
else if (selection == AppResources.CopyNotes)
{

View File

@@ -60,6 +60,7 @@ namespace Bit.App.Utilities
/// </summary>
public class VerificationActionsFlowHelper : IVerificationActionsFlowHelper
{
private readonly IKeyConnectorService _keyConnectorService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly ICryptoService _cryptoService;
private readonly IUserVerificationService _userVerificationService;
@@ -71,11 +72,12 @@ namespace Bit.App.Utilities
private readonly Dictionary<VerificationFlowAction, IActionFlowExecutioner> _actionExecutionerDictionary = new Dictionary<VerificationFlowAction, IActionFlowExecutioner>();
public VerificationActionsFlowHelper(
public VerificationActionsFlowHelper(IKeyConnectorService keyConnectorService,
IPasswordRepromptService passwordRepromptService,
ICryptoService cryptoService,
IUserVerificationService userVerificationService)
{
_keyConnectorService = keyConnectorService;
_passwordRepromptService = passwordRepromptService;
_cryptoService = cryptoService;
_userVerificationService = userVerificationService;
@@ -108,7 +110,7 @@ namespace Bit.App.Utilities
public async Task ValidateAndExecuteAsync()
{
var verificationType = await _userVerificationService.HasMasterPasswordAsync(true)
var verificationType = await _userVerificationService.HasMasterPasswordAsync()
? VerificationType.MasterPassword
: VerificationType.OTP;

View File

@@ -47,7 +47,7 @@ namespace Bit.Core.Abstractions
Task PutDeleteCipherAsync(string id);
Task<CipherResponse> PutRestoreCipherAsync(string id);
Task RefreshIdentityTokenAsync();
Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier);
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default);
@@ -72,10 +72,10 @@ namespace Bit.Core.Abstractions
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
OrganizationUserResetPasswordEnrollmentRequest request);
Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl);
Task PostMasterKeyToKeyConnectorAsync(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
Task PostSetKeyConnectorKeyAsync(SetKeyConnectorKeyRequest request);
Task PostConvertToKeyConnectorAsync();
Task PostLeaveOrganizationAsync(string id);
Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request);
Task PostConvertToKeyConnector();
Task PostLeaveOrganization(string id);
Task<SendResponse> GetSendAsync(string id);
Task<SendResponse> PostSendAsync(SendRequest request);

View File

@@ -27,7 +27,7 @@ namespace Bit.Core.Abstractions
Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId);
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
Task<AuthResult> LogInPasswordlessAsync(bool authingWithSso, string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();

View File

@@ -26,7 +26,7 @@ namespace Bit.Core.Abstractions
Task<MasterKey> GetMasterKeyAsync(string userId = null);
Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig);
Task ClearMasterKeyAsync(string userId = null);
Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null);
Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey);
Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null);
Task<Tuple<SymmetricCryptoKey, EncString>> MakeDataEncKeyAsync(SymmetricCryptoKey key);
Task<string> HashMasterKeyAsync(string password, MasterKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);

View File

@@ -6,6 +6,6 @@ namespace Bit.Core.Abstractions
public interface IUserVerificationService
{
Task<bool> VerifyUser(string secret, VerificationType verificationType);
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
Task<bool> HasMasterPasswordAsync();
}
}

View File

@@ -86,8 +86,8 @@ namespace Bit.Core
public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}";
public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}";
public static string MasterKeyEncryptedUserKeyKey(string userId) => $"masterKeyEncryptedUserKey_{userId}";
public static string UserKeyAutoUnlockKey(string userId) => $"userKeyAutoUnlock_{userId}";
public static string UserKeyBiometricUnlockKey(string userId) => $"userKeyBiometricUnlock_{userId}";
public static string UserKeyAutoUnlockKey(string userId) => $"autoUnlock_{userId}";
public static string UserKeyBiometricUnlockKey(string userId) => $"biometricUnlock_{userId}";
public static string CiphersKey(string userId) => $"ciphers_{userId}";
public static string FoldersKey(string userId) => $"folders_{userId}";
public static string CollectionsKey(string userId) => $"collections_{userId}";

View File

@@ -1,12 +0,0 @@
using System;
namespace Bit.Core.Exceptions
{
public class MasterKeyNullException : Exception
{
public MasterKeyNullException()
: base("MasterKey is null.")
{
}
}
}

View File

@@ -1,12 +0,0 @@
using System;
namespace Bit.Core.Exceptions
{
public class UserKeyNullException : Exception
{
public UserKeyNullException()
: base("UserKey is null.")
{
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Bit.Core.Enums;
namespace Bit.Core.Models.Domain
@@ -9,8 +8,6 @@ namespace Bit.Core.Models.Domain
public bool TwoFactor { get; set; }
public bool CaptchaNeeded => !string.IsNullOrWhiteSpace(CaptchaSiteKey);
public string CaptchaSiteKey { get; set; }
// TODO: PM-3287 - Remove after 3 releases of backwards compatibility - Target release 2023.12
[Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")]
public bool ResetMasterPassword { get; set; }
public bool ForcePasswordReset { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }

View File

@@ -211,12 +211,12 @@ namespace Bit.Core.Services
return SendAsync<DeleteAccountRequest, object>(HttpMethod.Delete, "/accounts", request, true, false);
}
public Task PostConvertToKeyConnectorAsync()
public Task PostConvertToKeyConnector()
{
return SendAsync<object, object>(HttpMethod.Post, "/accounts/convert-to-key-connector", null, true, false);
}
public Task PostSetKeyConnectorKeyAsync(SetKeyConnectorKeyRequest request)
public Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request)
{
return SendAsync<SetKeyConnectorKeyRequest>(HttpMethod.Post, "/accounts/set-key-connector-key", request, true);
}
@@ -486,7 +486,7 @@ namespace Bit.Core.Services
$"/organizations/{identifier}/auto-enroll-status", null, true, true);
}
public Task PostLeaveOrganizationAsync(string id)
public Task PostLeaveOrganization(string id)
{
return SendAsync<object, object>(HttpMethod.Post, $"/organizations/{id}/leave", null, true, false);
}
@@ -541,7 +541,7 @@ namespace Bit.Core.Services
}
}
public async Task PostMasterKeyToKeyConnectorAsync(string keyConnectorUrl, KeyConnectorUserKeyRequest request)
public async Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request)
{
using (var requestMessage = new HttpRequestMessage())
{
@@ -627,7 +627,7 @@ namespace Bit.Core.Services
return accessToken;
}
public async Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier)
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
{
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
using (var requestMessage = new HttpRequestMessage())

View File

@@ -33,7 +33,6 @@ namespace Bit.Core.Services
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
private MasterKey _masterKey;
private UserKey _userKey;
private string _authedUserId;
private MasterPasswordPolicyOptions _masterPasswordPolicy;
@@ -201,13 +200,12 @@ namespace Bit.Core.Services
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
}
public async Task<AuthResult> LogInPasswordlessAsync(bool authingWithSso, string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash)
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash)
{
var decryptedKey = await _cryptoService.RsaDecryptAsync(encryptedAuthRequestKey, decryptionKey);
// 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)
// On SSO flow user is already AuthN
if (await _stateService.IsAuthenticatedAsync())
{
if (string.IsNullOrEmpty(masterKeyHash))
{
@@ -224,10 +222,10 @@ 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)
{
return await LogInHelperAsync(email, accessCode, null, null, null, null, null, null, null, null, null, authRequestId: authRequestId, userKey2FA: new UserKey(decryptedKey));
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
return null;
}
var decKeyHash = await _cryptoService.RsaDecryptAsync(masterKeyHash, decryptionKey);
@@ -238,13 +236,7 @@ namespace Bit.Core.Services
public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId)
{
SelectedTwoFactorProviderType = null;
var result = await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
if (result.ForcePasswordReset)
{
await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.AdminForcePasswordReset);
}
return result;
return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
}
public async Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
@@ -255,7 +247,7 @@ namespace Bit.Core.Services
CaptchaToken = captchaToken;
}
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _masterKey,
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId, userKey2FA: _userKey);
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId);
// If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync()
if (!string.IsNullOrEmpty(_authedUserId) && _2faForcePasswordResetReason.HasValue)
@@ -400,7 +392,7 @@ namespace Bit.Core.Services
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
string code, string codeVerifier, string redirectUrl, MasterKey masterKey,
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null,
string captchaToken = null, string orgId = null, string authRequestId = null, UserKey userKey2FA = null)
string captchaToken = null, string orgId = null, string authRequestId = null)
{
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
var appId = await _appIdService.GetAppIdAsync();
@@ -465,7 +457,6 @@ namespace Bit.Core.Services
CodeVerifier = codeVerifier;
SsoRedirectUrl = redirectUrl;
_masterKey = _setCryptoKeys ? masterKey : null;
_userKey = userKey2FA;
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
@@ -547,10 +538,6 @@ namespace Bit.Core.Services
{
await _cryptoService.SetMasterKeyAsync(masterKey);
}
else if (userKey2FA != null)
{
await _cryptoService.SetUserKeyAsync(userKey2FA);
}
// Decrypt UserKey with MasterKey
masterKey ??= await _stateService.GetMasterKeyAsync();

View File

@@ -252,7 +252,7 @@ namespace Bit.Core.Services
{
if (!await _cryptoService.HasUserKeyAsync())
{
throw new UserKeyNullException();
throw new Exception("No key.");
}
var decCiphers = new List<CipherView>();
async Task decryptAndAddCipherAsync(Cipher cipher)

View File

@@ -6,7 +6,6 @@ using System.Text;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
@@ -64,7 +63,7 @@ namespace Bit.Core.Services
public async Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null)
{
var userKey = await GetUserKeyAsync(userId);
var userKey = await GetUserKeyAsync();
if (userKey != null)
{
return userKey;
@@ -72,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(userId)).Key);
return new UserKey((await GetMasterKeyAsync()).Key);
}
public async Task<bool> HasUserKeyAsync(string userId = null)
@@ -153,13 +152,9 @@ namespace Bit.Core.Services
return _stateService.SetMasterKeyAsync(null, userId);
}
public async Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null)
public async Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey)
{
userKey ??= await GetUserKeyAsync();
if (userKey == null)
{
throw new UserKeyNullException();
}
var userKey = await GetUserKeyAsync() ?? await MakeUserKeyAsync();
return await BuildProtectedSymmetricKeyAsync(masterKey, userKey.Key, keyBytes => new UserKey(keyBytes));
}
@@ -168,7 +163,7 @@ namespace Bit.Core.Services
masterKey ??= await GetMasterKeyAsync(userId);
if (masterKey == null)
{
throw new MasterKeyNullException();
throw new Exception("No master key found.");
}
if (encUserKey == null)
@@ -1011,10 +1006,6 @@ namespace Bit.Core.Services
// Decrypt
var masterKey = new MasterKey(Convert.FromBase64String(oldKey));
var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId);
if (encryptedUserKey == null)
{
return;
}
var userKey = await DecryptUserKeyWithMasterKeyAsync(
masterKey,
new EncString(encryptedUserKey),
@@ -1074,11 +1065,8 @@ namespace Bit.Core.Services
var encPin = await EncryptAsync(pin, userKey);
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
}
// Clear old key only if not needed for bio/auto migration
if (await _stateService.GetKeyEncryptedAsync() != null)
{
await _stateService.SetEncKeyEncryptedAsync(null);
}
// Clear old key
await _stateService.SetEncKeyEncryptedAsync(null);
return userKey;
}

View File

@@ -4,7 +4,6 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
@@ -81,7 +80,7 @@ namespace Bit.Core.Services
var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey)
{
throw new UserKeyNullException();
throw new Exception("No key.");
}
var decFolders = new List<FolderView>();
async Task decryptAndAddFolderAsync(Folder folder)

View File

@@ -68,14 +68,14 @@ namespace Bit.Core.Services
try
{
var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64);
await _apiService.PostMasterKeyToKeyConnectorAsync(organization.KeyConnectorUrl, keyConnectorRequest);
await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest);
}
catch (Exception e)
{
throw new Exception("Unable to reach Key Connector", e);
}
await _apiService.PostConvertToKeyConnectorAsync();
await _apiService.PostConvertToKeyConnector();
}
public async Task<bool> UserNeedsMigrationAsync()
@@ -95,15 +95,13 @@ namespace Bit.Core.Services
var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
await _cryptoService.SetMasterKeyAsync(newMasterKey);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
newMasterKey,
await _cryptoService.MakeUserKeyAsync());
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
await _cryptoService.SetUserKeyAsync(newUserKey);
try
{
await _apiService.PostMasterKeyToKeyConnectorAsync(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
await _apiService.PostUserKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
}
catch (Exception e)
{
@@ -117,9 +115,9 @@ namespace Bit.Core.Services
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
};
var setPasswordRequest = new SetKeyConnectorKeyRequest(
newProtectedUserKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
newProtectedPrivateKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
);
await _apiService.PostSetKeyConnectorKeyAsync(setPasswordRequest);
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
}
}
}

View File

@@ -1535,7 +1535,6 @@ namespace Bit.Core.Services
SetUserKeyAutoUnlockAsync(null, userId),
SetUserKeyBiometricUnlockAsync(null, userId),
SetProtectedPinAsync(null, userId),
SetPinKeyEncryptedUserKeyAsync(null, userId),
SetKeyHashAsync(null, userId),
SetOrgKeysEncryptedAsync(null, userId),
SetPrivateKeyEncryptedAsync(null, userId),

View File

@@ -68,20 +68,15 @@ namespace Bit.Core.Services
await _platformUtilsService.ShowDialogAsync(errorMessage);
}
public async Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false)
public async Task<bool> HasMasterPasswordAsync()
{
async Task<bool> CheckMasterKeyHashAsync()
{
return !checkMasterKeyHash || await _cryptoService.GetMasterKeyHashAsync() != null;
};
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
if (decryptOptions != null)
{
return decryptOptions.HasMasterPassword && await CheckMasterKeyHashAsync();
return decryptOptions.HasMasterPassword;
}
return !await _keyConnectorService.GetUsesKeyConnectorAsync() && await CheckMasterKeyHashAsync();
return !await _keyConnectorService.GetUsesKeyConnectorAsync();
}
}
}

View File

@@ -59,13 +59,6 @@ namespace Bit.Core.Services
public async Task<bool> 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))
{
@@ -74,11 +67,14 @@ namespace Bit.Core.Services
if (!await _cryptoService.HasUserKeyAsync(userId))
{
if (!await _cryptoService.HasAutoUnlockKeyAsync(userId))
if (await _cryptoService.HasAutoUnlockKeyAsync(userId))
{
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId));
}
else
{
return true;
}
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), userId);
}
// Check again to verify auto key was set

View File

@@ -313,7 +313,7 @@ namespace Bit.iOS.Autofill
// Add a timeout to resolve keyboard not always showing up.
await Task.Delay(250);
var passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
if (!await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync())
if (!await passwordRepromptService.ShowPasswordPromptAsync())
{
var err = new NSError(new NSString("ASExtensionErrorDomain"),
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
@@ -511,11 +511,11 @@ namespace Bit.iOS.Autofill
LogoutIfAuthed();
}
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false)
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null)
{
var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions, authingWithSso);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions);
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, true));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true));
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
}
var navigationPage = new NavigationPage(loginApproveDevicePage);

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.autofill</string>
<key>CFBundleShortVersionString</key>
<string>2023.8.0</string>
<string>2023.7.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleLocalizations</key>

View File

@@ -33,7 +33,7 @@ namespace Bit.iOS.Autofill.Utilities
return;
}
if (!await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(item.Reprompt))
if (item.Reprompt != Bit.Core.Enums.CipherRepromptType.None && !await passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}

View File

@@ -367,8 +367,7 @@ namespace Bit.iOS.Core.Controllers
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
await SetKeyAndContinueAsync(userKey);
DoContinue();
}
}

View File

@@ -245,6 +245,7 @@ namespace Bit.iOS.Core.Utilities
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
ServiceContainer.Resolve<IUserVerificationService>());

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.find-login-action-extension</string>
<key>CFBundleShortVersionString</key>
<string>2023.8.0</string>
<string>2023.7.1</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>

View File

@@ -533,11 +533,11 @@ namespace Bit.iOS.Extension
LogoutIfAuthed();
}
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false)
private void LaunchLoginWithDevice(AuthRequestType authRequestType,string email = null)
{
var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions, authingWithSso);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions);
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, true));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true));
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
}
var navigationPage = new NavigationPage(loginApproveDevicePage);

View File

@@ -126,7 +126,7 @@ namespace Bit.iOS.Extension
return;
}
if (!await _controller.PasswordRepromptService.PromptAndCheckPasswordIfNeededAsync(item.Reprompt))
if (item.Reprompt != Bit.Core.Enums.CipherRepromptType.None && !await _controller.PasswordRepromptService.ShowPasswordPromptAsync())
{
return;
}

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>2023.8.0</string>
<string>2023.7.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>MinimumOSVersion</key>

View File

@@ -348,9 +348,9 @@ namespace Bit.iOS.ShareExtension
LogoutIfAuthed();
}
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false)
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null)
{
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, _appOptions.Value, authingWithSso);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, _appOptions.Value);
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, true));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true));
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
}
var navigationPage = new NavigationPage(loginApproveDevicePage);

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key>
<string>2023.8.0</string>
<string>2023.7.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleIconName</key>