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
{