1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-18 17:23:18 +00:00

[PM-2289] [PM-2293] TDE Login with device Admin Request (#2642)

This commit is contained in:
André Bispo
2023-07-27 21:18:36 +01:00
committed by GitHub
parent dfc7c55b77
commit c25906206e
15 changed files with 193 additions and 27 deletions

View File

@@ -34,6 +34,7 @@ namespace Bit.App.Pages
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
private readonly IPasswordGenerationService _passwordGenerationService; private readonly IPasswordGenerationService _passwordGenerationService;
private IDeviceTrustCryptoService _deviceTrustCryptoService; private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ISyncService _syncService;
private string _email; private string _email;
private string _masterPassword; private string _masterPassword;
private string _pin; private string _pin;
@@ -65,6 +66,7 @@ namespace Bit.App.Pages
_policyService = ServiceContainer.Resolve<IPolicyService>(); _policyService = ServiceContainer.Resolve<IPolicyService>();
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(); _passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>(); _deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_syncService = ServiceContainer.Resolve<ISyncService>();
PageTitle = AppResources.VerifyMasterPassword; PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
@@ -480,6 +482,7 @@ namespace Bit.App.Pages
private async Task DoContinueAsync() private async Task DoContinueAsync()
{ {
_syncService.FullSyncAsync(false).FireAndForget();
await _stateService.SetBiometricLockedAsync(false); await _stateService.SetBiometricLockedAsync(false);
_watchDeviceService.SyncDataToWatchAsync().FireAndForget(); _watchDeviceService.SyncDataToWatchAsync().FireAndForget();
_messagingService.Send("unlocked"); _messagingService.Send("unlocked");

View File

@@ -13,6 +13,7 @@ using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
@@ -34,6 +35,8 @@ namespace Bit.App.Pages
private IEnvironmentService _environmentService; private IEnvironmentService _environmentService;
private ILogger _logger; private ILogger _logger;
private IDeviceTrustCryptoService _deviceTrustCryptoService; private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly ICryptoService _cryptoService;
protected override II18nService i18nService => _i18nService; protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService; protected override IEnvironmentService environmentService => _environmentService;
@@ -61,6 +64,8 @@ namespace Bit.App.Pages
_stateService = ServiceContainer.Resolve<IStateService>(); _stateService = ServiceContainer.Resolve<IStateService>();
_logger = ServiceContainer.Resolve<ILogger>(); _logger = ServiceContainer.Resolve<ILogger>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>(); _deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
PageTitle = AppResources.LogInWithAnotherDevice; PageTitle = AppResources.LogInWithAnotherDevice;
@@ -202,14 +207,22 @@ namespace Bit.App.Pages
private async Task CheckLoginRequestStatus() private async Task CheckLoginRequestStatus()
{ {
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_requestAccessCode)) if (string.IsNullOrEmpty(_requestId))
{ {
return; return;
} }
try try
{ {
var response = await _authService.GetPasswordlessLoginResponseAsync(_requestId, _requestAccessCode); PasswordlessLoginResponse response = null;
if (await _stateService.IsAuthenticatedAsync())
{
response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId);
}
else
{
response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode);
}
if (response.RequestApproved == null || !response.RequestApproved.Value) if (response.RequestApproved == null || !response.RequestApproved.Value)
{ {
@@ -221,6 +234,12 @@ namespace Bit.App.Pages
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash); var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (authResult == null && await _stateService.IsAuthenticatedAsync())
{
await HandleLoginCompleteAsync();
return;
}
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus)) if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
{ {
return; return;
@@ -236,9 +255,7 @@ namespace Bit.App.Pages
} }
else else
{ {
_syncService.FullSyncAsync(true).FireAndForget(); await HandleLoginCompleteAsync();
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
LogInSuccessAction?.Invoke();
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -248,22 +265,66 @@ namespace Bit.App.Pages
} }
} }
private async Task HandleLoginCompleteAsync()
{
await _stateService.SetPendingAdminAuthRequestAsync(null);
_syncService.FullSyncAsync(true).FireAndForget();
LogInSuccessAction?.Invoke();
}
private async Task CreatePasswordlessLoginAsync() private async Task CreatePasswordlessLoginAsync()
{ {
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading)); await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType); PasswordlessLoginResponse response = null;
if (response != null) var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null && _authRequestType == AuthRequestType.AdminApproval)
{ {
FingerprintPhrase = response.FingerprintPhrase; response = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
_requestId = response.Id; if (response == null || (response.IsAnswered && !response.RequestApproved.Value))
_requestAccessCode = response.RequestAccessCode; {
_requestKeyPair = response.RequestKeyPair; // handle pending auth request not valid remove it from state
await _stateService.SetPendingAdminAuthRequestAsync(null);
pendingRequest = null;
}
else
{
// Derive pubKey from privKey in state to avoid MITM attacks
// Also generate FingerprintPhrase locally for the same reason
var derivedPublicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(pendingRequest.PrivateKey);
response.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(Email, derivedPublicKey));
response.RequestKeyPair = new Tuple<byte[], byte[]>(derivedPublicKey, pendingRequest.PrivateKey);
}
} }
if (response == null)
{
response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
}
await HandlePasswordlessLoginAsync(response, pendingRequest == null && _authRequestType == AuthRequestType.AdminApproval);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
} }
private async Task HandlePasswordlessLoginAsync(PasswordlessLoginResponse response, bool createPendingAdminRequest)
{
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
if (createPendingAdminRequest)
{
var pendingAuthRequest = new PendingAdminAuthRequest { Id = response.Id, PrivateKey = response.RequestKeyPair.Item2 };
await _stateService.SetPendingAdminAuthRequestAsync(pendingAuthRequest);
}
FingerprintPhrase = response.FingerprintPhrase;
_requestId = response.Id;
_requestAccessCode = response.RequestAccessCode;
_requestKeyPair = response.RequestKeyPair;
}
private void HandleException(Exception ex) private void HandleException(Exception ex)
{ {
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () => Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>

View File

@@ -37,6 +37,8 @@ namespace Bit.App.Pages
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync()); Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
_vm.UpdateTempPasswordAction = _vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.StartDeviceApprovalOptionsAction =
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
_vm.CloseAction = async () => await Navigation.PopModalAsync(); _vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView; DuoWebView = _duoWebView;
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
@@ -180,6 +182,12 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
private async Task StartDeviceApprovalOptionsAsync()
{
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task TwoFactorAuthSuccessAsync() private async Task TwoFactorAuthSuccessAsync()
{ {
if (_authingWithSso) if (_authingWithSso)

View File

@@ -11,6 +11,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Newtonsoft.Json; using Newtonsoft.Json;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
@@ -33,7 +34,7 @@ namespace Bit.App.Pages
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly IAppIdService _appIdService; private readonly IAppIdService _appIdService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private TwoFactorProviderType? _selectedProviderType; private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction; private string _totpInstruction;
private string _webVaultUrl = "https://vault.bitwarden.com"; private string _webVaultUrl = "https://vault.bitwarden.com";
@@ -55,6 +56,7 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService"); _i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService"); _appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_logger = ServiceContainer.Resolve<ILogger>(); _logger = ServiceContainer.Resolve<ILogger>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
PageTitle = AppResources.TwoStepLogin; PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync()); SubmitCommand = new Command(async () => await SubmitAsync());
@@ -118,6 +120,7 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; } public Command SubmitCommand { get; }
public ICommand MoreCommand { get; } public ICommand MoreCommand { get; }
public Action TwoFactorAuthSuccessAction { get; set; } public Action TwoFactorAuthSuccessAction { get; set; }
public Action StartDeviceApprovalOptionsAction { get; set; }
public Action StartSetPasswordAction { get; set; } public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; } public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; } public Action UpdateTempPasswordAction { get; set; }
@@ -315,6 +318,7 @@ namespace Bit.App.Pages
var task = Task.Run(() => _syncService.FullSyncAsync(true)); var task = Task.Run(() => _syncService.FullSyncAsync(true));
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
_messagingService.Send("listenYubiKeyOTP", false); _messagingService.Send("listenYubiKeyOTP", false);
_broadcasterService.Unsubscribe(nameof(TwoFactorPage)); _broadcasterService.Unsubscribe(nameof(TwoFactorPage));
@@ -326,6 +330,27 @@ namespace Bit.App.Pages
{ {
UpdateTempPasswordAction?.Invoke(); UpdateTempPasswordAction?.Invoke();
} }
else if (decryptOptions?.TrustedDeviceOption != null)
{
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
TwoFactorAuthSuccessAction?.Invoke();
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
}
else else
{ {
TwoFactorAuthSuccessAction?.Invoke(); TwoFactorAuthSuccessAction?.Invoke();

View File

@@ -91,7 +91,7 @@ namespace Bit.Core.Abstractions
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id); Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode); Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved); Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest); Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType);
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier); Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier); Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier);
Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest deviceRequest); Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest deviceRequest);

