1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-15 15:53:44 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
André Bispo
7b237b4efe [SG-471] Added mock services. Added Accept/reject command binding, navigation and toast messages. 2022-07-27 17:42:50 +01:00
André Bispo
90fe9f8600 [SG-471] Added properties to speed up page bindings 2022-07-27 17:09:24 +01:00
André Bispo
4e0da8fd96 [SG-471] Added text resources and arguments to Page. 2022-07-27 15:29:23 +01:00
André Bispo
e52f527eea [SSG-471] Added UI for the device login request response. 2022-07-22 14:59:59 +01:00
8 changed files with 4064 additions and 5638 deletions

View File

@@ -122,6 +122,9 @@
<SubType>Code</SubType>
</Compile>
<Compile Remove="Pages\Accounts\AccountsPopupPage.xaml.cs" />
<Compile Update="Pages\Accounts\LoginPasswordlessPage.xaml.cs">
<DependentUpon>LoginPasswordlessPage.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,90 @@
<?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"
x:Class="Bit.App.Pages.LoginPasswordlessPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LoginPasswordlessViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:LoginPasswordlessViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValue" />
<u:IsNotNullConverter x:Key="notNull" />
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView x:Name="_scrollView" Padding="7, 0, 7, 20">
<StackLayout>
<Label
Text="{u:I18n AreYouTryingToLogIn}"
FontSize="Title"
FontAttributes="Bold"
Margin="0,14,0,21"/>
<Label
Text="{Binding LogInAttempByLabel}"
FontSize="Small"
Margin="0,0,0,24"/>
<Label
Text="{u:I18n FingerprintPhrase}"
FontSize="Small"
FontAttributes="Bold"/>
<controls:MonoLabel
FormattedText="{Binding FingerprintPhraseFormatted}"
FontSize="Medium"
Margin="0,0,0,27"/>
<Label
Text="{u:I18n DeviceType}"
FontSize="Small"
FontAttributes="Bold"/>
<Label
Text="{Binding DeviceType}"
FontSize="Small"
Margin="0,0,0,21"/>
<Label
Text="{u:I18n IpAddress}"
FontSize="Small"
FontAttributes="Bold"/>
<Label
Text="{Binding IpAddress}"
FontSize="Small"
Margin="0,0,0,21"/>
<Label
Text="{u:I18n Near}"
FontSize="Small"
FontAttributes="Bold"/>
<Label
Text="{Binding NearLocation}"
FontSize="Small"
Margin="0,0,0,21"/>
<Label
Text="{u:I18n Time}"
FontSize="Small"
FontAttributes="Bold"/>
<Label
Text="{Binding TimeOfRequestText}"
FontSize="Small"
Margin="0,0,0,57"/>
<Button
Text="{u:I18n ConfirmLogIn}"
Command="{Binding AcceptRequestCommand}"
Margin="0,0,0,17"
StyleClass="btn-primary"/>
<Button
Text="{u:I18n DenyLogIn}"
Command="{Binding RejectRequestCommand}"
StyleClass="btn-secundary"/>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View File

@@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
namespace Bit.App.Pages
{
public partial class LoginPasswordlessPage : BaseContentPage
{
private LoginPasswordlessViewModel _vm;
public LoginPasswordlessPage(string fingerprintPhrase, string email, string deviceType, string ipAddress, string location, DateTime requestDate)
{
InitializeComponent();
_vm = BindingContext as LoginPasswordlessViewModel;
_vm.Page = this;
_vm.Email = email;
_vm.DeviceType = deviceType;
_vm.IpAddress = ipAddress;
_vm.NearLocation = location;
_vm.FingerprintPhrase = fingerprintPhrase;
_vm.RequestDate = requestDate;
}
private async void Close_Clicked(object sender, System.EventArgs e)
{
await Close();
}
public async Task Close()
{
if (DoOnce())
{
await Navigation.PopModalAsync();
}
}
}
}

View File

