centralize login code into auth service

This commit is contained in:
Kyle Spearrin 2017-04-19 22:04:43 -04:00
parent 8e29a990cb
commit 0ebfe85d8e
10 changed files with 114 additions and 161 deletions

View File

@ -8,6 +8,5 @@ namespace Bit.App.Abstractions
{
Task<ApiResult<CipherResponse>> GetByIdAsync(string id);
Task<ApiResult<ListResponse<CipherResponse>>> GetAsync();
Task<ApiResult<CipherHistoryResponse>> GetByRevisionDateWithHistoryAsync(DateTime since);
}
}

View File

@ -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<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request);
Task<FullLoginResult> TokenPostAsync(string email, string masterPassword);
Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, byte[] key);
}
}

View File

@ -114,6 +114,7 @@
<Compile Include="Models\Data\LoginData.cs" />
<Compile Include="Models\DomainName.cs" />
<Compile Include="Models\Folder.cs" />
<Compile Include="Models\LoginResult.cs" />
<Compile Include="Models\Page\AppExtensionPageModel.cs" />
<Compile Include="Models\Page\SettingsFolderPageModel.cs" />
<Compile Include="Models\Page\PinPageModel.cs" />

View File

@ -14,5 +14,6 @@ namespace Bit.App.Models.Api
[JsonProperty("token_type")]
public string TokenType { get; set; }
public List<int> TwoFactorProviders { get; set; }
public string PrivateKey { get; set; }
}
}

View File

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

View File

@ -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<ICryptoService>();
_authService = Resolver.Resolve<IAuthService>();
_tokenService = Resolver.Resolve<ITokenService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_appIdService = Resolver.Resolve<IAppIdService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_syncService = Resolver.Resolve<ISyncService>();
_settings = Resolver.Resolve<ISettings>();
@ -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)

View File

@ -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<ICryptoService>();
_authService = Resolver.Resolve<IAuthService>();
_tokenService = Resolver.Resolve<ITokenService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_appIdService = Resolver.Resolve<IAppIdService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_syncService = Resolver.Resolve<ISyncService>();
_settings = Resolver.Resolve<ISettings>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_pushNotification = Resolver.Resolve<IPushNotification>();
@ -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)

View File

@ -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<ApiResult<CipherHistoryResponse>> GetByRevisionDateWithHistoryAsync(DateTime since)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<CipherHistoryResponse>();
}
var tokenStateResponse = await HandleTokenStateAsync<CipherHistoryResponse>();
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<CipherHistoryResponse>(response).ConfigureAwait(false);
}
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseObj = JsonConvert.DeserializeObject<CipherHistoryResponse>(responseContent);
return ApiResult<CipherHistoryResponse>.Success(responseObj, response.StatusCode);
}
catch
{
return HandledWebException<CipherHistoryResponse>();
}
}
}
}
}

View File

@ -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<ApiResult<ListResponse<FolderResponse>>> GetByRevisionDateAsync(DateTime since)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<ListResponse<FolderResponse>>();
}
var tokenStateResponse = await HandleTokenStateAsync<ListResponse<FolderResponse>>();
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<ListResponse<FolderResponse>>(response).ConfigureAwait(false);
}
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseObj = JsonConvert.DeserializeObject<ListResponse<FolderResponse>>(responseContent);
return ApiResult<ListResponse<FolderResponse>>.Success(responseObj, response.StatusCode);
}
catch
{
return HandledWebException<ListResponse<FolderResponse>>();
}
}
}
}
}

View File

@ -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<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request)
public async Task<FullLoginResult> 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<LoginResult> 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);
}
}
}