mirror of
https://github.com/bitwarden/mobile.git
synced 2024-12-17 15:27:43 +01:00
lock page
This commit is contained in:
parent
f7bb091366
commit
27b6631cc1
@ -29,6 +29,9 @@
|
||||
<Compile Update="Pages\Accounts\HintPage.xaml.cs">
|
||||
<DependentUpon>HintPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\LockPage.xaml.cs">
|
||||
<DependentUpon>LockPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\RegisterPage.xaml.cs">
|
||||
<DependentUpon>RegisterPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -20,6 +20,18 @@ namespace Bit.App
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly ICollectionService _collectionService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly ISearchService _searchService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public App()
|
||||
{
|
||||
@ -27,6 +39,19 @@ namespace Bit.App
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
|
||||
_settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
|
||||
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
|
||||
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
||||
_authService = ServiceContainer.Resolve<IAuthService>("authService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
|
||||
"passwordGenerationService");
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
|
||||
|
||||
InitializeComponent();
|
||||
@ -58,8 +83,23 @@ namespace Bit.App
|
||||
else if(message.Command == "locked")
|
||||
{
|
||||
await _stateService.PurgeAsync();
|
||||
MainPage = new NavigationPage(new LockPage());
|
||||
}
|
||||
else if(message.Command == "lockVault")
|
||||
{
|
||||
await _lockService.LockAsync(true);
|
||||
}
|
||||
else if(message.Command == "logout")
|
||||
{
|
||||
await LogOutAsync(false);
|
||||
}
|
||||
else if(message.Command == "loggedOut")
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
else if(message.Command == "unlocked" || message.Command == "loggedIn")
|
||||
{
|
||||
// TODO
|
||||
// MainPage = new LockPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -87,12 +127,46 @@ namespace Bit.App
|
||||
new System.Globalization.UmAlQuraCalendar();
|
||||
}
|
||||
|
||||
private async Task LogOutAsync(bool expired)
|
||||
{
|
||||
var userId = await _userService.GetUserIdAsync();
|
||||
await Task.WhenAll(
|
||||
_syncService.SetLastSyncAsync(DateTime.MinValue),
|
||||
_tokenService.ClearTokenAsync(),
|
||||
_cryptoService.ClearKeysAsync(),
|
||||
_userService.ClearAsync(),
|
||||
_settingsService.ClearAsync(userId),
|
||||
_cipherService.ClearAsync(userId),
|
||||
_folderService.ClearAsync(userId),
|
||||
_collectionService.ClearAsync(userId),
|
||||
_passwordGenerationService.ClearAsync(),
|
||||
_lockService.ClearAsync());
|
||||
_lockService.PinLocked = false;
|
||||
_searchService.ClearIndex();
|
||||
_authService.LogOut(() =>
|
||||
{
|
||||
if(expired)
|
||||
{
|
||||
// TODO: Toast?
|
||||
}
|
||||
MainPage = new HomePage();
|
||||
});
|
||||
}
|
||||
|
||||
private async Task SetMainPageAsync()
|
||||
{
|
||||
var authed = await _userService.IsAuthenticatedAsync();
|
||||
if(authed)
|
||||
{
|
||||
Current.MainPage = new TabsPage();
|
||||
var locked = await _lockService.IsLockedAsync();
|
||||
if(locked)
|
||||
{
|
||||
Current.MainPage = new NavigationPage(new LockPage());
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.MainPage = new TabsPage();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
94
src/App/Pages/Accounts/LockPage.xaml
Normal file
94
src/App/Pages/Accounts/LockPage.xaml
Normal file
@ -0,0 +1,94 @@
|
||||
<?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.LockPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="pages:LockPageViewModel"
|
||||
Title="{Binding PageTitle}">
|
||||
|
||||
<ContentPage.BindingContext>
|
||||
<pages:LockPageViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Unlock}" Clicked="Unlock_Clicked" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ScrollView>
|
||||
<StackLayout Spacing="20">
|
||||
<StackLayout StyleClass="box">
|
||||
<Grid StyleClass="box-row" IsVisible="{Binding PinLock}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{u:I18n PIN}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<controls:MonoEntry
|
||||
x:Name="_pin"
|
||||
Text="{Binding Pin}"
|
||||
StyleClass="box-value"
|
||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0" />
|
||||
<controls:FaButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowPasswordIcon}"
|
||||
Command="{Binding TogglePasswordCommand}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2" />
|
||||
</Grid>
|
||||
<Grid StyleClass="box-row" IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{u:I18n MasterPassword}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<controls:MonoEntry
|
||||
x:Name="_masterPassword"
|
||||
Text="{Binding MasterPassword}"
|
||||
StyleClass="box-value"
|
||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0" />
|
||||
<controls:FaButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowPasswordIcon}"
|
||||
Command="{Binding TogglePasswordCommand}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2" />
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
<StackLayout Padding="10, 0">
|
||||
<Button Text="{u:I18n LogOut}" Clicked="LogOut_Clicked"></Button>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
||||
</pages:BaseContentPage>
|
52
src/App/Pages/Accounts/LockPage.xaml.cs
Normal file
52
src/App/Pages/Accounts/LockPage.xaml.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class LockPage : BaseContentPage
|
||||
{
|
||||
private LockPageViewModel _vm;
|
||||
|
||||
public LockPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = BindingContext as LockPageViewModel;
|
||||
_vm.Page = this;
|
||||
MasterPasswordEntry = _masterPassword;
|
||||
PinEntry = _pin;
|
||||
}
|
||||
|
||||
public Entry MasterPasswordEntry { get; set; }
|
||||
public Entry PinEntry { get; set; }
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
await _vm.InitAsync();
|
||||
if(_vm.PinLock)
|
||||
{
|
||||
RequestFocus(PinEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestFocus(MasterPasswordEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Unlock_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if(DoOnce())
|
||||
{
|
||||
await _vm.SubmitAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void LogOut_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if(DoOnce())
|
||||
{
|
||||
await _vm.LogOutAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
180
src/App/Pages/Accounts/LockPageViewModel.cs
Normal file
180
src/App/Pages/Accounts/LockPageViewModel.cs
Normal file
@ -0,0 +1,180 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LockPageViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
|
||||
private string _email;
|
||||
private bool _showPassword;
|
||||
private bool _pinLock;
|
||||
private int _invalidPinAttempts = 0;
|
||||
private Tuple<bool, bool> _pinSet;
|
||||
|
||||
public LockPageViewModel()
|
||||
{
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
|
||||
PageTitle = AppResources.VerifyMasterPassword;
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
}
|
||||
|
||||
public bool ShowPassword
|
||||
{
|
||||
get => _showPassword;
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowPasswordIcon)
|
||||
});
|
||||
}
|
||||
|
||||
public bool PinLock
|
||||
{
|
||||
get => _pinLock;
|
||||
set => SetProperty(ref _pinLock, value);
|
||||
}
|
||||
|
||||
public Command TogglePasswordCommand { get; }
|
||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||
public string MasterPassword { get; set; }
|
||||
public string Pin { get; set; }
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
_pinSet = await _lockService.IsPinLockSetAsync();
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
PinLock = (_pinSet.Item1 && hasKey) || _pinSet.Item2;
|
||||
_email = await _userService.GetEmailAsync();
|
||||
PageTitle = PinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword;
|
||||
}
|
||||
|
||||
public async Task SubmitAsync()
|
||||
{
|
||||
if(PinLock && string.IsNullOrWhiteSpace(Pin))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
if(!PinLock && string.IsNullOrWhiteSpace(MasterPassword))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var kdf = await _userService.GetKdfAsync();
|
||||
var kdfIterations = await _userService.GetKdfIterationsAsync();
|
||||
|
||||
if(PinLock)
|
||||
{
|
||||
var failed = true;
|
||||
try
|
||||
{
|
||||
if(_pinSet.Item1)
|
||||
{
|
||||
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin));
|
||||
failed = decPin != Pin;
|
||||
_lockService.PinLocked = failed;
|
||||
if(failed)
|
||||
{
|
||||
DoContinue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
|
||||
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
|
||||
failed = false;
|
||||
await SetKeyAndContinueAsync(key);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
if(failed)
|
||||
{
|
||||
_invalidPinAttempts++;
|
||||
if(_invalidPinAttempts >= 5)
|
||||
{
|
||||
_messagingService.Send("logout");
|
||||
return;
|
||||
}
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
|
||||
AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
|
||||
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||
if(storedKeyHash != null && keyHash != null && storedKeyHash == keyHash)
|
||||
{
|
||||
await SetKeyAndContinueAsync(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
|
||||
AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LogOutAsync()
|
||||
{
|
||||
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LogoutConfirmation,
|
||||
AppResources.LogOut, AppResources.Yes, AppResources.Cancel);
|
||||
if(confirmed)
|
||||
{
|
||||
_messagingService.Send("logout");
|
||||
}
|
||||
}
|
||||
|
||||
public void TogglePassword()
|
||||
{
|
||||
ShowPassword = !ShowPassword;
|
||||
var page = (Page as LockPage);
|
||||
var entry = PinLock ? page.PinEntry : page.MasterPasswordEntry;
|
||||
entry.Focus();
|
||||
}
|
||||
|
||||
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
|
||||
{
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
}
|
||||
|
||||
private void DoContinue()
|
||||
{
|
||||
_messagingService.Send("unlocked");
|
||||
Application.Current.MainPage = new TabsPage();
|
||||
}
|
||||
}
|
||||
}
|
9
src/App/Resources/AppResources.Designer.cs
generated
9
src/App/Resources/AppResources.Designer.cs
generated
@ -2850,6 +2850,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to PIN.
|
||||
/// </summary>
|
||||
public static string PIN {
|
||||
get {
|
||||
return ResourceManager.GetString("PIN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Possible Matching Items.
|
||||
/// </summary>
|
||||
|
@ -1484,4 +1484,7 @@
|
||||
<data name="LockNow" xml:space="preserve">
|
||||
<value>Lock Now</value>
|
||||
</data>
|
||||
<data name="PIN" xml:space="preserve">
|
||||
<value>PIN</value>
|
||||
</data>
|
||||
</root>
|
@ -24,7 +24,7 @@ namespace Bit.Core.Services
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
_settingsCache.Clear();
|
||||
_settingsCache?.Clear();
|
||||
_settingsCache = null;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user