diff --git a/src/App/Abstractions/Repositories/IAccountsApiRepository.cs b/src/App/Abstractions/Repositories/IAccountsApiRepository.cs index a8fda105d..2c21643f5 100644 --- a/src/App/Abstractions/Repositories/IAccountsApiRepository.cs +++ b/src/App/Abstractions/Repositories/IAccountsApiRepository.cs @@ -6,5 +6,6 @@ namespace Bit.App.Abstractions public interface IAccountsApiRepository { Task PostRegisterAsync(RegisterRequest requestObj); + Task PostPasswordHintAsync(PasswordHintRequest requestObj); } } \ No newline at end of file diff --git a/src/App/App.csproj b/src/App/App.csproj index a31d7b8df..5b20cc4b6 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -81,6 +81,7 @@ + @@ -109,6 +110,7 @@ + diff --git a/src/App/Models/Api/Request/PasswordHintRequest.cs b/src/App/Models/Api/Request/PasswordHintRequest.cs new file mode 100644 index 000000000..6d515f9ad --- /dev/null +++ b/src/App/Models/Api/Request/PasswordHintRequest.cs @@ -0,0 +1,7 @@ +namespace Bit.App.Models.Api +{ + public class PasswordHintRequest + { + public string Email { get; set; } + } +} diff --git a/src/App/Pages/LoginPage.cs b/src/App/Pages/LoginPage.cs index c6d8e80ca..6998726c0 100644 --- a/src/App/Pages/LoginPage.cs +++ b/src/App/Pages/LoginPage.cs @@ -52,9 +52,11 @@ namespace Bit.App.Pages var table = new ExtendedTableView { Intent = TableIntent.Settings, - EnableScrolling = true, + EnableScrolling = false, HasUnevenRows = true, - EnableSelection = false, + EnableSelection = true, + NoFooter = true, + VerticalOptions = LayoutOptions.Start, Root = new TableRoot { new TableSection() @@ -65,10 +67,21 @@ namespace Bit.App.Pages } }; - var loginToolbarItem = new ToolbarItem(AppResources.LogIn, null, async () => + var forgotPasswordButton = new Button { - await LogIn(); - }, ToolbarItemOrder.Default, 0); + Text = "Get your master password hint", + Style = (Style)Application.Current.Resources["btn-primaryAccent"], + Margin = new Thickness(15, 0, 15, 25), + Command = new Command(async () => await ForgotPasswordAsync()) + }; + + var layout = new StackLayout + { + Children = { table, forgotPasswordButton }, + Spacing = 0 + }; + + var scrollView = new ScrollView { Content = layout }; if(Device.OS == TargetPlatform.iOS) { @@ -77,9 +90,15 @@ namespace Bit.App.Pages ToolbarItems.Add(new DismissModalToolBarItem(this, "Cancel")); } + var loginToolbarItem = new ToolbarItem(AppResources.LogIn, null, async () => + { + await LogIn(); + }, ToolbarItemOrder.Default, 0); + ToolbarItems.Add(loginToolbarItem); Title = AppResources.Bitwarden; - Content = table; + Content = scrollView; + NavigationPage.SetBackButtonTitle(this, "Log In"); } protected override void OnAppearing() @@ -92,6 +111,10 @@ namespace Bit.App.Pages { await LogIn(); } + private async Task ForgotPasswordAsync() + { + await Navigation.PushAsync(new PasswordHintPage()); + } private async Task LogIn() { diff --git a/src/App/Pages/PasswordHintPage.cs b/src/App/Pages/PasswordHintPage.cs new file mode 100644 index 000000000..1a80f4854 --- /dev/null +++ b/src/App/Pages/PasswordHintPage.cs @@ -0,0 +1,139 @@ +using System; +using System.Linq; +using Bit.App.Abstractions; +using Bit.App.Controls; +using Bit.App.Models.Api; +using Bit.App.Resources; +using Plugin.DeviceInfo.Abstractions; +using Xamarin.Forms; +using XLabs.Ioc; +using Acr.UserDialogs; +using System.Threading.Tasks; + +namespace Bit.App.Pages +{ + public class PasswordHintPage : ExtendedContentPage + { + private ICryptoService _cryptoService; + private IAuthService _authService; + private IDeviceInfo _deviceInfo; + private IAppIdService _appIdService; + private IUserDialogs _userDialogs; + private ISyncService _syncService; + private IAccountsApiRepository _accountApiRepository; + + public PasswordHintPage() + { + _cryptoService = Resolver.Resolve(); + _authService = Resolver.Resolve(); + _deviceInfo = Resolver.Resolve(); + _appIdService = Resolver.Resolve(); + _userDialogs = Resolver.Resolve(); + _syncService = Resolver.Resolve(); + _accountApiRepository = Resolver.Resolve(); + + Init(); + } + + public FormEntryCell EmailCell { get; set; } + + private void Init() + { + EmailCell = new FormEntryCell(AppResources.EmailAddress, entryKeyboard: Keyboard.Email, + useLabelAsPlaceholder: true, imageSource: "envelope", containerPadding: new Thickness(15, 20)); + + EmailCell.Entry.ReturnType = Enums.ReturnType.Go; + EmailCell.Entry.Completed += Entry_Completed; + + var table = new ExtendedTableView + { + Intent = TableIntent.Settings, + EnableScrolling = false, + HasUnevenRows = true, + EnableSelection = true, + NoFooter = true, + VerticalOptions = LayoutOptions.Start, + Root = new TableRoot + { + new TableSection() + { + EmailCell + } + } + }; + + var hintLabel = new Label + { + Text = "Enter your account email address to receive your master password hint.", + LineBreakMode = LineBreakMode.WordWrap, + FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)), + Style = (Style)Application.Current.Resources["text-muted"], + Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25) + }; + + var layout = new StackLayout + { + Children = { table, hintLabel }, + Spacing = 0 + }; + + var scrollView = new ScrollView { Content = layout }; + + if(Device.OS == TargetPlatform.iOS) + { + table.RowHeight = -1; + table.EstimatedRowHeight = 70; + } + + var submitToolbarItem = new ToolbarItem("Submit", null, async () => + { + await SubmitAsync(); + }, ToolbarItemOrder.Default, 0); + + ToolbarItems.Add(submitToolbarItem); + Title = "Password Hint"; + Content = scrollView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + EmailCell.Entry.Focus(); + } + + private async void Entry_Completed(object sender, EventArgs e) + { + await SubmitAsync(); + } + + private async Task SubmitAsync() + { + if(string.IsNullOrWhiteSpace(EmailCell.Entry.Text)) + { + await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress), AppResources.Ok); + return; + } + + var request = new PasswordHintRequest + { + Email = EmailCell.Entry.Text + }; + + var responseTask = _accountApiRepository.PostPasswordHintAsync(request); + _userDialogs.ShowLoading("Submitting...", MaskType.Black); + var response = await responseTask; + _userDialogs.HideLoading(); + if(!response.Succeeded) + { + await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok); + return; + } + else + { + await DisplayAlert(null, "We've sent you an email with your master password hint. ", AppResources.Ok); + } + + await Navigation.PopAsync(); + } + } +} diff --git a/src/App/Pages/RegisterPage.cs b/src/App/Pages/RegisterPage.cs index 682901670..ab48edb07 100644 --- a/src/App/Pages/RegisterPage.cs +++ b/src/App/Pages/RegisterPage.cs @@ -187,7 +187,7 @@ namespace Bit.App.Pages Intent = TableIntent.Settings; EnableScrolling = false; HasUnevenRows = true; - EnableSelection = false; + EnableSelection = true; VerticalOptions = LayoutOptions.Start; NoFooter = true; } diff --git a/src/App/Repositories/AccountsApiRepository.cs b/src/App/Repositories/AccountsApiRepository.cs index ab2d9c4a5..58a840819 100644 --- a/src/App/Repositories/AccountsApiRepository.cs +++ b/src/App/Repositories/AccountsApiRepository.cs @@ -39,5 +39,30 @@ namespace Bit.App.Repositories return ApiResult.Success(response.StatusCode); } } + + public virtual async Task PostPasswordHintAsync(PasswordHintRequest requestObj) + { + if(!Connectivity.IsConnected) + { + return HandledNotConnected(); + } + + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage(requestObj) + { + Method = HttpMethod.Post, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/password-hint")), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + return ApiResult.Success(response.StatusCode); + } + } } }