register page

This commit is contained in:
Kyle Spearrin 2019-05-02 10:10:05 -04:00
parent 15cda95c64
commit 9965121011
7 changed files with 298 additions and 3 deletions

View file

@ -25,6 +25,9 @@
<Compile Update="Pages\Accounts\HintPage.xaml.cs">
<DependentUpon>HintPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Accounts\RegisterPage.xaml.cs">
<DependentUpon>RegisterPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Accounts\LoginPage.xaml.cs">
<DependentUpon>LoginPage.xaml</DependentUpon>
</Compile>

View file

@ -0,0 +1,117 @@
<?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.RegisterPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:RegisterPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:RegisterPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n EmailAddress}"
StyleClass="box-label" />
<Entry
x:Name="_email"
Text="{Binding Email}"
Keyboard="Email"
StyleClass="box-value" />
</StackLayout>
<Grid StyleClass="box-row">
<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>
<Label
Text="{u:I18n MasterPasswordDescription}"
StyleClass="box-footer-label" />
</StackLayout>
<StackLayout StyleClass="box">
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n RetypeMasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_confirmMasterPassword"
Text="{Binding ConfirmMasterPassword}"
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 ToggleConfirmPasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
</Grid>
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n MasterPasswordHint}"
StyleClass="box-label" />
<Entry
Text="{Binding Hint}"
StyleClass="box-value" />
</StackLayout>
<Label
Text="{u:I18n MasterPasswordHintDescription}"
StyleClass="box-footer-label" />
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View file

@ -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();
}
}
}

View file

@ -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<IDeviceActionService>("deviceActionService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("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();
}
}
}

View file

@ -24,7 +24,8 @@
HorizontalTextAlignment="Center"></Label>
<Button Text="{u:I18n LogIn}"
Clicked="LogIn_Clicked"></Button>
<Button Text="{u:I18n CreateAccount}"></Button>
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked"></Button>
</StackLayout>
</StackLayout>

View file

@ -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()));
}
}
}

View file

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Bit.App.Pages
{