From cf27ace05e5adaeb3a8754d516f90fb49c3bba65 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Sat, 23 Jul 2016 02:17:11 -0400 Subject: [PATCH] support for two factor login flow --- src/App/Abstractions/Services/IAuthService.cs | 2 + src/App/App.csproj | 1 + .../Api/Request/TokenTwoFactorRequest.cs | 1 + src/App/Pages/LoginPage.cs | 15 +- src/App/Pages/LoginTwoFactorPage.cs | 141 ++++++++++++++++++ src/App/Pages/PasswordHintPage.cs | 10 -- src/App/Services/AuthService.cs | 13 ++ 7 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 src/App/Pages/LoginTwoFactorPage.cs diff --git a/src/App/Abstractions/Services/IAuthService.cs b/src/App/Abstractions/Services/IAuthService.cs index 4e743c3ad..615c8c4e7 100644 --- a/src/App/Abstractions/Services/IAuthService.cs +++ b/src/App/Abstractions/Services/IAuthService.cs @@ -6,6 +6,7 @@ namespace Bit.App.Abstractions public interface IAuthService { bool IsAuthenticated { get; } + bool IsAuthenticatedTwoFactor { get; } string Token { get; set; } string UserId { get; set; } string Email { get; set; } @@ -13,5 +14,6 @@ namespace Bit.App.Abstractions void LogOut(); Task> TokenPostAsync(TokenRequest request); + Task> TokenTwoFactorPostAsync(TokenTwoFactorRequest request); } } diff --git a/src/App/App.csproj b/src/App/App.csproj index 5b20cc4b6..210362662 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -110,6 +110,7 @@ + diff --git a/src/App/Models/Api/Request/TokenTwoFactorRequest.cs b/src/App/Models/Api/Request/TokenTwoFactorRequest.cs index edf1258e8..9742d9580 100644 --- a/src/App/Models/Api/Request/TokenTwoFactorRequest.cs +++ b/src/App/Models/Api/Request/TokenTwoFactorRequest.cs @@ -4,5 +4,6 @@ { public string Code { get; set; } public string Provider { get; set; } + public DeviceRequest Device { get; set; } } } diff --git a/src/App/Pages/LoginPage.cs b/src/App/Pages/LoginPage.cs index 6998726c0..0250e5d49 100644 --- a/src/App/Pages/LoginPage.cs +++ b/src/App/Pages/LoginPage.cs @@ -151,11 +151,18 @@ namespace Bit.App.Pages _cryptoService.Key = key; _authService.Token = response.Result.Token; - _authService.UserId = response.Result.Profile.Id; - _authService.Email = response.Result.Profile.Email; + _authService.UserId = response.Result?.Profile?.Id; + _authService.Email = response.Result?.Profile?.Email; - var task = Task.Run(async () => await _syncService.FullSyncAsync()); - Application.Current.MainPage = new MainPage(); + if(_authService.IsAuthenticatedTwoFactor) + { + await Navigation.PushAsync(new LoginTwoFactorPage()); + } + else + { + var task = Task.Run(async () => await _syncService.FullSyncAsync()); + Application.Current.MainPage = new MainPage(); + } } } } diff --git a/src/App/Pages/LoginTwoFactorPage.cs b/src/App/Pages/LoginTwoFactorPage.cs new file mode 100644 index 000000000..8a0a6cb82 --- /dev/null +++ b/src/App/Pages/LoginTwoFactorPage.cs @@ -0,0 +1,141 @@ +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 LoginTwoFactorPage : ExtendedContentPage + { + private ICryptoService _cryptoService; + private IAuthService _authService; + private IDeviceInfo _deviceInfo; + private IAppIdService _appIdService; + private IUserDialogs _userDialogs; + private ISyncService _syncService; + + public LoginTwoFactorPage() + { + _cryptoService = Resolver.Resolve(); + _authService = Resolver.Resolve(); + _deviceInfo = Resolver.Resolve(); + _appIdService = Resolver.Resolve(); + _userDialogs = Resolver.Resolve(); + _syncService = Resolver.Resolve(); + + Init(); + } + + public FormEntryCell CodeCell { get; set; } + + private void Init() + { + CodeCell = new FormEntryCell("Verification Code", useLabelAsPlaceholder: true, + imageSource: "lock", containerPadding: new Thickness(15, 20)); + + CodeCell.Entry.Keyboard = Keyboard.Numeric; + CodeCell.Entry.ReturnType = Enums.ReturnType.Go; + CodeCell.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() + { + CodeCell + } + } + }; + + var codeLabel = new Label + { + Text = "Enter your two-step verification code.", + 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, codeLabel }, + Spacing = 0 + }; + + var scrollView = new ScrollView { Content = layout }; + + if(Device.OS == TargetPlatform.iOS) + { + table.RowHeight = -1; + table.EstimatedRowHeight = 70; + } + + var continueToolbarItem = new ToolbarItem("Continue", null, async () => + { + await LogIn(); + }, ToolbarItemOrder.Default, 0); + + ToolbarItems.Add(continueToolbarItem); + Title = "Verification Code"; + Content = scrollView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + CodeCell.Entry.Focus(); + } + + private async void Entry_Completed(object sender, EventArgs e) + { + await LogIn(); + } + + private async Task LogIn() + { + if(string.IsNullOrWhiteSpace(CodeCell.Entry.Text)) + { + await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, "Verification code"), AppResources.Ok); + return; + } + + var request = new TokenTwoFactorRequest + { + Code = CodeCell.Entry.Text, + Provider = "Authenticator", + Device = new DeviceRequest(_appIdService, _deviceInfo) + }; + + var responseTask = _authService.TokenTwoFactorPostAsync(request); + _userDialogs.ShowLoading("Validating code...", MaskType.Black); + var response = await responseTask; + _userDialogs.HideLoading(); + if(!response.Succeeded) + { + await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok); + return; + } + + _authService.Token = response.Result.Token; + _authService.UserId = response.Result.Profile.Id; + _authService.Email = response.Result.Profile.Email; + + var task = Task.Run(async () => await _syncService.FullSyncAsync()); + Application.Current.MainPage = new MainPage(); + } + } +} diff --git a/src/App/Pages/PasswordHintPage.cs b/src/App/Pages/PasswordHintPage.cs index 1a80f4854..8904e55e7 100644 --- a/src/App/Pages/PasswordHintPage.cs +++ b/src/App/Pages/PasswordHintPage.cs @@ -14,22 +14,12 @@ 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(); diff --git a/src/App/Services/AuthService.cs b/src/App/Services/AuthService.cs index f13eacd08..1c28b4ed2 100644 --- a/src/App/Services/AuthService.cs +++ b/src/App/Services/AuthService.cs @@ -131,6 +131,13 @@ namespace Bit.App.Services return _cryptoService.Key != null && Token != null && UserId != null; } } + public bool IsAuthenticatedTwoFactor + { + get + { + return _cryptoService.Key != null && Token != null && UserId == null; + } + } public string PIN { @@ -179,5 +186,11 @@ namespace Bit.App.Services // TODO: move more logic in here return await _authApiRepository.PostTokenAsync(request); } + + public async Task> TokenTwoFactorPostAsync(TokenTwoFactorRequest request) + { + // TODO: move more logic in here + return await _authApiRepository.PostTokenTwoFactorAsync(request); + } } }