View File

@@ -32,7 +32,10 @@ namespace Bit.Core.Abstractions
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync(); Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync(); Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id); Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode); /// <summary>
/// Gets a passwordless login request by <paramref name="id"/> and <paramref name="accessCode"/>. No authentication required.
/// </summary>
Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved); Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType); Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);

View File

@@ -183,6 +183,8 @@ namespace Bit.Core.Abstractions
Task<string> GetPreLoginEmailAsync(); Task<string> GetPreLoginEmailAsync();
Task SetPreLoginEmailAsync(string value); Task SetPreLoginEmailAsync(string value);
Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null); Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null);
Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null);
Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null);
string GetLocale(); string GetLocale();
void SetLocale(string locale); void SetLocale(string locale);
ConfigResponse GetConfigs(); ConfigResponse GetConfigs();

View File

@@ -124,6 +124,7 @@ namespace Bit.Core
public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}"; public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}";
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}"; public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}"; public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
public static string PendingAdminAuthRequest(string userId) => $"pendingAdminAuthRequest_{userId}";
[Obsolete] [Obsolete]
public static string KeyKey(string userId) => $"key_{userId}"; public static string KeyKey(string userId) => $"key_{userId}";
[Obsolete] [Obsolete]

View File

@@ -0,0 +1,10 @@
using System;
namespace Bit.Core.Models.Domain
{
public class PendingAdminAuthRequest
{
public string Id { get; set; }
public byte[] PrivateKey { get; set; }
}
}

