From 0ebfe85d8e341ee20472601f663f45db14401abe Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 19 Apr 2017 22:04:43 -0400 Subject: [PATCH] centralize login code into auth service --- .../Repositories/ICipherApiRepository.cs | 1 - src/App/Abstractions/Services/IAuthService.cs | 8 +- src/App/App.csproj | 1 + src/App/Models/Api/Response/TokenResponse.cs | 1 + src/App/Models/LoginResult.cs | 15 ++++ src/App/Pages/LoginPage.cs | 37 ++------ src/App/Pages/LoginTwoFactorPage.cs | 40 ++------- src/App/Repositories/CipherApiRepository.cs | 43 +--------- src/App/Repositories/FolderApiRepository.cs | 45 ---------- src/App/Services/AuthService.cs | 84 ++++++++++++++++++- 10 files changed, 114 insertions(+), 161 deletions(-) create mode 100644 src/App/Models/LoginResult.cs diff --git a/src/App/Abstractions/Repositories/ICipherApiRepository.cs b/src/App/Abstractions/Repositories/ICipherApiRepository.cs index a25dae4bd..e695d78de 100644 --- a/src/App/Abstractions/Repositories/ICipherApiRepository.cs +++ b/src/App/Abstractions/Repositories/ICipherApiRepository.cs @@ -8,6 +8,5 @@ namespace Bit.App.Abstractions { Task> GetByIdAsync(string id); Task>> GetAsync(); - Task> GetByRevisionDateWithHistoryAsync(DateTime since); } } \ No newline at end of file diff --git a/src/App/Abstractions/Services/IAuthService.cs b/src/App/Abstractions/Services/IAuthService.cs index 38a933f89..9a62db229 100644 --- a/src/App/Abstractions/Services/IAuthService.cs +++ b/src/App/Abstractions/Services/IAuthService.cs @@ -1,6 +1,5 @@ -using System.Threading.Tasks; -using Bit.App.Models.Api; -using System; +using Bit.App.Models; +using System.Threading.Tasks; namespace Bit.App.Abstractions { @@ -14,6 +13,7 @@ namespace Bit.App.Abstractions string PIN { get; set; } void LogOut(); - Task> TokenPostAsync(TokenRequest request); + Task TokenPostAsync(string email, string masterPassword); + Task TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, byte[] key); } } diff --git a/src/App/App.csproj b/src/App/App.csproj index 2fd6a73c9..c880b6f26 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -114,6 +114,7 @@ + diff --git a/src/App/Models/Api/Response/TokenResponse.cs b/src/App/Models/Api/Response/TokenResponse.cs index 92bc1cf6a..b033101f7 100644 --- a/src/App/Models/Api/Response/TokenResponse.cs +++ b/src/App/Models/Api/Response/TokenResponse.cs @@ -14,5 +14,6 @@ namespace Bit.App.Models.Api [JsonProperty("token_type")] public string TokenType { get; set; } public List TwoFactorProviders { get; set; } + public string PrivateKey { get; set; } } } diff --git a/src/App/Models/LoginResult.cs b/src/App/Models/LoginResult.cs new file mode 100644 index 000000000..fa95621c5 --- /dev/null +++ b/src/App/Models/LoginResult.cs @@ -0,0 +1,15 @@ +namespace Bit.App.Models +{ + public class LoginResult + { + public bool Success { get; set; } + public string ErrorMessage { get; set; } + } + + public class FullLoginResult : LoginResult + { + public bool TwoFactorRequired { get; set; } + public byte[] Key { get; set; } + public string MasterPasswordHash { get; set; } + } +} diff --git a/src/App/Pages/LoginPage.cs b/src/App/Pages/LoginPage.cs index 5870ab3cf..414b1d39a 100644 --- a/src/App/Pages/LoginPage.cs +++ b/src/App/Pages/LoginPage.cs @@ -1,8 +1,6 @@ using System; -using System.Linq; using Bit.App.Abstractions; using Bit.App.Controls; -using Bit.App.Models.Api; using Bit.App.Resources; using Xamarin.Forms; using XLabs.Ioc; @@ -15,11 +13,7 @@ namespace Bit.App.Pages { public class LoginPage : ExtendedContentPage { - private ICryptoService _cryptoService; private IAuthService _authService; - private ITokenService _tokenService; - private IDeviceInfoService _deviceInfoService; - private IAppIdService _appIdService; private IUserDialogs _userDialogs; private ISyncService _syncService; private ISettings _settings; @@ -31,11 +25,7 @@ namespace Bit.App.Pages : base(updateActivity: false) { _email = email; - _cryptoService = Resolver.Resolve(); _authService = Resolver.Resolve(); - _tokenService = Resolver.Resolve(); - _deviceInfoService = Resolver.Resolve(); - _appIdService = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _syncService = Resolver.Resolve(); _settings = Resolver.Resolve(); @@ -188,39 +178,22 @@ namespace Bit.App.Pages return; } - var normalizedEmail = EmailCell.Entry.Text.ToLower(); - - var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, normalizedEmail); - - var request = new TokenRequest - { - Email = normalizedEmail, - MasterPasswordHash = _cryptoService.HashPasswordBase64(key, PasswordCell.Entry.Text), - Device = new DeviceRequest(_appIdService, _deviceInfoService) - }; - _userDialogs.ShowLoading(AppResources.LoggingIn, MaskType.Black); - var response = await _authService.TokenPostAsync(request); + var result = await _authService.TokenPostAsync(EmailCell.Entry.Text, PasswordCell.Entry.Text); _userDialogs.HideLoading(); - if(!response.Succeeded) + if(!result.Success) { - await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok); + await DisplayAlert(AppResources.AnErrorHasOccurred, result.ErrorMessage, AppResources.Ok); return; } - if(response.Result.TwoFactorProviders != null && response.Result.TwoFactorProviders.Count > 0) + if(result.TwoFactorRequired) { _googleAnalyticsService.TrackAppEvent("LoggedIn To Two-step"); - await Navigation.PushAsync(new LoginTwoFactorPage(request.Email, request.MasterPasswordHash, key)); + await Navigation.PushAsync(new LoginTwoFactorPage(EmailCell.Entry.Text, result.MasterPasswordHash, result.Key)); return; } - _cryptoService.Key = key; - _tokenService.Token = response.Result.AccessToken; - _tokenService.RefreshToken = response.Result.RefreshToken; - _authService.UserId = _tokenService.TokenUserId; - _authService.Email = _tokenService.TokenEmail; - _settings.AddOrUpdateValue(Constants.LastLoginEmail, _authService.Email); _googleAnalyticsService.TrackAppEvent("LoggedIn"); if(Device.OS == TargetPlatform.Android) diff --git a/src/App/Pages/LoginTwoFactorPage.cs b/src/App/Pages/LoginTwoFactorPage.cs index bf2c25118..5825935a6 100644 --- a/src/App/Pages/LoginTwoFactorPage.cs +++ b/src/App/Pages/LoginTwoFactorPage.cs @@ -1,28 +1,20 @@ using System; -using System.Linq; using Bit.App.Abstractions; using Bit.App.Controls; -using Bit.App.Models.Api; using Bit.App.Resources; using Xamarin.Forms; using XLabs.Ioc; using Acr.UserDialogs; using System.Threading.Tasks; -using Plugin.Settings.Abstractions; using PushNotification.Plugin.Abstractions; namespace Bit.App.Pages { public class LoginTwoFactorPage : ExtendedContentPage { - private ICryptoService _cryptoService; private IAuthService _authService; - private ITokenService _tokenService; - private IDeviceInfoService _deviceInfoService; - private IAppIdService _appIdService; private IUserDialogs _userDialogs; private ISyncService _syncService; - private ISettings _settings; private IGoogleAnalyticsService _googleAnalyticsService; private IPushNotification _pushNotification; private readonly string _email; @@ -36,14 +28,9 @@ namespace Bit.App.Pages _masterPasswordHash = masterPasswordHash; _key = key; - _cryptoService = Resolver.Resolve(); _authService = Resolver.Resolve(); - _tokenService = Resolver.Resolve(); - _deviceInfoService = Resolver.Resolve(); - _appIdService = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _syncService = Resolver.Resolve(); - _settings = Resolver.Resolve(); _googleAnalyticsService = Resolver.Resolve(); _pushNotification = Resolver.Resolve(); @@ -117,7 +104,7 @@ namespace Bit.App.Pages var continueToolbarItem = new ToolbarItem(AppResources.Continue, null, async () => { - await LogIn(); + await LogInAsync(); }, ToolbarItemOrder.Default, 0); ToolbarItems.Add(continueToolbarItem); @@ -147,10 +134,10 @@ namespace Bit.App.Pages private async void Entry_Completed(object sender, EventArgs e) { - await LogIn(); + await LogInAsync(); } - private async Task LogIn() + private async Task LogInAsync() { if(string.IsNullOrWhiteSpace(CodeCell.Entry.Text)) { @@ -159,30 +146,15 @@ namespace Bit.App.Pages return; } - var request = new TokenRequest - { - Email = _email, - MasterPasswordHash = _masterPasswordHash, - Token = CodeCell.Entry.Text.Replace(" ", ""), - Provider = 0, // Authenticator app (only 1 provider for now, so hard coded) - Device = new DeviceRequest(_appIdService, _deviceInfoService) - }; - _userDialogs.ShowLoading(AppResources.ValidatingCode, MaskType.Black); - var response = await _authService.TokenPostAsync(request); + var response = await _authService.TokenPostTwoFactorAsync(CodeCell.Entry.Text, _email, _masterPasswordHash, _key); _userDialogs.HideLoading(); - if(!response.Succeeded) + if(!response.Success) { - await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok); + await DisplayAlert(AppResources.AnErrorHasOccurred, response.ErrorMessage, AppResources.Ok); return; } - _cryptoService.Key = _key; - _tokenService.Token = response.Result.AccessToken; - _tokenService.RefreshToken = response.Result.RefreshToken; - _authService.UserId = _tokenService.TokenUserId; - _authService.Email = _tokenService.TokenEmail; - _settings.AddOrUpdateValue(Constants.LastLoginEmail, _authService.Email); _googleAnalyticsService.TrackAppEvent("LoggedIn From Two-step"); if(Device.OS == TargetPlatform.Android) diff --git a/src/App/Repositories/CipherApiRepository.cs b/src/App/Repositories/CipherApiRepository.cs index 61ea216d3..f3dd7ee5b 100644 --- a/src/App/Repositories/CipherApiRepository.cs +++ b/src/App/Repositories/CipherApiRepository.cs @@ -77,7 +77,8 @@ namespace Bit.App.Repositories var requestMessage = new TokenHttpRequestMessage() { Method = HttpMethod.Get, - RequestUri = new Uri(client.BaseAddress, ApiRoute), + RequestUri = new Uri(client.BaseAddress, + string.Format("{0}?includeFolders=false&includeShared=true", ApiRoute)), }; try @@ -98,45 +99,5 @@ namespace Bit.App.Repositories } } } - - public virtual async Task> GetByRevisionDateWithHistoryAsync(DateTime since) - { - if(!Connectivity.IsConnected) - { - return HandledNotConnected(); - } - - var tokenStateResponse = await HandleTokenStateAsync(); - if(!tokenStateResponse.Succeeded) - { - return tokenStateResponse; - } - - using(var client = HttpService.Client) - { - var requestMessage = new TokenHttpRequestMessage() - { - Method = HttpMethod.Get, - RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/history", "?since=", since)), - }; - - try - { - var response = await client.SendAsync(requestMessage).ConfigureAwait(false); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response).ConfigureAwait(false); - } - - var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); - } - catch - { - return HandledWebException(); - } - } - } } } diff --git a/src/App/Repositories/FolderApiRepository.cs b/src/App/Repositories/FolderApiRepository.cs index c23048902..c53a80920 100644 --- a/src/App/Repositories/FolderApiRepository.cs +++ b/src/App/Repositories/FolderApiRepository.cs @@ -1,12 +1,7 @@ using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; -using Newtonsoft.Json; using Plugin.Connectivity.Abstractions; -using System.Net; namespace Bit.App.Repositories { @@ -20,45 +15,5 @@ namespace Bit.App.Repositories { } protected override string ApiRoute => "folders"; - - public virtual async Task>> GetByRevisionDateAsync(DateTime since) - { - if(!Connectivity.IsConnected) - { - return HandledNotConnected>(); - } - - var tokenStateResponse = await HandleTokenStateAsync>(); - if(!tokenStateResponse.Succeeded) - { - return tokenStateResponse; - } - - using(var client = HttpService.Client) - { - var requestMessage = new TokenHttpRequestMessage() - { - Method = HttpMethod.Get, - RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "?since=", since)), - }; - - try - { - var response = await client.SendAsync(requestMessage).ConfigureAwait(false); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync>(response).ConfigureAwait(false); - } - - var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var responseObj = JsonConvert.DeserializeObject>(responseContent); - return ApiResult>.Success(responseObj, response.StatusCode); - } - catch - { - return HandledWebException>(); - } - } - } } } diff --git a/src/App/Services/AuthService.cs b/src/App/Services/AuthService.cs index cc084fade..414e3a8d7 100644 --- a/src/App/Services/AuthService.cs +++ b/src/App/Services/AuthService.cs @@ -4,6 +4,10 @@ using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; using Plugin.Settings.Abstractions; +using Bit.App.Models; +using System.Linq; +using Xamarin.Forms; +using PushNotification.Plugin.Abstractions; namespace Bit.App.Services { @@ -19,6 +23,8 @@ namespace Bit.App.Services private readonly ISettings _settings; private readonly ICryptoService _cryptoService; private readonly IConnectApiRepository _connectApiRepository; + private readonly IAppIdService _appIdService; + private readonly IDeviceInfoService _deviceInfoService; private string _email; private string _userId; @@ -30,13 +36,17 @@ namespace Bit.App.Services ITokenService tokenService, ISettings settings, ICryptoService cryptoService, - IConnectApiRepository connectApiRepository) + IConnectApiRepository connectApiRepository, + IAppIdService appIdService, + IDeviceInfoService deviceInfoService) { _secureStorage = secureStorage; _tokenService = tokenService; _settings = settings; _cryptoService = cryptoService; _connectApiRepository = connectApiRepository; + _appIdService = appIdService; + _deviceInfoService = deviceInfoService; } public string UserId @@ -191,10 +201,76 @@ namespace Bit.App.Services _settings.Remove(Constants.Locked); } - public async Task> TokenPostAsync(TokenRequest request) + public async Task TokenPostAsync(string email, string masterPassword) { - // TODO: move more logic in here - return await _connectApiRepository.PostTokenAsync(request); + var result = new FullLoginResult(); + + var normalizedEmail = email.Trim().ToLower(); + var key = _cryptoService.MakeKeyFromPassword(masterPassword, normalizedEmail); + + var request = new TokenRequest + { + Email = normalizedEmail, + MasterPasswordHash = _cryptoService.HashPasswordBase64(key, masterPassword), + Device = new DeviceRequest(_appIdService, _deviceInfoService) + }; + + var response = await _connectApiRepository.PostTokenAsync(request); + if(!response.Succeeded) + { + result.Success = false; + result.ErrorMessage = response.Errors.FirstOrDefault()?.Message; + return result; + } + + result.Success = true; + if(response.Result.TwoFactorProviders != null && response.Result.TwoFactorProviders.Count > 0) + { + result.Key = key; + result.MasterPasswordHash = request.MasterPasswordHash; + result.TwoFactorRequired = true; + return result; + } + + ProcessLoginSuccess(key, response.Result); + return result; + } + + public async Task TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, + byte[] key) + { + var result = new LoginResult(); + + var request = new TokenRequest + { + Email = email.Trim().ToLower(), + MasterPasswordHash = masterPasswordHash, + Token = token.Trim().Replace(" ", ""), + Provider = 0, // Authenticator app (only 1 provider for now, so hard coded) + Device = new DeviceRequest(_appIdService, _deviceInfoService) + }; + + var response = await _connectApiRepository.PostTokenAsync(request); + if(!response.Succeeded) + { + result.Success = false; + result.ErrorMessage = response.Errors.FirstOrDefault()?.Message; + return result; + } + + result.Success = true; + ProcessLoginSuccess(key, response.Result); + return result; + } + + private void ProcessLoginSuccess(byte[] key, TokenResponse response) + { + _cryptoService.Key = key; + _tokenService.Token = response.AccessToken; + _tokenService.RefreshToken = response.RefreshToken; + UserId = _tokenService.TokenUserId; + Email = _tokenService.TokenEmail; + _settings.AddOrUpdateValue(Constants.LastLoginEmail, Email); } } }