mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 18:38:27 +03:00
[SG-816] Get all login requests and pick the most recent (#2191)
* [SG-816] Get all login requests anfd pick the most recent * [SG-816] Add check if active user has approve login with device active * [SG-816] Build fix. Fix response model. * [SG-816] Move code to sync service
This commit is contained in:
parent
0992a989d4
commit
693a4ef776
9 changed files with 75 additions and 5 deletions
|
@ -147,7 +147,6 @@ namespace Bit.App
|
||||||
}
|
}
|
||||||
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
||||||
|| message.Command == "unlocked"
|
|| message.Command == "unlocked"
|
||||||
|| message.Command == "syncCompleted"
|
|
||||||
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
||||||
{
|
{
|
||||||
lock (_processingLoginRequestLock)
|
lock (_processingLoginRequestLock)
|
||||||
|
@ -209,7 +208,7 @@ namespace Bit.App
|
||||||
});
|
});
|
||||||
await _stateService.SetPasswordlessLoginNotificationAsync(null);
|
await _stateService.SetPasswordlessLoginNotificationAsync(null);
|
||||||
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
|
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
|
||||||
if (loginRequestData.CreationDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) > DateTime.UtcNow)
|
if (!loginRequestData.IsExpired)
|
||||||
{
|
{
|
||||||
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
|
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ namespace Bit.App.Pages
|
||||||
private async Task UpdateRequestTime()
|
private async Task UpdateRequestTime()
|
||||||
{
|
{
|
||||||
TriggerPropertyChanged(nameof(TimeOfRequestText));
|
TriggerPropertyChanged(nameof(TimeOfRequestText));
|
||||||
if (DateTime.UtcNow > LoginRequest?.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes))
|
if (LoginRequest?.IsExpired ?? false)
|
||||||
{
|
{
|
||||||
StopRequestTimeUpdater();
|
StopRequestTimeUpdater();
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
||||||
|
@ -110,7 +110,7 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
private async Task PasswordlessLoginAsync(bool approveRequest)
|
private async Task PasswordlessLoginAsync(bool approveRequest)
|
||||||
{
|
{
|
||||||
if (LoginRequest.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) <= DateTime.UtcNow)
|
if (LoginRequest.IsExpired)
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
||||||
await Page.Navigation.PopModalAsync();
|
await Page.Navigation.PopModalAsync();
|
||||||
|
@ -179,5 +179,7 @@ namespace Bit.App.Pages
|
||||||
public string DeviceType { get; set; }
|
public string DeviceType { get; set; }
|
||||||
|
|
||||||
public string IpAddress { get; set; }
|
public string IpAddress { get; set; }
|
||||||
|
|
||||||
|
public bool IsExpired => RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) < DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ namespace Bit.Core.Abstractions
|
||||||
Task<SendResponse> PutSendAsync(string id, SendRequest request);
|
Task<SendResponse> PutSendAsync(string id, SendRequest request);
|
||||||
Task<SendResponse> PutSendRemovePasswordAsync(string id);
|
Task<SendResponse> PutSendRemovePasswordAsync(string id);
|
||||||
Task DeleteSendAsync(string id);
|
Task DeleteSendAsync(string id);
|
||||||
|
Task<List<PasswordlessLoginResponse>> GetAuthRequestAsync();
|
||||||
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
|
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
|
||||||
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
|
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
|
||||||
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
||||||
|
|
|
@ -29,6 +29,7 @@ namespace Bit.Core.Abstractions
|
||||||
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
|
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
|
||||||
Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
|
Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
|
||||||
|
|
||||||
|
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
|
||||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
||||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
||||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Response
|
namespace Bit.Core.Models.Response
|
||||||
{
|
{
|
||||||
|
@ -18,5 +20,14 @@ namespace Bit.Core.Models.Response
|
||||||
public string Origin { get; set; }
|
public string Origin { get; set; }
|
||||||
public string RequestAccessCode { get; set; }
|
public string RequestAccessCode { get; set; }
|
||||||
public Tuple<byte[], byte[]> RequestKeyPair { get; set; }
|
public Tuple<byte[], byte[]> RequestKeyPair { get; set; }
|
||||||
|
|
||||||
|
public bool IsAnswered => RequestApproved != null && ResponseDate != null;
|
||||||
|
|
||||||
|
public bool IsExpired => CreationDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) < DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PasswordlessLoginsResponse
|
||||||
|
{
|
||||||
|
public List<PasswordlessLoginResponse> Data { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -536,6 +536,12 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
#region PasswordlessLogin
|
#region PasswordlessLogin
|
||||||
|
|
||||||
|
public async Task<List<PasswordlessLoginResponse>> GetAuthRequestAsync()
|
||||||
|
{
|
||||||
|
var response = await SendAsync<object, PasswordlessLoginsResponse>(HttpMethod.Get, $"/auth-requests/", null, true, true);
|
||||||
|
return response.Data;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id)
|
public Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id)
|
||||||
{
|
{
|
||||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}", null, true, true);
|
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}", null, true, true);
|
||||||
|
|
|
@ -485,6 +485,11 @@ namespace Bit.Core.Services
|
||||||
SelectedTwoFactorProviderType = null;
|
SelectedTwoFactorProviderType = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync()
|
||||||
|
{
|
||||||
|
return await _apiService.GetAuthRequestAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
||||||
{
|
{
|
||||||
return await _apiService.GetAuthRequestAsync(id);
|
return await _apiService.GetAuthRequestAsync(id);
|
||||||
|
|
|
@ -24,6 +24,7 @@ namespace Bit.Core.Services
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ISendService _sendService;
|
private readonly ISendService _sendService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly Func<Tuple<string, bool, bool>, Task> _logoutCallbackAsync;
|
private readonly Func<Tuple<string, bool, bool>, Task> _logoutCallbackAsync;
|
||||||
|
|
||||||
public SyncService(
|
public SyncService(
|
||||||
|
@ -39,6 +40,7 @@ namespace Bit.Core.Services
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
ISendService sendService,
|
ISendService sendService,
|
||||||
IKeyConnectorService keyConnectorService,
|
IKeyConnectorService keyConnectorService,
|
||||||
|
ILogger logger,
|
||||||
Func<Tuple<string, bool, bool>, Task> logoutCallbackAsync)
|
Func<Tuple<string, bool, bool>, Task> logoutCallbackAsync)
|
||||||
{
|
{
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
|
@ -53,6 +55,7 @@ namespace Bit.Core.Services
|
||||||
_policyService = policyService;
|
_policyService = policyService;
|
||||||
_sendService = sendService;
|
_sendService = sendService;
|
||||||
_keyConnectorService = keyConnectorService;
|
_keyConnectorService = keyConnectorService;
|
||||||
|
_logger = logger;
|
||||||
_logoutCallbackAsync = logoutCallbackAsync;
|
_logoutCallbackAsync = logoutCallbackAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +111,7 @@ namespace Bit.Core.Services
|
||||||
await SyncSettingsAsync(userId, response.Domains);
|
await SyncSettingsAsync(userId, response.Domains);
|
||||||
await SyncPoliciesAsync(response.Policies);
|
await SyncPoliciesAsync(response.Policies);
|
||||||
await SyncSendsAsync(userId, response.Sends);
|
await SyncSendsAsync(userId, response.Sends);
|
||||||
|
await SyncPasswordlessLoginRequestsAsync(userId);
|
||||||
await SetLastSyncAsync(now);
|
await SetLastSyncAsync(now);
|
||||||
return SyncCompleted(true);
|
return SyncCompleted(true);
|
||||||
}
|
}
|
||||||
|
@ -382,5 +386,44 @@ namespace Bit.Core.Services
|
||||||
new Dictionary<string, SendData>();
|
new Dictionary<string, SendData>();
|
||||||
await _sendService.ReplaceAsync(sends);
|
await _sendService.ReplaceAsync(sends);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SyncPasswordlessLoginRequestsAsync(string userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// if the user has not enabled passwordless logins ignore requests
|
||||||
|
if (!await _stateService.GetApprovePasswordlessLoginsAsync(userId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginRequests = await _apiService.GetAuthRequestAsync();
|
||||||
|
if (loginRequests == null || !loginRequests.Any())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var validLoginRequest = loginRequests.Where(l => !l.IsAnswered && !l.IsExpired)
|
||||||
|
.OrderByDescending(x => x.CreationDate)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (validLoginRequest is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _stateService.SetPasswordlessLoginNotificationAsync(new PasswordlessRequestNotification()
|
||||||
|
{
|
||||||
|
Id = validLoginRequest.Id,
|
||||||
|
UserId = userId
|
||||||
|
});
|
||||||
|
|
||||||
|
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ namespace Bit.Core.Utilities
|
||||||
var messagingService = Resolve<IMessagingService>("messagingService");
|
var messagingService = Resolve<IMessagingService>("messagingService");
|
||||||
var cryptoFunctionService = Resolve<ICryptoFunctionService>("cryptoFunctionService");
|
var cryptoFunctionService = Resolve<ICryptoFunctionService>("cryptoFunctionService");
|
||||||
var cryptoService = Resolve<ICryptoService>("cryptoService");
|
var cryptoService = Resolve<ICryptoService>("cryptoService");
|
||||||
|
var logger = Resolve<ILogger>();
|
||||||
|
|
||||||
SearchService searchService = null;
|
SearchService searchService = null;
|
||||||
|
|
||||||
var tokenService = new TokenService(stateService);
|
var tokenService = new TokenService(stateService);
|
||||||
|
@ -67,7 +69,7 @@ namespace Bit.Core.Utilities
|
||||||
});
|
});
|
||||||
var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService,
|
var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService,
|
||||||
cryptoService, collectionService, organizationService, messagingService, policyService, sendService,
|
cryptoService, collectionService, organizationService, messagingService, policyService, sendService,
|
||||||
keyConnectorService, (extras) =>
|
keyConnectorService, logger, (extras) =>
|
||||||
{
|
{
|
||||||
messagingService.Send("logout", extras);
|
messagingService.Send("logout", extras);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
Loading…
Reference in a new issue