View File

@@ -591,9 +591,9 @@ namespace Bit.Core.Services
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}/response?code={accessCode}", null, false, true); return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}/response?code={accessCode}", null, false, true);
} }
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest) public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType)
{ {
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, $"/auth-requests", passwordlessCreateLoginRequest, false, true); return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, authRequestType == AuthRequestType.AdminApproval ? "/auth-requests/admin-request" : "/auth-requests", passwordlessCreateLoginRequest, authRequestType == AuthRequestType.AdminApproval, true);
} }
public Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string encKey, string encMasterPasswordHash, string deviceIdentifier, bool requestApproved) public Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string encKey, string encMasterPasswordHash, string deviceIdentifier, bool requestApproved)

View File

@@ -198,11 +198,34 @@ namespace Bit.Core.Services
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy); return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
} }
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered) public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash)
{ {
var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey); var decryptedKey = await _cryptoService.RsaDecryptAsync(encryptedAuthRequestKey, decryptionKey);
var decPasswordHash = await _cryptoService.RsaDecryptAsync(localHashedPasswordCiphered, decryptionKey);
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new MasterKey(decKey), null, null, // On SSO flow user is already AuthN
if (await _stateService.IsAuthenticatedAsync())
{
if (string.IsNullOrEmpty(masterKeyHash))
{
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
}
else
{
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(new MasterKey(decryptedKey));
await _cryptoService.SetUserKeyAsync(userKey);
}
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
return null;
}
if (string.IsNullOrEmpty(masterKeyHash) && decryptionKey != null)
{
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
return null;
}
var decKeyHash = await _cryptoService.RsaDecryptAsync(masterKeyHash, decryptionKey);
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decKeyHash), null, null, null, new MasterKey(decryptedKey), null, null,
null, null, authRequestId: authRequestId); null, null, authRequestId: authRequestId);
} }
@@ -474,10 +497,12 @@ namespace Bit.Core.Services
_messagingService.Send("accountAdded"); _messagingService.Send("accountAdded");
if (_setCryptoKeys) if (_setCryptoKeys)
{ {
if (localHashedPassword != null) if (localHashedPassword != null)
{ {
await _cryptoService.SetPasswordHashAsync(localHashedPassword); await _cryptoService.SetPasswordHashAsync(localHashedPassword);
await _cryptoService.SetMasterKeyAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey);
} }
if (code == null || tokenResponse.Key != null) if (code == null || tokenResponse.Key != null)
@@ -505,6 +530,14 @@ namespace Bit.Core.Services
} }
} }
// Login with Device
if (masterKey != null && !string.IsNullOrEmpty(authRequestId))
{
await _cryptoService.SetMasterKeyAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey);
}
// User doesn't have a key pair yet (old account), let's generate one for them. // User doesn't have a key pair yet (old account), let's generate one for them.
if (tokenResponse.PrivateKey == null) if (tokenResponse.PrivateKey == null)
{ {
@@ -557,7 +590,6 @@ namespace Bit.Core.Services
); );
await _apiService.PostSetKeyConnectorKey(setPasswordRequest); await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
} }
} }
_authedUserId = _tokenService.GetUserId(); _authedUserId = _tokenService.GetUserId();
@@ -594,14 +626,14 @@ namespace Bit.Core.Services
var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList(); var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
return await PopulateFingerprintPhrasesAsync(activeRequests); return await PopulateFingerprintPhrasesAsync(activeRequests);
} }
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id) public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
{ {
var response = await _apiService.GetAuthRequestAsync(id); var response = await _apiService.GetAuthRequestAsync(id);
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync()); return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
} }
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode) /// <inheritdoc />
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode)
{ {
return await _apiService.GetAuthResponseAsync(id, accessCode); return await _apiService.GetAuthResponseAsync(id, accessCode);
} }
@@ -631,7 +663,7 @@ namespace Bit.Core.Services
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, fingerprintPhrase); var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, authRequestType, fingerprintPhrase);
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest); var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest, authRequestType);
if (response != null) if (response != null)
{ {

View File

@@ -336,12 +336,16 @@ namespace Bit.Core.Services
public async Task<string> GetUserKeyMasterKeyAsync(string userId = null) public async Task<string> GetUserKeyMasterKeyAsync(string userId = null)
{ {
return await _storageMediatorService.GetAsync<string>(Constants.UserKeyKey(userId), false); var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
return await _storageMediatorService.GetAsync<string>(Constants.UserKeyKey(reconciledOptions.UserId), false);
} }
public async Task SetUserKeyMasterKeyAsync(string value, string userId = null) public async Task SetUserKeyMasterKeyAsync(string value, string userId = null)
{ {
await _storageMediatorService.SaveAsync(Constants.UserKeyKey(userId), value, false); var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
await _storageMediatorService.SaveAsync(Constants.UserKeyKey(reconciledOptions.UserId), value, false);
} }
public async Task<bool> CanAccessPremiumAsync(string userId = null) public async Task<bool> CanAccessPremiumAsync(string userId = null)
@@ -1347,6 +1351,20 @@ namespace Bit.Core.Services
await _storageMediatorService.SaveAsync(Constants.ShouldTrustDevice, value); await _storageMediatorService.SaveAsync(Constants.ShouldTrustDevice, value);
} }
public async Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
return await _storageMediatorService.GetAsync<PendingAdminAuthRequest>(Constants.PendingAdminAuthRequest(reconciledOptions.UserId), true);
}
public async Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await _storageMediatorService.SaveAsync(Constants.PendingAdminAuthRequest(reconciledOptions.UserId), value, true);
}
public ConfigResponse GetConfigs() public ConfigResponse GetConfigs()
{ {
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey); return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);

View File

@@ -568,6 +568,7 @@ namespace Bit.iOS.Autofill
{ {
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue(); vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
if (authingWithSso) if (authingWithSso)
{ {
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow()); vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());

View File

@@ -590,6 +590,7 @@ namespace Bit.iOS.Extension
{ {
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue(); vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
if (authingWithSso) if (authingWithSso)
{ {
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow()); vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());

View File

@@ -390,6 +390,7 @@ namespace Bit.iOS.ShareExtension
{ {
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue(); vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
if (authingWithSso) if (authingWithSso)
{ {
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow()); vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());