@@ -0,0 +1,189 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Bit.App.Utilities;
using System.Linq;
using Xamarin.CommunityToolkit.ObjectModel;
using System.Windows.Input;
namespace Bit.App.Pages
{
public class LoginPasswordlessViewModel : BaseViewModel
{
private IAuthService _authService;
private IPlatformUtilsService _platformUtilsService;
private ILogger _logger;
private string _logInAttempByLabel;
private string _deviceType;
private FormattedString _fingerprintPhraseFormatted;
private string _fingerprintPhrase;
private string _email;
private string _timeOfRequest;
private DateTime _requestDate;
private string _nearLocation;
private string _ipAddress;
public LoginPasswordlessViewModel()
{
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.LogInRequested;
AcceptRequestCommand = new AsyncCommand(AcceptRequestAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
RejectRequestCommand = new AsyncCommand(RejectRequestAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
}
public ICommand AcceptRequestCommand { get; }
public ICommand RejectRequestCommand { get; }
public string Email
{
get => _email;
set
{
LogInAttempByLabel = string.Format(AppResources.LogInAttemptByOn, value, "bitwarden login test");
SetProperty(ref _email, value);
}
}
public string FingerprintPhrase
{
get => _fingerprintPhrase;
set
{
FingerprintPhraseFormatted = CreateFingerprintPhrase(value);
SetProperty(ref _fingerprintPhrase, value);
}
}
public FormattedString FingerprintPhraseFormatted
{
get => _fingerprintPhraseFormatted;
set => SetProperty(ref _fingerprintPhraseFormatted, value);
}
public string LogInAttempByLabel
{
get => _logInAttempByLabel;
set => SetProperty(ref _logInAttempByLabel, value);
}
public string DeviceType
{
get => _deviceType;
set => SetProperty(ref _deviceType, value);
}
public string IpAddress
{
get => _ipAddress;
set => SetProperty(ref _ipAddress, value);
}
public string NearLocation
{
get => _nearLocation;
set => SetProperty(ref _nearLocation, value);
}
public DateTime RequestDate
{
get => _requestDate;
set
{
TimeOfRequestText = CreateRequestDate();
SetProperty(ref _requestDate, value);
}
}
public string TimeOfRequestText
{
get => _timeOfRequest;
set
{
SetProperty(ref _timeOfRequest, value);
}
}
private FormattedString CreateFingerprintPhrase(string fingerprintPhrase)
{
var fingerprintList = fingerprintPhrase.Split('-').ToList();
var fs = new FormattedString();
var lastFingerprint = fingerprintList.LastOrDefault();
foreach (var fingerprint in fingerprintList)
{
fs.Spans.Add(new Span
{
Text = fingerprint
});
if(fingerprint == lastFingerprint)
{
break;
}
fs.Spans.Add(new Span
{
Text = "-",
TextColor = ThemeManager.GetResourceColor("DangerColor")
});
}
return fs;
}
private string CreateRequestDate()
{
var minutesSinceRequest = RequestDate.ToUniversalTime().Minute - DateTime.UtcNow.Minute;
if(minutesSinceRequest < 5)
{
return AppResources.JustNow;
}
if(minutesSinceRequest < 59)
{
return $"{minutesSinceRequest} {AppResources.MinutesAgo}";
}
return RequestDate.ToShortTimeString();
}
private async Task AcceptRequestAsync()
{
try
{
var res = await _authService.LogInPasswordlessAcceptAsync();
await ((LoginPasswordlessPage)this.Page).Close();
_platformUtilsService.ShowToast("info", null, AppResources.LogInAccepted);
}
catch (Exception ex)
{
_logger.Exception(ex);
}
}
private async Task RejectRequestAsync()
{
try
{
var res = await _authService.LogInPasswordlessRejectAsync();
await ((LoginPasswordlessPage)this.Page).Close();
_platformUtilsService.ShowToast("info", null, AppResources.LogInDenied);
}
catch (Exception ex)
{
_logger.Exception(ex);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2269,4 +2269,46 @@
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Are you sure you want to enable Screen Capture?</value>
</data>
<data name="LogInRequested" xml:space="preserve">
<value>Log in requested</value>
</data>
<data name="AreYouTryingToLogIn" xml:space="preserve">
<value>Are you trying to log in?</value>
</data>
<data name="LogInAttemptByOn" xml:space="preserve">
<value>Log in attempt by {0} on {1}</value>
</data>
<data name="FingerprintPhrase" xml:space="preserve">
<value>Fingerprint phrase</value>
</data>
<data name="DeviceType" xml:space="preserve">
<value>Device Type</value>
</data>
<data name="IpAddress" xml:space="preserve">
<value>IP Address</value>
</data>
<data name="Time" xml:space="preserve">
<value>Time</value>
</data>
<data name="Near" xml:space="preserve">
<value>Near</value>
</data>
<data name="ConfirmLogIn" xml:space="preserve">
<value>Confirm Log In</value>
</data>
<data name="DenyLogIn" xml:space="preserve">
<value>Deny Log In</value>
</data>
<data name="JustNow" xml:space="preserve">
<value>Just Now</value>
</data>
<data name="MinutesAgo" xml:space="preserve">
<value>minutes ago</value>
</data>
<data name="LogInAccepted" xml:space="preserve">
<value>Log in accepted</value>
</data>
<data name="LogInDenied" xml:space="preserve">
<value>Log in denied</value>
</data>
</root>

View File

@@ -25,6 +25,11 @@ namespace Bit.Core.Abstractions
Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId);
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
Task<AuthResult> GetLogInPasswordlessRequestsAsync();
Task<AuthResult> LogInPasswordlessAcceptAsync();
Task<AuthResult> LogInPasswordlessRejectAsync();
void LogOut(Action callback);
void Init();
}

View File

@@ -468,5 +468,9 @@ namespace Bit.Core.Services
TwoFactorProvidersData = null;
SelectedTwoFactorProviderType = null;
}
public async Task<AuthResult> GetLogInPasswordlessRequestsAsync() => await Task.FromResult<AuthResult>(new AuthResult());
public async Task<AuthResult> LogInPasswordlessAcceptAsync() => await Task.FromResult<AuthResult>(new AuthResult());
public async Task<AuthResult> LogInPasswordlessRejectAsync() => await Task.FromResult<AuthResult>(new AuthResult());
}
}