[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:
André Bispo 2022-11-15 17:36:21 +00:00 committed by GitHub
parent 0992a989d4
commit 693a4ef776
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 75 additions and 5 deletions

View file

@ -147,7 +147,6 @@ namespace Bit.App
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == "syncCompleted"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{
lock (_processingLoginRequestLock)
@ -209,7 +208,7 @@ namespace Bit.App
});
await _stateService.SetPasswordlessLoginNotificationAsync(null);
_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)));
}

View file

@ -100,7 +100,7 @@ namespace Bit.App.Pages
private async Task UpdateRequestTime()
{
TriggerPropertyChanged(nameof(TimeOfRequestText));
if (DateTime.UtcNow > LoginRequest?.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes))
if (LoginRequest?.IsExpired ?? false)
{
StopRequestTimeUpdater();
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
@ -110,7 +110,7 @@ namespace Bit.App.Pages
private async Task PasswordlessLoginAsync(bool approveRequest)
{
if (LoginRequest.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) <= DateTime.UtcNow)
if (LoginRequest.IsExpired)
{
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
await Page.Navigation.PopModalAsync();
@ -179,5 +179,7 @@ namespace Bit.App.Pages
public string DeviceType { get; set; }
public string IpAddress { get; set; }
public bool IsExpired => RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) < DateTime.UtcNow;
}
}

View file

@ -83,6 +83,7 @@ namespace Bit.Core.Abstractions
Task<SendResponse> PutSendAsync(string id, SendRequest request);
Task<SendResponse> PutSendRemovePasswordAsync(string id);
Task DeleteSendAsync(string id);
Task<List<PasswordlessLoginResponse>> GetAuthRequestAsync();
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);

View file

@ -29,6 +29,7 @@ namespace Bit.Core.Abstractions
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<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);

View file

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Core.Models.Response
{
@ -18,5 +20,14 @@ namespace Bit.Core.Models.Response
public string Origin { get; set; }
public string RequestAccessCode { 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; }
}
}

View file

@ -536,6 +536,12 @@ namespace Bit.Core.Services
#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)
{
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}", null, true, true);

View file

@ -485,6 +485,11 @@ namespace Bit.Core.Services
SelectedTwoFactorProviderType = null;
}
public async Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync()
{
return await _apiService.GetAuthRequestAsync();
}
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
{
return await _apiService.GetAuthRequestAsync(id);

View file

@ -24,6 +24,7 @@ namespace Bit.Core.Services
private readonly IPolicyService _policyService;
private readonly ISendService _sendService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly ILogger _logger;
private readonly Func<Tuple<string, bool, bool>, Task> _logoutCallbackAsync;
public SyncService(
@ -39,6 +40,7 @@ namespace Bit.Core.Services
IPolicyService policyService,
ISendService sendService,
IKeyConnectorService keyConnectorService,
ILogger logger,
Func<Tuple<string, bool, bool>, Task> logoutCallbackAsync)
{
_stateService = stateService;
@ -53,6 +55,7 @@ namespace Bit.Core.Services
_policyService = policyService;
_sendService = sendService;
_keyConnectorService = keyConnectorService;
_logger = logger;
_logoutCallbackAsync = logoutCallbackAsync;
}
@ -108,6 +111,7 @@ namespace Bit.Core.Services
await SyncSettingsAsync(userId, response.Domains);
await SyncPoliciesAsync(response.Policies);
await SyncSendsAsync(userId, response.Sends);
await SyncPasswordlessLoginRequestsAsync(userId);
await SetLastSyncAsync(now);
return SyncCompleted(true);
}
@ -382,5 +386,44 @@ namespace Bit.Core.Services
new Dictionary<string, SendData>();
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);
}
}
}
}

View file

@ -29,6 +29,8 @@ namespace Bit.Core.Utilities
var messagingService = Resolve<IMessagingService>("messagingService");
var cryptoFunctionService = Resolve<ICryptoFunctionService>("cryptoFunctionService");
var cryptoService = Resolve<ICryptoService>("cryptoService");
var logger = Resolve<ILogger>();
SearchService searchService = null;
var tokenService = new TokenService(stateService);
@ -67,7 +69,7 @@ namespace Bit.Core.Utilities
});
var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService,
cryptoService, collectionService, organizationService, messagingService, policyService, sendService,
keyConnectorService, (extras) =>
keyConnectorService, logger, (extras) =>
{
messagingService.Send("logout", extras);
return Task.CompletedTask;