diff --git a/src/App/App.csproj b/src/App/App.csproj
index b178d6813..b767773cf 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -29,6 +29,9 @@
HintPage.xaml
+
+ LockPage.xaml
+
RegisterPage.xaml
diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs
index 53d1ec65d..e2ec08a7e 100644
--- a/src/App/App.xaml.cs
+++ b/src/App/App.xaml.cs
@@ -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("broadcasterService");
_messagingService = ServiceContainer.Resolve("messagingService");
_stateService = ServiceContainer.Resolve("stateService");
+ _lockService = ServiceContainer.Resolve("lockService");
+ _syncService = ServiceContainer.Resolve("syncService");
+ _tokenService = ServiceContainer.Resolve("tokenService");
+ _cryptoService = ServiceContainer.Resolve("cryptoService");
+ _cipherService = ServiceContainer.Resolve("cipherService");
+ _folderService = ServiceContainer.Resolve("folderService");
+ _settingsService = ServiceContainer.Resolve("settingsService");
+ _collectionService = ServiceContainer.Resolve("collectionService");
+ _searchService = ServiceContainer.Resolve("searchService");
+ _authService = ServiceContainer.Resolve("authService");
+ _platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
+ _passwordGenerationService = ServiceContainer.Resolve(
+ "passwordGenerationService");
_i18nService = ServiceContainer.Resolve("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
{
diff --git a/src/App/Pages/Accounts/LockPage.xaml b/src/App/Pages/Accounts/LockPage.xaml
new file mode 100644
index 000000000..f800b5178
--- /dev/null
+++ b/src/App/Pages/Accounts/LockPage.xaml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Pages/Accounts/LockPage.xaml.cs b/src/App/Pages/Accounts/LockPage.xaml.cs
new file mode 100644
index 000000000..bacab30d8
--- /dev/null
+++ b/src/App/Pages/Accounts/LockPage.xaml.cs
@@ -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();
+ }
+ }
+ }
+}
diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs
new file mode 100644
index 000000000..b9d60ee25
--- /dev/null
+++ b/src/App/Pages/Accounts/LockPageViewModel.cs
@@ -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 _pinSet;
+
+ public LockPageViewModel()
+ {
+ _platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
+ _deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _lockService = ServiceContainer.Resolve("lockService");
+ _cryptoService = ServiceContainer.Resolve("cryptoService");
+ _storageService = ServiceContainer.Resolve("storageService");
+ _userService = ServiceContainer.Resolve("userService");
+ _messagingService = ServiceContainer.Resolve("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(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();
+ }
+ }
+}
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index a61d3c8a4..1c77242c8 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -2850,6 +2850,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to PIN.
+ ///
+ public static string PIN {
+ get {
+ return ResourceManager.GetString("PIN", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Possible Matching Items.
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index 312a0c724..488441cfb 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -1484,4 +1484,7 @@
Lock Now
+
+ PIN
+
\ No newline at end of file
diff --git a/src/Core/Services/SettingsService.cs b/src/Core/Services/SettingsService.cs
index 7f607c86f..89176c907 100644
--- a/src/Core/Services/SettingsService.cs
+++ b/src/Core/Services/SettingsService.cs
@@ -24,7 +24,7 @@ namespace Bit.Core.Services
public void ClearCache()
{
- _settingsCache.Clear();
+ _settingsCache?.Clear();
_settingsCache = null;
}