mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-22 11:35:21 +01:00
[KeyConnector] Add support for key connector OTP (#1633)
* initial commit - add UsesKeyConnector to UserService - add models - begin work on authentication * finish auth workflow for key connector sso login - finish api call for get user key - start api calls for posts to key connector * Bypass lock page if already unlocked * Move logic to KeyConnectorService, log out if no pin or biometric is set * Disable password reprompt when using key connector * hide password reprompt checkbox when editing or adding cipher * add PostUserKey and PostSetKeyConnector calls * add ConvertMasterPasswordPage * add functionality to RemoveMasterPasswordPage - rename Convert to Remove * Hide Change Master Password button if using key connector * Add OTP verification for export component * Update src/App/Pages/Vault/AddEditPage.xaml.cs Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * remove toolbar item "close" * Update src/Core/Models/Request/KeyConnectorUserKeyRequest.cs Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * remove new line in resource string - format warning as two labels - set label in code behind for loading simultaneously * implement GetAndSetKey in KeyConnectorService - ignore EnvironmentService call * remove unnecesary orgIdentifier * move RemoveMasterPasswordPage call to LockPage * add spacing to export vault page * log out if no PIN or bio on lock page with key connector * Delete excessive whitespace * Delete excessive whitespace * Change capitalisation of OTP * add default value to models for backwards compatibility * remove this keyword * actually handle exceptions * move RemoveMasterPasswordPage to TabPage using messaging service * add minor improvements * remove 'this.' Co-authored-by: Hinton <oscar@oscarhinton.com> Co-authored-by: Thomas Rittson <trittson@bitwarden.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
parent
90b62d61ae
commit
13869b5a1b
@ -7,5 +7,7 @@ namespace Bit.App.Abstractions
|
||||
string[] ProtectedFields { get; }
|
||||
|
||||
Task<bool> ShowPasswordPromptAsync();
|
||||
|
||||
Task<bool> Enabled();
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +140,15 @@ namespace Bit.App
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (message.Command == "convertAccountToKeyConnector")
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await Application.Current.MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ namespace Bit.App.Pages
|
||||
{
|
||||
public partial class LockPage : BaseContentPage
|
||||
{
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly AppOptions _appOptions;
|
||||
private readonly bool _autoPromptBiometric;
|
||||
private readonly LockPageViewModel _vm;
|
||||
@ -21,7 +20,6 @@ namespace Bit.App.Pages
|
||||
|
||||
public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true)
|
||||
{
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_appOptions = appOptions;
|
||||
_autoPromptBiometric = autoPromptBiometric;
|
||||
InitializeComponent();
|
||||
@ -130,6 +128,7 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ namespace Bit.App.Pages
|
||||
private readonly IEnvironmentService _environmentService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IBiometricService _biometricService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
|
||||
private string _email;
|
||||
private bool _showPassword;
|
||||
@ -54,6 +55,7 @@ namespace Bit.App.Pages
|
||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
|
||||
PageTitle = AppResources.VerifyMasterPassword;
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
@ -124,6 +126,12 @@ namespace Bit.App.Pages
|
||||
_pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
PinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
|
||||
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
|
||||
|
||||
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||
if (await _keyConnectorService.GetUsesKeyConnector() && !(BiometricLock || PinLock))
|
||||
{
|
||||
await _vaultTimeoutService.LogOutAsync();
|
||||
}
|
||||
_email = await _userService.GetEmailAsync();
|
||||
var webVault = _environmentService.GetWebVaultUrl();
|
||||
if (string.IsNullOrWhiteSpace(webVault))
|
||||
|
@ -116,7 +116,14 @@ namespace Bit.App.Pages
|
||||
{
|
||||
RestoreAppOptionsFromCopy();
|
||||
await AppHelpers.ClearPreviousPage();
|
||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
33
src/App/Pages/Accounts/RemoveMasterPasswordPage.xaml
Normal file
33
src/App/Pages/Accounts/RemoveMasterPasswordPage.xaml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Pages.RemoveMasterPasswordPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="pages:RemoveMasterPasswordPageViewModel"
|
||||
Title="{Binding PageTitle}">
|
||||
|
||||
<ContentPage.BindingContext>
|
||||
<pages:RemoveMasterPasswordPageViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<StackLayout Spacing="20"
|
||||
Padding="10, 5">
|
||||
<StackLayout Spacing="18"
|
||||
Padding="30">
|
||||
<Label x:Name="_warningLabel"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
<Label x:Name="_warningLabel2"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
</StackLayout>
|
||||
<StackLayout Spacing="5">
|
||||
<Button Text="{u:I18n Continue}"
|
||||
StyleClass="btn-primary"
|
||||
Clicked="Continue_Clicked" />
|
||||
<Button Text="{u:I18n LeaveOrganization}"
|
||||
Clicked="LeaveOrg_Clicked" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
|
||||
</pages:BaseContentPage>
|
58
src/App/Pages/Accounts/RemoveMasterPasswordPage.xaml.cs
Normal file
58
src/App/Pages/Accounts/RemoveMasterPasswordPage.xaml.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class RemoveMasterPasswordPage : BaseContentPage
|
||||
{
|
||||
private readonly RemoveMasterPasswordPageViewModel _vm;
|
||||
|
||||
public Action NavigateAction { get; set; }
|
||||
|
||||
public RemoveMasterPasswordPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = BindingContext as RemoveMasterPasswordPageViewModel;
|
||||
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
await _vm.Init();
|
||||
_warningLabel.Text = string.Format(AppResources.RemoveMasterPasswordWarning,
|
||||
_vm.Organization.Name);
|
||||
_warningLabel2.Text = AppResources.RemoveMasterPasswordWarning2;
|
||||
}
|
||||
|
||||
private async void Continue_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await _vm.MigrateAccount();
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void LeaveOrg_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
var confirm = await DisplayAlert(AppResources.LeaveOrganization,
|
||||
string.Format(AppResources.LeaveOrganizationName, _vm.Organization.Name),
|
||||
AppResources.Yes, AppResources.No);
|
||||
if (confirm)
|
||||
{
|
||||
await _vm.LeaveOrganization();
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnDisappearing()
|
||||
{
|
||||
NavigateAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
56
src/App/Pages/Accounts/RemoveMasterPasswordPageViewModel.cs
Normal file
56
src/App/Pages/Accounts/RemoveMasterPasswordPageViewModel.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class RemoveMasterPasswordPageViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IApiService _apiService;
|
||||
private readonly ISyncService _syncService;
|
||||
|
||||
public Organization Organization;
|
||||
|
||||
public RemoveMasterPasswordPageViewModel()
|
||||
{
|
||||
PageTitle = AppResources.RemoveMasterPassword;
|
||||
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
Organization = await _keyConnectorService.GetManagingOrganization();
|
||||
}
|
||||
|
||||
public async Task MigrateAccount()
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
|
||||
await _keyConnectorService.MigrateUser();
|
||||
await _syncService.FullSyncAsync(true);
|
||||
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
}
|
||||
|
||||
public async Task LeaveOrganization()
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
|
||||
await _apiService.PostLeaveOrganization(Organization.Id);
|
||||
await _syncService.FullSyncAsync(true);
|
||||
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -25,8 +25,9 @@
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ScrollView>
|
||||
<StackLayout Spacing="20">
|
||||
<StackLayout StyleClass="box">
|
||||
<StackLayout>
|
||||
<StackLayout StyleClass="box"
|
||||
Spacing="20">
|
||||
<Frame
|
||||
IsVisible="{Binding DisablePrivateVaultPolicyEnabled}"
|
||||
Padding="10"
|
||||
@ -39,7 +40,7 @@
|
||||
StyleClass="text-muted, text-sm, text-bold"
|
||||
HorizontalTextAlignment="Center" />
|
||||
</Frame>
|
||||
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
Text="{u:I18n FileFormat}"
|
||||
StyleClass="box-label" />
|
||||
@ -53,6 +54,7 @@
|
||||
</StackLayout>
|
||||
<Grid StyleClass="box-row">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
@ -60,20 +62,30 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{u:I18n MasterPassword}"
|
||||
StyleClass="box-label"
|
||||
<Button x:Name="_requestOTP"
|
||||
Text="{u:I18n RequestOTP}"
|
||||
Clicked="RequestOTP_Clicked"
|
||||
HorizontalOptions="Fill"
|
||||
VerticalOptions="End"
|
||||
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
|
||||
IsVisible="{Binding UseOTPVerification}"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,0,0,10"/>
|
||||
<Label
|
||||
Text="{Binding SecretName}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0" />
|
||||
<controls:MonoEntry
|
||||
x:Name="_masterPassword"
|
||||
Text="{Binding MasterPassword}"
|
||||
x:Name="_secret"
|
||||
Text="{Binding Secret}"
|
||||
StyleClass="box-value"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsTextPredictionEnabled="False"
|
||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
|
||||
Grid.Row="1"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
ReturnType="Go"
|
||||
ReturnCommand="{Binding ExportVaultCommand}" />
|
||||
@ -81,16 +93,16 @@
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowPasswordIcon}"
|
||||
Command="{Binding TogglePasswordCommand}"
|
||||
Grid.Row="0"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
||||
</Grid>
|
||||
<Label
|
||||
Text="{u:I18n ExportVaultMasterPasswordDescription}"
|
||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||
<StackLayout Spacing="20">
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
Text="{Binding InstructionText}"
|
||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||
<Button Text="{u:I18n ExportVault}"
|
||||
Clicked="ExportVault_Clicked"
|
||||
HorizontalOptions="Fill"
|
||||
|
@ -17,7 +17,7 @@ namespace Bit.App.Pages
|
||||
_vm = BindingContext as ExportVaultPageViewModel;
|
||||
_vm.Page = this;
|
||||
_fileFormatPicker.ItemDisplayBinding = new Binding("Value");
|
||||
MasterPasswordEntry = _masterPassword;
|
||||
SecretEntry = _secret;
|
||||
}
|
||||
|
||||
protected async override void OnAppearing()
|
||||
@ -39,7 +39,7 @@ namespace Bit.App.Pages
|
||||
});
|
||||
}
|
||||
});
|
||||
RequestFocus(_masterPassword);
|
||||
RequestFocus(_secret);
|
||||
}
|
||||
|
||||
protected async override void OnDisappearing()
|
||||
@ -48,7 +48,7 @@ namespace Bit.App.Pages
|
||||
_broadcasterService.Unsubscribe(nameof(ExportVaultPage));
|
||||
}
|
||||
|
||||
public Entry MasterPasswordEntry { get; set; }
|
||||
public Entry SecretEntry { get; set; }
|
||||
|
||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
@ -66,6 +66,15 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private async void RequestOTP_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await _vm.RequestOTP();
|
||||
_requestOTP.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FileFormat_Changed(object sender, EventArgs e)
|
||||
{
|
||||
_vm?.UpdateWarning();
|
||||
|
@ -4,10 +4,8 @@ using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Enums;
|
||||
#if !FDROID
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
@ -21,26 +19,33 @@ namespace Bit.App.Pages
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IExportService _exportService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
private readonly IApiService _apiService;
|
||||
|
||||
private int _fileFormatSelectedIndex;
|
||||
private string _exportWarningMessage;
|
||||
private bool _showPassword;
|
||||
private string _masterPassword;
|
||||
private string _secret;
|
||||
private byte[] _exportResult;
|
||||
private string _defaultFilename;
|
||||
private bool _initialized = false;
|
||||
private bool _useOTPVerification = false;
|
||||
private string _secretName;
|
||||
private string _instructionText;
|
||||
|
||||
public ExportVaultPageViewModel()
|
||||
{
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
|
||||
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||
|
||||
PageTitle = AppResources.ExportVault;
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
@ -59,7 +64,19 @@ namespace Bit.App.Pages
|
||||
_initialized = true;
|
||||
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
|
||||
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
|
||||
UseOTPVerification = await _keyConnectorService.GetUsesKeyConnector();
|
||||
|
||||
if (UseOTPVerification)
|
||||
{
|
||||
InstructionText = _i18nService.T("ExportVaultOTPDescription");
|
||||
SecretName = _i18nService.T("VerificationCode");
|
||||
}
|
||||
else
|
||||
{
|
||||
InstructionText = _i18nService.T("ExportVaultMasterPasswordDescription");
|
||||
SecretName = _i18nService.T("MasterPassword");
|
||||
}
|
||||
|
||||
UpdateWarning();
|
||||
}
|
||||
|
||||
@ -94,10 +111,28 @@ namespace Bit.App.Pages
|
||||
additionalPropertyNames: new string[] {nameof(ShowPasswordIcon)});
|
||||
}
|
||||
|
||||
public string MasterPassword
|
||||
public bool UseOTPVerification
|
||||
{
|
||||
get => _masterPassword;
|
||||
set => SetProperty(ref _masterPassword, value);
|
||||
get => _useOTPVerification;
|
||||
set => SetProperty(ref _useOTPVerification, value);
|
||||
}
|
||||
|
||||
public string Secret
|
||||
{
|
||||
get => _secret;
|
||||
set => SetProperty(ref _secret, value);
|
||||
}
|
||||
|
||||
public string SecretName
|
||||
{
|
||||
get => _secretName;
|
||||
set => SetProperty(ref _secretName, value);
|
||||
}
|
||||
|
||||
public string InstructionText
|
||||
{
|
||||
get => _instructionText;
|
||||
set => SetProperty(ref _instructionText, value);
|
||||
}
|
||||
|
||||
public Command TogglePasswordCommand { get; }
|
||||
@ -107,27 +142,13 @@ namespace Bit.App.Pages
|
||||
public void TogglePassword()
|
||||
{
|
||||
ShowPassword = !ShowPassword;
|
||||
(Page as ExportVaultPage).MasterPasswordEntry.Focus();
|
||||
(Page as ExportVaultPage).SecretEntry.Focus();
|
||||
}
|
||||
|
||||
public Command ExportVaultCommand { get; }
|
||||
|
||||
public async Task ExportVaultAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_masterPassword))
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(_i18nService.T("InvalidMasterPassword"));
|
||||
return;
|
||||
}
|
||||
|
||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(_masterPassword, null);
|
||||
MasterPassword = string.Empty;
|
||||
if (!passwordValid)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(_i18nService.T("InvalidMasterPassword"));
|
||||
return;
|
||||
}
|
||||
|
||||
bool userConfirmedExport = await _platformUtilsService.ShowDialogAsync(ExportWarningMessage,
|
||||
_i18nService.T("ExportVaultConfirmationTitle"), _i18nService.T("ExportVault"), _i18nService.T("Cancel"));
|
||||
|
||||
@ -136,6 +157,16 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
|
||||
? VerificationType.OTP
|
||||
: VerificationType.MasterPassword;
|
||||
if (!await _userVerificationService.VerifyUser(Secret, verificationType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Secret = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var data = await _exportService.GetExport(FileFormatOptions[FileFormatSelectedIndex].Key);
|
||||
@ -162,6 +193,11 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RequestOTP()
|
||||
{
|
||||
await _apiService.PostAccountRequestOTP();
|
||||
}
|
||||
|
||||
public async void SaveFileSelected(string contentUri, string filename)
|
||||
{
|
||||
if (_deviceActionService.SaveFile(_exportResult, null, filename ?? _defaultFilename, contentUri))
|
||||
|
@ -27,6 +27,7 @@ namespace Bit.App.Pages
|
||||
private readonly IBiometricService _biometricService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly ILocalizeService _localizeService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
|
||||
private const int CustomVaultTimeoutValue = -100;
|
||||
|
||||
@ -36,6 +37,8 @@ namespace Bit.App.Pages
|
||||
private string _lastSyncDate;
|
||||
private string _vaultTimeoutDisplayValue;
|
||||
private string _vaultTimeoutActionDisplayValue;
|
||||
private bool _showChangeMasterPassword;
|
||||
|
||||
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
||||
new List<KeyValuePair<string, int?>>
|
||||
{
|
||||
@ -74,6 +77,7 @@ namespace Bit.App.Pages
|
||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
|
||||
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
|
||||
PageTitle = AppResources.Settings;
|
||||
@ -116,6 +120,9 @@ namespace Bit.App.Pages
|
||||
_vaultTimeoutDisplayValue = AppResources.Custom;
|
||||
}
|
||||
|
||||
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
||||
!await _keyConnectorService.GetUsesKeyConnector();
|
||||
|
||||
BuildList();
|
||||
}
|
||||
|
||||
@ -460,7 +467,7 @@ namespace Bit.App.Pages
|
||||
new SettingsPageListItem { Name = AppResources.FingerprintPhrase },
|
||||
new SettingsPageListItem { Name = AppResources.LogOut }
|
||||
};
|
||||
if (IncludeLinksWithSubscriptionInfo())
|
||||
if (_showChangeMasterPassword)
|
||||
{
|
||||
accountItems.Insert(0, new SettingsPageListItem { Name = AppResources.ChangeMasterPassword });
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace Bit.App.Pages
|
||||
public class TabsPage : TabbedPage
|
||||
{
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
|
||||
private NavigationPage _groupingsPage;
|
||||
private NavigationPage _sendGroupingsPage;
|
||||
@ -18,6 +19,7 @@ namespace Bit.App.Pages
|
||||
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
||||
{
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
|
||||
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage))
|
||||
{
|
||||
@ -72,6 +74,15 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
if (await _keyConnectorService.UserNeedsMigration())
|
||||
{
|
||||
_messagingService.Send("convertAccountToKeyConnector");
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetToVaultPage()
|
||||
{
|
||||
CurrentPage = _groupingsPage;
|
||||
|
@ -556,7 +556,7 @@
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row, box-row-switch">
|
||||
<StackLayout x:Name="_passwordPrompt" StyleClass="box-row, box-row-switch">
|
||||
<Label
|
||||
Text="{u:I18n PasswordPrompt}"
|
||||
StyleClass="box-label-regular" />
|
||||
|
@ -21,6 +21,7 @@ namespace Bit.App.Pages
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
|
||||
private AddEditPageViewModel _vm;
|
||||
private bool _fromAutofill;
|
||||
@ -40,6 +41,8 @@ namespace Bit.App.Pages
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
|
||||
_appOptions = appOptions;
|
||||
_fromAutofill = fromAutofill;
|
||||
FromAutofillFramework = _appOptions?.FromAutofillFramework ?? false;
|
||||
@ -171,6 +174,8 @@ namespace Bit.App.Pages
|
||||
}
|
||||
_scrollView.Scrolled += (sender, args) => _vm.HandleScroll();
|
||||
});
|
||||
// Hide password reprompt option if using key connector
|
||||
_passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
|
48
src/App/Resources/AppResources.Designer.cs
generated
48
src/App/Resources/AppResources.Designer.cs
generated
@ -2837,6 +2837,12 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExportVaultOTPDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportVaultOTPDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExportVaultWarning {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportVaultWarning", resourceCulture);
|
||||
@ -3599,6 +3605,36 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string RemoveMasterPassword {
|
||||
get {
|
||||
return ResourceManager.GetString("RemoveMasterPassword", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RemoveMasterPasswordWarning {
|
||||
get {
|
||||
return ResourceManager.GetString("RemoveMasterPasswordWarning", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RemoveMasterPasswordWarning2 {
|
||||
get {
|
||||
return ResourceManager.GetString("RemoveMasterPasswordWarning2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LeaveOrganization {
|
||||
get {
|
||||
return ResourceManager.GetString("LeaveOrganization", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LeaveOrganizationName {
|
||||
get {
|
||||
return ResourceManager.GetString("LeaveOrganizationName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Fido2Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Fido2Title", resourceCulture);
|
||||
@ -3658,5 +3694,17 @@ namespace Bit.App.Resources {
|
||||
return ResourceManager.GetString("DisablePersonalVaultExportPolicyInEffect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string InvalidVerificationCode {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidVerificationCode", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RequestOTP {
|
||||
get {
|
||||
return ResourceManager.GetString("RequestOTP", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1610,6 +1610,9 @@
|
||||
</data>
|
||||
<data name="ExportVaultMasterPasswordDescription" xml:space="preserve">
|
||||
<value>Enter your master password to export your vault data.</value>
|
||||
</data>
|
||||
<data name="ExportVaultOTPDescription" xml:space="preserve">
|
||||
<value>Enter the verification code to export your vault data.</value>
|
||||
</data>
|
||||
<data name="ExportVaultWarning" xml:space="preserve">
|
||||
<value>This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it.</value>
|
||||
@ -2033,6 +2036,21 @@
|
||||
<data name="UpdatePasswordError" xml:space="preserve">
|
||||
<value>Currently unable to update password</value>
|
||||
</data>
|
||||
<data name="RemoveMasterPassword" xml:space="preserve">
|
||||
<value>Remove Master Password</value>
|
||||
</data>
|
||||
<data name="RemoveMasterPasswordWarning" xml:space="preserve">
|
||||
<value>{0} is using SSO with customer-managed encryption. Continuing will remove your Master Password from your account and require SSO to login.</value>
|
||||
</data>
|
||||
<data name="RemoveMasterPasswordWarning2" xml:space="preserve">
|
||||
<value>If you do not want to remove your Master Password, you may leave this organization.</value>
|
||||
</data>
|
||||
<data name="LeaveOrganization" xml:space="preserve">
|
||||
<value>Leave Organization</value>
|
||||
</data>
|
||||
<data name="LeaveOrganizationName" xml:space="preserve">
|
||||
<value>Leave {0}?</value>
|
||||
</data>
|
||||
<data name="Fido2Title" xml:space="preserve">
|
||||
<value>FIDO2 WebAuthn</value>
|
||||
</data>
|
||||
@ -2063,4 +2081,10 @@
|
||||
<data name="DisablePersonalVaultExportPolicyInEffect">
|
||||
<value>One or more organization policies prevents your from exporting your personal vault.</value>
|
||||
</data>
|
||||
<data name="InvalidVerificationCode" xml:space="preserve">
|
||||
<value>Invalid Verification Code.</value>
|
||||
</data>
|
||||
<data name="RequestOTP" xml:space="preserve">
|
||||
<value>Request one-time password</value>
|
||||
</data>
|
||||
</root>
|
||||
|
@ -3,6 +3,7 @@ using Bit.Core.Abstractions;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using System;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
@ -21,6 +22,11 @@ namespace Bit.App.Services
|
||||
|
||||
public async Task<bool> ShowPasswordPromptAsync()
|
||||
{
|
||||
if (!await Enabled())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Func<string, Task<bool>> validator = async (string password) =>
|
||||
{
|
||||
// Assume user has canceled.
|
||||
@ -34,5 +40,11 @@ namespace Bit.App.Services
|
||||
|
||||
return await _platformUtilsService.ShowPasswordDialogAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, validator);
|
||||
}
|
||||
|
||||
public async Task<bool> Enabled()
|
||||
{
|
||||
var keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
return !await keyConnectorService.GetUsesKeyConnector();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ namespace Bit.Core.Abstractions
|
||||
Task<SyncResponse> GetSyncAsync();
|
||||
Task PostAccountKeysAsync(KeysRequest request);
|
||||
Task PostAccountVerifyPasswordAsync(PasswordVerificationRequest request);
|
||||
Task PostAccountRequestOTP();
|
||||
Task PostAccountVerifyOTPAsync(VerifyOTPRequest request);
|
||||
Task<CipherResponse> PostCipherAsync(CipherRequest request);
|
||||
Task<CipherResponse> PostCipherCreateAsync(CipherCreateRequest request);
|
||||
Task<FolderResponse> PostFolderAsync(FolderRequest request);
|
||||
@ -63,6 +65,11 @@ namespace Bit.Core.Abstractions
|
||||
Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier);
|
||||
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
|
||||
OrganizationUserResetPasswordEnrollmentRequest request);
|
||||
Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl);
|
||||
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);
|
||||
|
16
src/Core/Abstractions/IKeyConnectorService.cs
Normal file
16
src/Core/Abstractions/IKeyConnectorService.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IKeyConnectorService
|
||||
{
|
||||
Task SetUsesKeyConnector(bool usesKeyConnector);
|
||||
Task<bool> GetUsesKeyConnector();
|
||||
Task<bool> UserNeedsMigration();
|
||||
Task MigrateUser();
|
||||
Task GetAndSetKey(string url);
|
||||
Task<Organization> GetManagingOrganization();
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ namespace Bit.Core.Abstractions
|
||||
string GetIssuer();
|
||||
string GetName();
|
||||
bool GetPremium();
|
||||
bool GetIsExternal();
|
||||
Task<string> GetRefreshTokenAsync();
|
||||
Task<string> GetTokenAsync();
|
||||
Task ToggleTokensAsync();
|
||||
|
10
src/Core/Abstractions/IUserVerificationService.cs
Normal file
10
src/Core/Abstractions/IUserVerificationService.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IUserVerificationService
|
||||
{
|
||||
Task<bool> VerifyUser(string secret, VerificationType verificationType);
|
||||
}
|
||||
}
|
8
src/Core/Enums/VerificationType.cs
Normal file
8
src/Core/Enums/VerificationType.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum VerificationType
|
||||
{
|
||||
MasterPassword = 0,
|
||||
OTP = 1,
|
||||
}
|
||||
}
|
@ -29,6 +29,8 @@ namespace Bit.Core.Models.Data
|
||||
MaxStorageGb = response.MaxStorageGb;
|
||||
Permissions = response.Permissions ?? new Permissions();
|
||||
Identifier = response.Identifier;
|
||||
UsesKeyConnector = response.UsesKeyConnector;
|
||||
KeyConnectorUrl = response.KeyConnectorUrl;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
@ -50,5 +52,7 @@ namespace Bit.Core.Models.Data
|
||||
public short? MaxStorageGb { get; set; }
|
||||
public Permissions Permissions { get; set; } = new Permissions();
|
||||
public string Identifier { get; set; }
|
||||
public bool UsesKeyConnector { get; set; }
|
||||
public string KeyConnectorUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ namespace Bit.Core.Models.Domain
|
||||
MaxStorageGb = obj.MaxStorageGb;
|
||||
Permissions = obj.Permissions ?? new Permissions();
|
||||
Identifier = obj.Identifier;
|
||||
UsesKeyConnector = obj.UsesKeyConnector;
|
||||
KeyConnectorUrl = obj.KeyConnectorUrl;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
@ -50,6 +52,8 @@ namespace Bit.Core.Models.Domain
|
||||
public short? MaxStorageGb { get; set; }
|
||||
public Permissions Permissions { get; set; } = new Permissions();
|
||||
public string Identifier { get; set; }
|
||||
public bool UsesKeyConnector { get; set; }
|
||||
public string KeyConnectorUrl { get; set; }
|
||||
|
||||
public bool CanAccess
|
||||
{
|
||||
|
13
src/Core/Models/Request/KeyConnectorUserKeyRequest.cs
Normal file
13
src/Core/Models/Request/KeyConnectorUserKeyRequest.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class KeyConnectorUserKeyRequest
|
||||
{
|
||||
public string Key { get; set; }
|
||||
|
||||
public KeyConnectorUserKeyRequest(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
}
|
24
src/Core/Models/Request/SetKeyConnectorKeyRequest.cs
Normal file
24
src/Core/Models/Request/SetKeyConnectorKeyRequest.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class SetKeyConnectorKeyRequest
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public KeysRequest Keys { get; set; }
|
||||
public KdfType Kdf { get; set; }
|
||||
public int? KdfIterations { get; set; }
|
||||
public string OrgIdentifier { get; set; }
|
||||
|
||||
public SetKeyConnectorKeyRequest(string key, KeysRequest keys,
|
||||
KdfType kdf, int? kdfIterations, string orgIdentifier)
|
||||
{
|
||||
this.Key = key;
|
||||
this.Keys = keys;
|
||||
this.Kdf = kdf;
|
||||
this.KdfIterations = kdfIterations;
|
||||
this.OrgIdentifier = orgIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
13
src/Core/Models/Request/VerifyOTPRequest.cs
Normal file
13
src/Core/Models/Request/VerifyOTPRequest.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class VerifyOTPRequest
|
||||
{
|
||||
public string OTP;
|
||||
|
||||
public VerifyOTPRequest(string otp)
|
||||
{
|
||||
OTP = otp;
|
||||
}
|
||||
}
|
||||
}
|
@ -21,5 +21,6 @@ namespace Bit.Core.Models.Response
|
||||
public KdfType Kdf { get; set; }
|
||||
public int? KdfIterations { get; set; }
|
||||
public bool ForcePasswordReset { get; set; }
|
||||
public string KeyConnectorUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
9
src/Core/Models/Response/KeyConnectorUserKeyResponse.cs
Normal file
9
src/Core/Models/Response/KeyConnectorUserKeyResponse.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Core.Models.Response
|
||||
{
|
||||
public class KeyConnectorUserKeyResponse
|
||||
{
|
||||
public string Key { get; set; }
|
||||
}
|
||||
}
|
@ -25,5 +25,8 @@ namespace Bit.Core.Models.Response
|
||||
public bool Enabled { get; set; }
|
||||
public Permissions Permissions { get; set; } = new Permissions();
|
||||
public string Identifier { get; set; }
|
||||
public bool UsesKeyConnector { get; set; }
|
||||
public string KeyConnectorUrl { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -18,5 +18,6 @@ namespace Bit.Core.Models.Response
|
||||
public string SecurityStamp { get; set; }
|
||||
public bool ForcePasswordReset { get; set; }
|
||||
public List<ProfileOrganizationResponse> Organizations { get; set; }
|
||||
public bool UsesKeyConnector { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -178,12 +178,33 @@ namespace Bit.Core.Services
|
||||
true, false);
|
||||
}
|
||||
|
||||
public Task PostAccountRequestOTP()
|
||||
{
|
||||
return SendAsync<object, object>(HttpMethod.Post, "/accounts/request-otp", null, true, false);
|
||||
}
|
||||
|
||||
public Task PostAccountVerifyOTPAsync(VerifyOTPRequest request)
|
||||
{
|
||||
return SendAsync<VerifyOTPRequest, object>(HttpMethod.Post, "/accounts/verify-otp", request,
|
||||
true, false);
|
||||
}
|
||||
|
||||
public Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request)
|
||||
{
|
||||
return SendAsync<UpdateTempPasswordRequest, object>(HttpMethod.Put, "/accounts/update-temp-password",
|
||||
request, true, false);
|
||||
}
|
||||
|
||||
|
||||
public Task PostConvertToKeyConnector()
|
||||
{
|
||||
return SendAsync<object, object>(HttpMethod.Post, "/accounts/convert-to-key-connector", null, true, false);
|
||||
}
|
||||
|
||||
public Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request)
|
||||
{
|
||||
return SendAsync<SetKeyConnectorKeyRequest>(HttpMethod.Post, "/accounts/set-key-connector-key", request, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Folder APIs
|
||||
@ -422,9 +443,14 @@ namespace Bit.Core.Services
|
||||
return SendAsync<object, OrganizationAutoEnrollStatusResponse>(HttpMethod.Get,
|
||||
$"/organizations/{identifier}/auto-enroll-status", null, true, true);
|
||||
}
|
||||
|
||||
|
||||
public Task PostLeaveOrganization(string id)
|
||||
{
|
||||
return SendAsync<object, object>(HttpMethod.Post, $"/organizations/{id}/leave", null, true, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Organization User APIs
|
||||
|
||||
public Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
|
||||
@ -433,7 +459,71 @@ namespace Bit.Core.Services
|
||||
return SendAsync<OrganizationUserResetPasswordEnrollmentRequest, object>(HttpMethod.Put,
|
||||
$"/organizations/{orgId}/users/{userId}/reset-password-enrollment", request, true, false);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Key Connector
|
||||
|
||||
public async Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl)
|
||||
{
|
||||
using (var requestMessage = new HttpRequestMessage())
|
||||
{
|
||||
var authHeader = await GetActiveBearerTokenAsync();
|
||||
|
||||
requestMessage.Version = new Version(1, 0);
|
||||
requestMessage.Method = HttpMethod.Get;
|
||||
requestMessage.RequestUri = new Uri(string.Concat(keyConnectorUrl, "/user-keys"));
|
||||
requestMessage.Headers.Add("Authorization", string.Concat("Bearer ", authHeader));
|
||||
|
||||
HttpResponseMessage response;
|
||||
try
|
||||
{
|
||||
response = await _httpClient.SendAsync(requestMessage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApiException(HandleWebError(e));
|
||||
}
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = await HandleErrorAsync(response, false, true);
|
||||
throw new ApiException(error);
|
||||
}
|
||||
var responseJsonString = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<KeyConnectorUserKeyResponse>(responseJsonString);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request)
|
||||
{
|
||||
using (var requestMessage = new HttpRequestMessage())
|
||||
{
|
||||
var authHeader = await GetActiveBearerTokenAsync();
|
||||
|
||||
requestMessage.Version = new Version(1, 0);
|
||||
requestMessage.Method = HttpMethod.Post;
|
||||
requestMessage.RequestUri = new Uri(string.Concat(keyConnectorUrl, "/user-keys"));
|
||||
requestMessage.Headers.Add("Authorization", string.Concat("Bearer ", authHeader));
|
||||
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(request, _jsonSettings),
|
||||
Encoding.UTF8, "application/json");
|
||||
|
||||
HttpResponseMessage response;
|
||||
try
|
||||
{
|
||||
response = await _httpClient.SendAsync(requestMessage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApiException(HandleWebError(e));
|
||||
}
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = await HandleErrorAsync(response, false, true);
|
||||
throw new ApiException(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
@ -12,6 +12,7 @@ namespace Bit.Core.Services
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly IApiService _apiService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ITokenService _tokenService;
|
||||
@ -20,12 +21,14 @@ namespace Bit.Core.Services
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly bool _setCryptoKeys;
|
||||
|
||||
private SymmetricCryptoKey _key;
|
||||
|
||||
public AuthService(
|
||||
ICryptoService cryptoService,
|
||||
ICryptoFunctionService cryptoFunctionService,
|
||||
IApiService apiService,
|
||||
IUserService userService,
|
||||
ITokenService tokenService,
|
||||
@ -34,9 +37,11 @@ namespace Bit.Core.Services
|
||||
IPlatformUtilsService platformUtilsService,
|
||||
IMessagingService messagingService,
|
||||
IVaultTimeoutService vaultTimeoutService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
bool setCryptoKeys = true)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
_cryptoFunctionService = cryptoFunctionService;
|
||||
_apiService = apiService;
|
||||
_userService = userService;
|
||||
_tokenService = tokenService;
|
||||
@ -45,6 +50,7 @@ namespace Bit.Core.Services
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_messagingService = messagingService;
|
||||
_vaultTimeoutService = vaultTimeoutService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_setCryptoKeys = setCryptoKeys;
|
||||
|
||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||
@ -275,7 +281,7 @@ namespace Bit.Core.Services
|
||||
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
|
||||
string code, string codeVerifier, string redirectUrl, SymmetricCryptoKey key,
|
||||
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null,
|
||||
string captchaToken = null)
|
||||
string captchaToken = null, string orgId = null)
|
||||
{
|
||||
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
|
||||
var appId = await _appIdService.GetAppIdAsync();
|
||||
@ -353,27 +359,75 @@ namespace Bit.Core.Services
|
||||
tokenResponse.Kdf, tokenResponse.KdfIterations);
|
||||
if (_setCryptoKeys)
|
||||
{
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
await _cryptoService.SetKeyHashAsync(localHashedPassword);
|
||||
await _cryptoService.SetEncKeyAsync(tokenResponse.Key);
|
||||
|
||||
// User doesn't have a key pair yet (old account), let's generate one for them.
|
||||
if (tokenResponse.PrivateKey == null)
|
||||
if (key != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var keyPair = await _cryptoService.MakeKeyPairAsync();
|
||||
await _apiService.PostAccountKeysAsync(new KeysRequest
|
||||
{
|
||||
PublicKey = keyPair.Item1,
|
||||
EncryptedPrivateKey = keyPair.Item2.EncryptedString
|
||||
});
|
||||
tokenResponse.PrivateKey = keyPair.Item2.EncryptedString;
|
||||
}
|
||||
catch { }
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
}
|
||||
|
||||
if (localHashedPassword != null)
|
||||
{
|
||||
await _cryptoService.SetKeyHashAsync(localHashedPassword);
|
||||
}
|
||||
|
||||
if (code == null || tokenResponse.Key != null)
|
||||
{
|
||||
if (tokenResponse.KeyConnectorUrl != null)
|
||||
{
|
||||
await _keyConnectorService.GetAndSetKey(tokenResponse.KeyConnectorUrl);
|
||||
}
|
||||
|
||||
await _cryptoService.SetEncKeyAsync(tokenResponse.Key);
|
||||
|
||||
// User doesn't have a key pair yet (old account), let's generate one for them.
|
||||
if (tokenResponse.PrivateKey == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var keyPair = await _cryptoService.MakeKeyPairAsync();
|
||||
await _apiService.PostAccountKeysAsync(new KeysRequest
|
||||
{
|
||||
PublicKey = keyPair.Item1,
|
||||
EncryptedPrivateKey = keyPair.Item2.EncryptedString
|
||||
});
|
||||
tokenResponse.PrivateKey = keyPair.Item2.EncryptedString;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
|
||||
}
|
||||
else if (tokenResponse.KeyConnectorUrl != null)
|
||||
{
|
||||
// SSO Key Connector Onboarding
|
||||
var password = await _cryptoFunctionService.RandomBytesAsync(64);
|
||||
var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.Kdf, tokenResponse.KdfIterations);
|
||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64);
|
||||
await _cryptoService.SetKeyAsync(k);
|
||||
|
||||
var encKey = await _cryptoService.MakeEncKeyAsync(k);
|
||||
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||
var keyPair = await _cryptoService.MakeKeyPairAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await _apiService.PostUserKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("Unable to reach Key Connector", e);
|
||||
}
|
||||
|
||||
var keys = new KeysRequest
|
||||
{
|
||||
PublicKey = keyPair.Item1,
|
||||
EncryptedPrivateKey = keyPair.Item2.EncryptedString
|
||||
};
|
||||
var setPasswordRequest = new SetKeyConnectorKeyRequest(
|
||||
encKey.Item2.EncryptedString, keys, tokenResponse.Kdf, tokenResponse.KdfIterations, orgId
|
||||
);
|
||||
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
|
||||
}
|
||||
|
||||
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
|
||||
}
|
||||
|
||||
_vaultTimeoutService.BiometricLocked = false;
|
||||
|
98
src/Core/Services/KeyConnectorService.cs
Normal file
98
src/Core/Services/KeyConnectorService.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class KeyConnectorService : IKeyConnectorService
|
||||
{
|
||||
private const string Keys_UsesKeyConnector = "usesKeyConnector";
|
||||
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IApiService _apiService;
|
||||
|
||||
private bool? _usesKeyConnector;
|
||||
|
||||
public KeyConnectorService(IUserService userService, ICryptoService cryptoService,
|
||||
IStorageService storageService, ITokenService tokenService, IApiService apiService)
|
||||
{
|
||||
_userService = userService;
|
||||
_cryptoService = cryptoService;
|
||||
_storageService = storageService;
|
||||
_tokenService = tokenService;
|
||||
_apiService = apiService;
|
||||
}
|
||||
|
||||
public async Task GetAndSetKey(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userKeyResponse = await _apiService.GetUserKeyFromKeyConnector(url);
|
||||
var keyArr = Convert.FromBase64String(userKeyResponse.Key);
|
||||
var k = new SymmetricCryptoKey(keyArr);
|
||||
await _cryptoService.SetKeyAsync(k);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("Unable to reach Key Connector", e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetUsesKeyConnector(bool usesKeyConnector)
|
||||
{
|
||||
_usesKeyConnector = usesKeyConnector;
|
||||
await _storageService.SaveAsync(Keys_UsesKeyConnector, usesKeyConnector);
|
||||
}
|
||||
|
||||
public async Task<bool> GetUsesKeyConnector()
|
||||
{
|
||||
if (!_usesKeyConnector.HasValue)
|
||||
{
|
||||
_usesKeyConnector = await _storageService.GetAsync<bool>(Keys_UsesKeyConnector);
|
||||
}
|
||||
|
||||
return _usesKeyConnector.Value;
|
||||
}
|
||||
|
||||
public async Task<Organization> GetManagingOrganization()
|
||||
{
|
||||
var orgs = await _userService.GetAllOrganizationAsync();
|
||||
return orgs.Find(o =>
|
||||
o.UsesKeyConnector &&
|
||||
!o.IsAdmin);
|
||||
}
|
||||
|
||||
public async Task MigrateUser()
|
||||
{
|
||||
var organization = await GetManagingOrganization();
|
||||
var key = await _cryptoService.GetKeyAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(key.EncKeyB64);
|
||||
await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("Unable to reach Key Connector", e);
|
||||
}
|
||||
|
||||
await _apiService.PostConvertToKeyConnector();
|
||||
}
|
||||
|
||||
public async Task<bool> UserNeedsMigration()
|
||||
{
|
||||
var loggedInUsingSso = _tokenService.GetIsExternal();
|
||||
var requiredByOrganization = await GetManagingOrganization() != null;
|
||||
var userIsNotUsingKeyConnector = !await GetUsesKeyConnector();
|
||||
|
||||
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ namespace Bit.Core.Services
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly ISendService _sendService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly Func<bool, Task> _logoutCallbackAsync;
|
||||
|
||||
public SyncService(
|
||||
@ -39,6 +40,7 @@ namespace Bit.Core.Services
|
||||
IMessagingService messagingService,
|
||||
IPolicyService policyService,
|
||||
ISendService sendService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
Func<bool, Task> logoutCallbackAsync)
|
||||
{
|
||||
_userService = userService;
|
||||
@ -52,6 +54,7 @@ namespace Bit.Core.Services
|
||||
_messagingService = messagingService;
|
||||
_policyService = policyService;
|
||||
_sendService = sendService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_logoutCallbackAsync = logoutCallbackAsync;
|
||||
}
|
||||
|
||||
@ -329,6 +332,7 @@ namespace Bit.Core.Services
|
||||
await _userService.ReplaceOrganizationsAsync(organizations);
|
||||
await _userService.SetEmailVerifiedAsync(response.EmailVerified);
|
||||
await _userService.SetForcePasswordReset(response.ForcePasswordReset);
|
||||
await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector);
|
||||
}
|
||||
|
||||
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)
|
||||
|
@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@ -230,6 +231,16 @@ namespace Bit.Core.Services
|
||||
return decoded["iss"].Value<string>();
|
||||
}
|
||||
|
||||
public bool GetIsExternal()
|
||||
{
|
||||
var decoded = DecodeToken();
|
||||
if (decoded?["amr"] == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return decoded["amr"].Value<JArray>().Any(t => t.Value<string>() == "external");
|
||||
}
|
||||
|
||||
private async Task<bool> SkipTokenStorage()
|
||||
{
|
||||
var timeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
|
||||
|
69
src/Core/Services/UserVerificationService.cs
Normal file
69
src/Core/Services/UserVerificationService.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class UserVerificationService : IUserVerificationService
|
||||
{
|
||||
private readonly IApiService _apiService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
|
||||
public UserVerificationService(IApiService apiService, IPlatformUtilsService platformUtilsService,
|
||||
II18nService i18nService, ICryptoService cryptoService)
|
||||
{
|
||||
_apiService = apiService;
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_i18nService = i18nService;
|
||||
_cryptoService = cryptoService;
|
||||
}
|
||||
|
||||
async public Task<bool> VerifyUser(string secret, VerificationType verificationType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(secret))
|
||||
{
|
||||
await InvalidSecretErrorAsync(verificationType);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (verificationType == VerificationType.OTP)
|
||||
{
|
||||
var request = new VerifyOTPRequest(secret);
|
||||
try
|
||||
{
|
||||
await _apiService.PostAccountVerifyOTPAsync(request);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await InvalidSecretErrorAsync(verificationType);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(secret, null);
|
||||
if (!passwordValid)
|
||||
{
|
||||
await InvalidSecretErrorAsync(verificationType);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async private Task InvalidSecretErrorAsync(VerificationType verificationType)
|
||||
{
|
||||
var errorMessage = verificationType == VerificationType.OTP
|
||||
? _i18nService.T("InvalidVerificationCode")
|
||||
: _i18nService.T("InvalidMasterPassword");
|
||||
|
||||
await _platformUtilsService.ShowDialogAsync(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ namespace Bit.Core.Services
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly Action<bool> _lockedCallback;
|
||||
private readonly Func<bool, Task> _loggedOutCallback;
|
||||
|
||||
@ -35,6 +36,7 @@ namespace Bit.Core.Services
|
||||
IMessagingService messagingService,
|
||||
ITokenService tokenService,
|
||||
IPolicyService policyService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
Action<bool> lockedCallback,
|
||||
Func<bool, Task> loggedOutCallback)
|
||||
{
|
||||
@ -49,6 +51,7 @@ namespace Bit.Core.Services
|
||||
_messagingService = messagingService;
|
||||
_tokenService = tokenService;
|
||||
_policyService = policyService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_lockedCallback = lockedCallback;
|
||||
_loggedOutCallback = loggedOutCallback;
|
||||
}
|
||||
@ -119,6 +122,18 @@ namespace Bit.Core.Services
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _keyConnectorService.GetUsesKeyConnector()) {
|
||||
var pinSet = await IsPinLockSetAsync();
|
||||
var pinLock = (pinSet.Item1 && PinProtectedKey != null) || pinSet.Item2;
|
||||
|
||||
if (!pinLock && !await IsBiometricLockSetAsync())
|
||||
{
|
||||
await LogOutAsync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowSoftLock)
|
||||
{
|
||||
BiometricLocked = await IsBiometricLockSetAsync();
|
||||
|
@ -49,16 +49,17 @@ namespace Bit.Core.Utilities
|
||||
i18nService, cryptoFunctionService);
|
||||
searchService = new SearchService(cipherService, sendService);
|
||||
var policyService = new PolicyService(storageService, userService);
|
||||
var keyConnectorService = new KeyConnectorService(userService, cryptoService, storageService, tokenService, apiService);
|
||||
var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService,
|
||||
storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
||||
policyService, null, (expired) =>
|
||||
policyService, keyConnectorService, null, (expired) =>
|
||||
{
|
||||
messagingService.Send("logout", expired);
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
var syncService = new SyncService(userService, apiService, settingsService, folderService,
|
||||
cipherService, cryptoService, collectionService, storageService, messagingService, policyService, sendService,
|
||||
(bool expired) =>
|
||||
keyConnectorService, (bool expired) =>
|
||||
{
|
||||
messagingService.Send("logout", expired);
|
||||
return Task.FromResult(0);
|
||||
@ -66,12 +67,13 @@ namespace Bit.Core.Utilities
|
||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, storageService,
|
||||
cryptoFunctionService, policyService);
|
||||
var totpService = new TotpService(storageService, cryptoFunctionService);
|
||||
var authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
|
||||
i18nService, platformUtilsService, messagingService, vaultTimeoutService);
|
||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, userService, tokenService, appIdService,
|
||||
i18nService, platformUtilsService, messagingService, vaultTimeoutService, keyConnectorService);
|
||||
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||
var environmentService = new EnvironmentService(apiService, storageService);
|
||||
var eventService = new EventService(storageService, apiService, userService, cipherService);
|
||||
var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService, cryptoService);
|
||||
|
||||
Register<IStateService>("stateService", stateService);
|
||||
Register<ITokenService>("tokenService", tokenService);
|
||||
@ -94,6 +96,8 @@ namespace Bit.Core.Utilities
|
||||
Register<IAuditService>("auditService", auditService);
|
||||
Register<IEnvironmentService>("environmentService", environmentService);
|
||||
Register<IEventService>("eventService", eventService);
|
||||
Register<IKeyConnectorService>("keyConnectorService", keyConnectorService);
|
||||
Register<IUserVerificationService>("userVerificationService", userVerificationService);
|
||||
}
|
||||
|
||||
public static void Register<T>(string serviceName, T obj)
|
||||
|
Loading…
Reference in New Issue
Block a user