mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
[SG-834] Mobile pending login requests management screen (#2281)
* Bootstrap new classes for settings list * [SG-834] Add new method GetActivePasswordlessLoginRequestsAsync to AuthService * [SG-834] Add generic handle exception method to BaseViewModel * [SG-834] Add request verification to settings entry * [SG-834] Add text resources * [SG-834] Update view and viewmodel * [SG-834] Remove unnecessary property assignment * [SG-834] removed logger resolve
This commit is contained in:
parent
c3ad5f0580
commit
e61ca489ce
10 changed files with 395 additions and 5 deletions
|
@ -1,4 +1,6 @@
|
|||
using Xamarin.Forms;
|
||||
using System.Linq;
|
||||
using Xamarin.CommunityToolkit.Converters;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
|
@ -6,4 +8,13 @@ namespace Bit.App.Controls
|
|||
{
|
||||
public string ExtraDataForLogging { get; set; }
|
||||
}
|
||||
|
||||
public class SelectionChangedEventArgsConverter : BaseNullableConverterOneWay<SelectionChangedEventArgs, object>
|
||||
{
|
||||
public override object? ConvertFrom(SelectionChangedEventArgs? value)
|
||||
{
|
||||
return value?.CurrentSelection.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ namespace Bit.App.Pages
|
|||
}
|
||||
|
||||
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(LoginRequest.Id);
|
||||
if (loginRequestData.RequestApproved.HasValue && loginRequestData.ResponseDate.HasValue)
|
||||
if (loginRequestData.IsAnswered)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.ThisRequestIsNoLongerValid);
|
||||
await Page.Navigation.PopModalAsync();
|
||||
|
|
107
src/App/Pages/Settings/LoginPasswordlessRequestsListPage.xaml
Normal file
107
src/App/Pages/Settings/LoginPasswordlessRequestsListPage.xaml
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
||||
x:Class="Bit.App.Pages.LoginPasswordlessRequestsListPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
x:DataType="pages:LoginPasswordlessRequestsListViewModel"
|
||||
xmlns:models="clr-namespace:Bit.Core.Models.Response;assembly=BitwardenCore"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
Title="{Binding PageTitle}">
|
||||
|
||||
<ContentPage.BindingContext>
|
||||
<pages:LoginPasswordlessRequestsListViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:DateTimeConverter x:Key="dateTime" />
|
||||
<xct:ItemSelectedEventArgsConverter x:Key="ItemSelectedEventArgsConverter" />
|
||||
<controls:SelectionChangedEventArgsConverter x:Key="SelectionChangedEventArgsConverter" />
|
||||
<DataTemplate
|
||||
x:Key="loginRequestTemplate"
|
||||
x:DataType="models:PasswordlessLoginResponse">
|
||||
<Grid
|
||||
Padding="10, 0"
|
||||
RowSpacing="0"
|
||||
RowDefinitions="*, Auto, *, 10"
|
||||
ColumnDefinitions="*, *">
|
||||
<Label
|
||||
Text="{u:I18n FingerprintPhrase}"
|
||||
FontSize="Small"
|
||||
Padding="0, 10, 0 ,0"
|
||||
FontAttributes="Bold"/>
|
||||
<controls:MonoLabel
|
||||
FormattedText="{Binding RequestFingerprint}"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
FontSize="Small"
|
||||
Padding="0, 5, 0, 10"
|
||||
VerticalTextAlignment="Center"
|
||||
TextColor="{DynamicResource FingerprintPhrase}"/>
|
||||
<Label
|
||||
Grid.Row="2"
|
||||
HorizontalOptions="Start"
|
||||
HorizontalTextAlignment="Start"
|
||||
Text="{Binding RequestDeviceType}"
|
||||
StyleClass="list-header-sub" />
|
||||
<Label
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
HorizontalOptions="End"
|
||||
HorizontalTextAlignment="End"
|
||||
Text="{Binding CreationDate, Converter={StaticResource dateTime}}"
|
||||
StyleClass="list-header-sub" />
|
||||
<BoxView
|
||||
StyleClass="list-section-separator-top, list-section-separator-top-platform"
|
||||
VerticalOptions="End"
|
||||
Grid.Row="3"
|
||||
Grid.ColumnSpan="2"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<StackLayout
|
||||
x:Key="mainLayout"
|
||||
x:Name="_mainLayout"
|
||||
Padding="0, 10">
|
||||
<RefreshView
|
||||
IsRefreshing="{Binding IsRefreshing}"
|
||||
Command="{Binding RefreshCommand}"
|
||||
VerticalOptions="FillAndExpand"
|
||||
BackgroundColor="{DynamicResource BackgroundColor}">
|
||||
<controls:ExtendedCollectionView
|
||||
ItemsSource="{Binding LoginRequests}"
|
||||
ItemTemplate="{StaticResource loginRequestTemplate}"
|
||||
SelectionMode="Single"
|
||||
ExtraDataForLogging="Login requests page" >
|
||||
<controls:ExtendedCollectionView.Behaviors>
|
||||
<xct:EventToCommandBehavior
|
||||
EventName="SelectionChanged"
|
||||
Command="{Binding AnswerRequestCommand}"
|
||||
EventArgsConverter="{StaticResource SelectionChangedEventArgsConverter}" />
|
||||
</controls:ExtendedCollectionView.Behaviors>
|
||||
</controls:ExtendedCollectionView>
|
||||
</RefreshView>
|
||||
<controls:IconLabelButton
|
||||
VerticalOptions="End"
|
||||
Margin="10,0"
|
||||
Icon="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
||||
Label="{u:I18n DeclineAllRequests}"
|
||||
ButtonCommand="{Binding DeclineAllRequestsCommand}"/>
|
||||
</StackLayout>
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentView
|
||||
x:Name="_mainContent">
|
||||
</ContentView>
|
||||
|
||||
</pages:BaseContentPage>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class LoginPasswordlessRequestsListPage : BaseContentPage
|
||||
{
|
||||
private LoginPasswordlessRequestsListViewModel _vm;
|
||||
|
||||
public LoginPasswordlessRequestsListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
SetActivityIndicator(_mainContent);
|
||||
_vm = BindingContext as LoginPasswordlessRequestsListViewModel;
|
||||
_vm.Page = this;
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
await LoadOnAppearedAsync(_mainLayout, false, _vm.RefreshAsync, _mainContent);
|
||||
}
|
||||
|
||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
139
src/App/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs
Normal file
139
src/App/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs
Normal file
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LoginPasswordlessRequestsListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private bool _isRefreshing;
|
||||
|
||||
public LoginPasswordlessRequestsListViewModel()
|
||||
{
|
||||
_authService = ServiceContainer.Resolve<IAuthService>();
|
||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
|
||||
|
||||
PageTitle = AppResources.PendingLogInRequests;
|
||||
LoginRequests = new ObservableRangeCollection<PasswordlessLoginResponse>();
|
||||
|
||||
AnswerRequestCommand = new AsyncCommand<PasswordlessLoginResponse>(PasswordlessLoginAsync,
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
DeclineAllRequestsCommand = new AsyncCommand(DeclineAllRequestsAsync,
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
RefreshCommand = new Command(async () => await RefreshAsync());
|
||||
}
|
||||
|
||||
public ICommand RefreshCommand { get; }
|
||||
|
||||
public AsyncCommand<PasswordlessLoginResponse> AnswerRequestCommand { get; }
|
||||
|
||||
public AsyncCommand DeclineAllRequestsCommand { get; }
|
||||
|
||||
public ObservableRangeCollection<PasswordlessLoginResponse> LoginRequests { get; }
|
||||
|
||||
public bool IsRefreshing
|
||||
{
|
||||
get => _isRefreshing;
|
||||
set => SetProperty(ref _isRefreshing, value);
|
||||
}
|
||||
|
||||
public async Task RefreshAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsRefreshing = true;
|
||||
LoginRequests.ReplaceRange(await _authService.GetActivePasswordlessLoginRequestsAsync());
|
||||
if (!LoginRequests.Any())
|
||||
{
|
||||
Page.Navigation.PopModalAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PasswordlessLoginAsync(PasswordlessLoginResponse request)
|
||||
{
|
||||
if (request.IsExpired)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
||||
await Page.Navigation.PopModalAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(request.Id);
|
||||
if (loginRequestData.IsAnswered)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.ThisRequestIsNoLongerValid);
|
||||
return;
|
||||
}
|
||||
|
||||
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
|
||||
{
|
||||
PubKey = loginRequestData.PublicKey,
|
||||
Id = loginRequestData.Id,
|
||||
IpAddress = loginRequestData.RequestIpAddress,
|
||||
Email = await _stateService.GetEmailAsync(),
|
||||
FingerprintPhrase = loginRequestData.RequestFingerprint,
|
||||
RequestDate = loginRequestData.CreationDate,
|
||||
DeviceType = loginRequestData.RequestDeviceType,
|
||||
Origin = loginRequestData.Origin
|
||||
});
|
||||
|
||||
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
|
||||
}
|
||||
|
||||
private async Task DeclineAllRequestsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await _platformUtilsService.ShowDialogAsync(AppResources.AreYouSureYouWantToDeclineAllPendingLogInRequests, null, AppResources.Yes, AppResources.No))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
var taskList = new List<Task>();
|
||||
foreach (var request in LoginRequests)
|
||||
{
|
||||
taskList.Add(_authService.PasswordlessLoginAsync(request.Id, request.PublicKey, false));
|
||||
}
|
||||
await Task.WhenAll(taskList);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
await RefreshAsync();
|
||||
_platformUtilsService.ShowToast("info", null, AppResources.RequestsDeclined);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex);
|
||||
RefreshAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ using Bit.Core.Abstractions;
|
|||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
@ -35,6 +36,7 @@ namespace Bit.App.Pages
|
|||
private readonly IClipboardService _clipboardService;
|
||||
private readonly ILogger _loggerService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IAuthService _authService;
|
||||
private readonly IWatchDeviceService _watchDeviceService;
|
||||
private const int CustomVaultTimeoutValue = -100;
|
||||
|
||||
|
@ -49,7 +51,6 @@ namespace Bit.App.Pages
|
|||
private bool _reportLoggingEnabled;
|
||||
private bool _approvePasswordlessLoginRequests;
|
||||
private bool _shouldConnectToWatch;
|
||||
|
||||
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
||||
new List<KeyValuePair<string, int?>>
|
||||
{
|
||||
|
@ -92,8 +93,8 @@ namespace Bit.App.Pages
|
|||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
||||
_authService = ServiceContainer.Resolve<IAuthService>();
|
||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||
|
||||
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||
PageTitle = AppResources.Settings;
|
||||
|
||||
|
@ -144,7 +145,6 @@ namespace Bit.App.Pages
|
|||
!await _keyConnectorService.GetUsesKeyConnector();
|
||||
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
|
||||
|
||||
_shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync();
|
||||
|
||||
BuildList();
|
||||
|
@ -563,6 +563,14 @@ namespace Bit.App.Pages
|
|||
ExecuteAsync = () => TwoStepAsync()
|
||||
}
|
||||
};
|
||||
if (_approvePasswordlessLoginRequests)
|
||||
{
|
||||
manageItems.Add(new SettingsPageListItem
|
||||
{
|
||||
Name = AppResources.PendingLogInRequests,
|
||||
ExecuteAsync = () => PendingLoginRequestsAsync()
|
||||
});
|
||||
}
|
||||
if (_supportsBiometric || _biometric)
|
||||
{
|
||||
var biometricName = AppResources.Biometrics;
|
||||
|
@ -754,6 +762,25 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
|
||||
private async Task PendingLoginRequestsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var requests = await _authService.GetActivePasswordlessLoginRequestsAsync();
|
||||
if (requests == null || !requests.Any())
|
||||
{
|
||||
_platformUtilsService.ShowToast("info", null, AppResources.NoPendingRequests);
|
||||
return;
|
||||
}
|
||||
|
||||
Page.Navigation.PushModalAsync(new NavigationPage(new LoginPasswordlessRequestsListPage())).FireAndForget();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IncludeLinksWithSubscriptionInfo()
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
|
|
45
src/App/Resources/AppResources.Designer.cs
generated
45
src/App/Resources/AppResources.Designer.cs
generated
|
@ -562,6 +562,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure you want to decline all pending login requests?.
|
||||
/// </summary>
|
||||
public static string AreYouSureYouWantToDeclineAllPendingLogInRequests {
|
||||
get {
|
||||
return ResourceManager.GetString("AreYouSureYouWantToDeclineAllPendingLogInRequests", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure you want to turn on screen capture?.
|
||||
/// </summary>
|
||||
|
@ -1759,6 +1768,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Decline all requests.
|
||||
/// </summary>
|
||||
public static string DeclineAllRequests {
|
||||
get {
|
||||
return ResourceManager.GetString("DeclineAllRequests", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Default.
|
||||
/// </summary>
|
||||
|
@ -4283,6 +4301,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No pending requests.
|
||||
/// </summary>
|
||||
public static string NoPendingRequests {
|
||||
get {
|
||||
return ResourceManager.GetString("NoPendingRequests", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Nord.
|
||||
/// </summary>
|
||||
|
@ -4770,6 +4797,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pending login requests.
|
||||
/// </summary>
|
||||
public static string PendingLogInRequests {
|
||||
get {
|
||||
return ResourceManager.GetString("PendingLogInRequests", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An organization policy is affecting your ownership options..
|
||||
/// </summary>
|
||||
|
@ -5122,6 +5158,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Requests declined.
|
||||
/// </summary>
|
||||
public static string RequestsDeclined {
|
||||
get {
|
||||
return ResourceManager.GetString("RequestsDeclined", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Resend code.
|
||||
/// </summary>
|
||||
|
|
|
@ -2523,6 +2523,21 @@ Do you want to switch to this account?</value>
|
|||
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
|
||||
<value>This request is no longer valid</value>
|
||||
</data>
|
||||
<data name="PendingLogInRequests" xml:space="preserve">
|
||||
<value>Pending login requests</value>
|
||||
</data>
|
||||
<data name="DeclineAllRequests" xml:space="preserve">
|
||||
<value>Decline all requests</value>
|
||||
</data>
|
||||
<data name="AreYouSureYouWantToDeclineAllPendingLogInRequests" xml:space="preserve">
|
||||
<value>Are you sure you want to decline all pending login requests?</value>
|
||||
</data>
|
||||
<data name="RequestsDeclined" xml:space="preserve">
|
||||
<value>Requests declined</value>
|
||||
</data>
|
||||
<data name="NoPendingRequests" xml:space="preserve">
|
||||
<value>No pending requests</value>
|
||||
</data>
|
||||
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
|
||||
<value>Enable camera permission to use the scanner</value>
|
||||
</data>
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace Bit.Core.Abstractions
|
|||
Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
|
||||
|
||||
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
|
||||
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
|
@ -494,6 +495,12 @@ namespace Bit.Core.Services
|
|||
return await _apiService.GetAuthRequestAsync();
|
||||
}
|
||||
|
||||
public async Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync()
|
||||
{
|
||||
var requests = await GetPasswordlessLoginRequestsAsync();
|
||||
return requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
|
||||
}
|
||||
|
||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
||||
{
|
||||
return await _apiService.GetAuthRequestAsync(id);
|
||||
|
|
Loading…
Reference in a new issue