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:
@@ -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");
|
||||||
|
|||||||
@@ -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 () =>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
10
src/Core/Models/Domain/PendingAdminAuthRequest.cs
Normal file
10
src/Core/Models/Domain/PendingAdminAuthRequest.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
Reference in New Issue
Block a user