diff --git a/src/App/App.csproj b/src/App/App.csproj index 93cec5d17..6ca26a8cf 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -25,6 +25,9 @@ HintPage.xaml + + RegisterPage.xaml + LoginPage.xaml diff --git a/src/App/Pages/Accounts/RegisterPage.xaml b/src/App/Pages/Accounts/RegisterPage.xaml new file mode 100644 index 000000000..ebfa23f5a --- /dev/null +++ b/src/App/Pages/Accounts/RegisterPage.xaml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Accounts/RegisterPage.xaml.cs b/src/App/Pages/Accounts/RegisterPage.xaml.cs new file mode 100644 index 000000000..1a79fdb37 --- /dev/null +++ b/src/App/Pages/Accounts/RegisterPage.xaml.cs @@ -0,0 +1,33 @@ +using System; +using Xamarin.Forms; + +namespace Bit.App.Pages +{ + public partial class RegisterPage : BaseContentPage + { + private RegisterPageViewModel _vm; + + public RegisterPage() + { + InitializeComponent(); + _vm = BindingContext as RegisterPageViewModel; + _vm.Page = this; + MasterPasswordEntry = _masterPassword; + ConfirmMasterPasswordEntry = _confirmMasterPassword; + } + + public Entry MasterPasswordEntry { get; set; } + public Entry ConfirmMasterPasswordEntry { get; set; } + + protected override async void OnAppearing() + { + base.OnAppearing(); + RequestFocus(_email); + } + + private async void Submit_Clicked(object sender, EventArgs e) + { + await _vm.SubmitAsync(); + } + } +} diff --git a/src/App/Pages/Accounts/RegisterPageViewModel.cs b/src/App/Pages/Accounts/RegisterPageViewModel.cs new file mode 100644 index 000000000..ced9122b0 --- /dev/null +++ b/src/App/Pages/Accounts/RegisterPageViewModel.cs @@ -0,0 +1,138 @@ +using Bit.App.Abstractions; +using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Request; +using Bit.Core.Utilities; +using System.Threading.Tasks; +using Xamarin.Forms; + +namespace Bit.App.Pages +{ + public class RegisterPageViewModel : BaseViewModel + { + private readonly IDeviceActionService _deviceActionService; + private readonly IApiService _apiService; + private readonly ICryptoService _cryptoService; + + private bool _showPassword; + + public RegisterPageViewModel() + { + _deviceActionService = ServiceContainer.Resolve("deviceActionService"); + _apiService = ServiceContainer.Resolve("apiService"); + _cryptoService = ServiceContainer.Resolve("cryptoService"); + + PageTitle = AppResources.Bitwarden; + TogglePasswordCommand = new Command(TogglePassword); + ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword); + } + + public bool ShowPassword + { + get => _showPassword; + set => SetProperty(ref _showPassword, value, + additionalPropertyNames: new string[] + { + nameof(ShowPasswordIcon) + }); + } + + public Command TogglePasswordCommand { get; } + public Command ToggleConfirmPasswordCommand { get; } + public string ShowPasswordIcon => ShowPassword ? "" : ""; + public string Name { get; set; } + public string Email { get; set; } + public string MasterPassword { get; set; } + public string ConfirmMasterPassword { get; set; } + public string Hint { get; set; } + + public async Task SubmitAsync() + { + if(string.IsNullOrWhiteSpace(Email)) + { + await Page.DisplayAlert(AppResources.AnErrorHasOccurred, + string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress), + AppResources.Ok); + return; + } + if(!Email.Contains("@")) + { + await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok); + return; + } + if(string.IsNullOrWhiteSpace(MasterPassword)) + { + await Page.DisplayAlert(AppResources.AnErrorHasOccurred, + string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), + AppResources.Ok); + return; + } + if(MasterPassword.Length < 8) + { + await Page.DisplayAlert(AppResources.AnErrorHasOccurred, + AppResources.MasterPasswordLengthValMessage, AppResources.Ok); + return; + } + if(MasterPassword != ConfirmMasterPassword) + { + await Page.DisplayAlert(AppResources.AnErrorHasOccurred, + AppResources.MasterPasswordConfirmationValMessage, AppResources.Ok); + return; + } + + // TODO: Password strength check? + + Name = string.IsNullOrWhiteSpace(Name) ? null : Name; + Email = Email.Trim().ToLower(); + var kdf = KdfType.PBKDF2_SHA256; + var kdfIterations = 100_000; + var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdf, kdfIterations); + var encKey = await _cryptoService.MakeEncKeyAsync(key); + var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key); + var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); + var request = new RegisterRequest + { + Email = Email, + Name = Name, + MasterPasswordHash = hashedPassword, + MasterPasswordHint = Hint, + Key = encKey.Item2.EncryptedString, + Kdf = kdf, + KdfIterations = kdfIterations, + Keys = new KeysRequest + { + PublicKey = keys.Item1, + EncryptedPrivateKey = keys.Item2.EncryptedString + } + }; + // TODO: org invite? + + try + { + await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn); + await _apiService.PostRegisterAsync(request); + await _deviceActionService.HideLoadingAsync(); + // TODO: dismiss this page from home page and pass email for login page + } + catch(ApiException e) + { + await _deviceActionService.HideLoadingAsync(); + await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok); + } + } + + public void TogglePassword() + { + ShowPassword = !ShowPassword; + (Page as RegisterPage).MasterPasswordEntry.Focus(); + } + + public void ToggleConfirmPassword() + { + ShowPassword = !ShowPassword; + (Page as RegisterPage).ConfirmMasterPasswordEntry.Focus(); + } + } +} diff --git a/src/App/Pages/HomePage.xaml b/src/App/Pages/HomePage.xaml index 199f5c32c..8b569aa4d 100644 --- a/src/App/Pages/HomePage.xaml +++ b/src/App/Pages/HomePage.xaml @@ -24,7 +24,8 @@ HorizontalTextAlignment="Center"> - + diff --git a/src/App/Pages/HomePage.xaml.cs b/src/App/Pages/HomePage.xaml.cs index 342dcfd6e..3951ade5e 100644 --- a/src/App/Pages/HomePage.xaml.cs +++ b/src/App/Pages/HomePage.xaml.cs @@ -19,5 +19,10 @@ namespace Bit.App.Pages { Navigation.PushModalAsync(new NavigationPage(new LoginPage())); } + + private void Register_Clicked(object sender, EventArgs e) + { + Navigation.PushModalAsync(new NavigationPage(new RegisterPage())); + } } } diff --git a/src/App/Pages/HomePageViewModel.cs b/src/App/Pages/HomePageViewModel.cs index 3a8b0c636..3dbd0c946 100644 --- a/src/App/Pages/HomePageViewModel.cs +++ b/src/App/Pages/HomePageViewModel.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Bit.App.Pages {