Skip to content

Commit

Permalink
PM-5154 Implement iOS passkey add login
Browse files Browse the repository at this point in the history
  • Loading branch information
fedemkr committed Feb 21, 2024
1 parent 16e1b60 commit db4219a
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 57 deletions.
1 change: 1 addition & 0 deletions src/iOS.Autofill/Fido2MakeCredentialUserInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public Fido2MakeCredentialUserInterface(Func<Task> ensureUnlockedVaultCallback,
{
_context.ConfirmNewCredentialTcs?.SetCanceled();
_context.ConfirmNewCredentialTcs = new TaskCompletionSource<(string CipherId, bool UserVerified)>();
_context.PasskeyCreationParams = confirmNewCredentialParams;

_onConfirmingNewCredential();

Expand Down
65 changes: 64 additions & 1 deletion src/iOS.Autofill/LoginAddViewController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Autofill.Models;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using Foundation;
using UIKit;
Expand All @@ -7,6 +15,8 @@ namespace Bit.iOS.Autofill
{
public partial class LoginAddViewController : Core.Controllers.LoginAddViewController
{
LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();

public LoginAddViewController(IntPtr handle)
: base(handle)
{
Expand All @@ -20,12 +30,32 @@ public LoginAddViewController(IntPtr handle)
public override UIBarButtonItem BaseCancelButton => CancelBarButton;
public override UIBarButtonItem BaseSaveButton => SaveBarButton;

public override Action<string> Success => id =>
private new Context Context => (Context)base.Context;

public override Action<string> Success => cipherId =>
{
if (IsCreatingPasskey)
{
Context.ConfirmNewCredentialTcs.TrySetResult((cipherId, true));
return;
}

LoginListController?.DismissModal();
LoginSearchController?.DismissModal();
};

public override void ViewDidLoad()
{
IsCreatingPasskey = Context.IsCreatingPasskey;
if (IsCreatingPasskey)
{
NameCell.TextField.Text = Context.PasskeyCreationParams?.CredentialName;
UsernameCell.TextField.Text = Context.PasskeyCreationParams?.UserName;
}

base.ViewDidLoad();
}

partial void CancelBarButton_Activated(UIBarButtonItem sender)
{
Cancel();
Expand All @@ -36,6 +66,39 @@ private void Cancel()
DismissViewController(true, null);
}

protected override async Task EncryptAndSaveAsync(CipherView cipher)
{
if (!IsCreatingPasskey)
{
await base.EncryptAndSaveAsync(cipher);
return;
}

if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
{
Context?.ConfirmNewCredentialTcs?.TrySetException(new InvalidOperationException("Trying to save passkey as new login on iOS less than 17."));
return;
}

var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving);
try
{
PresentViewController(loadingAlert, true, null);

var encryptedCipher = await _cipherService.Value.EncryptAsync(cipher);
await _cipherService.Value.SaveWithServerAsync(encryptedCipher);

await loadingAlert.DismissViewControllerAsync(true);

Success(encryptedCipher.Id);
}
catch
{
await loadingAlert.DismissViewControllerAsync(false);
throw;
}
}

async partial void SaveBarButton_Activated(UIBarButtonItem sender)
{
await SaveAsync();
Expand Down
2 changes: 1 addition & 1 deletion src/iOS.Autofill/LoginListViewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ public override UIView GetViewForHeader(UITableView tableView, nint section)
return headerItemView;
}

return new UIView(CGRect.Empty);// base.GetViewForHeader(tableView, section);
return new UIView(CGRect.Empty);
}
catch (Exception ex)
{
Expand Down
2 changes: 2 additions & 0 deletions src/iOS.Autofill/Models/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ public class Context : AppExtensionContext
public ASPasswordCredentialIdentity PasswordCredentialIdentity { get; set; }
public ASPasskeyCredentialRequest PasskeyCredentialRequest { get; set; }
public bool Configuring { get; set; }

public bool IsCreatingPasskey { get; set; }
public Fido2ConfirmNewCredentialParams? PasskeyCreationParams { get; set; }
public TaskCompletionSource<bool> UnlockVaultTcs { get; set; }
public TaskCompletionSource<(string CipherId, bool UserVerified)> ConfirmNewCredentialTcs { get; set; }

Expand Down
127 changes: 72 additions & 55 deletions src/iOS.Core/Controllers/LoginAddViewController.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AuthenticationServices;
using Bit.App.Models;
using Bit.App.Models;
using Bit.App.Pages;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Core.Models;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using Foundation;
using UIKit;
using Microsoft.Maui.Controls.Compatibility;
using UIKit;

namespace Bit.iOS.Core.Controllers
{
Expand All @@ -28,7 +24,7 @@ public abstract class LoginAddViewController : ExtendedUITableViewController
private IStorageService _storageService;
private IEnumerable<FolderView> _folders;

public LoginAddViewController(IntPtr handle)
protected LoginAddViewController(IntPtr handle)
: base(handle)
{ }

Expand All @@ -47,6 +43,8 @@ public LoginAddViewController(IntPtr handle)
public abstract UIBarButtonItem BaseSaveButton { get; }
public abstract Action<string> Success { get; }

protected bool IsCreatingPasskey { get; set; }

public override void ViewDidLoad()
{
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
Expand Down Expand Up @@ -127,63 +125,84 @@ public override void ViewDidAppear(bool animated)
base.ViewDidAppear(animated);
}

protected async Task SaveAsync()
protected virtual async Task SaveAsync()
{
/*
if (!_connectivity.IsConnected)
{
AlertNoConnection();
return;
}
*/

if (string.IsNullOrWhiteSpace(PasswordCell?.TextField?.Text))
try
{
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
AppResources.Password), AppResources.Ok);
return;
}
/*
if (!_connectivity.IsConnected)
{
AlertNoConnection();
return;
}
*/

if (string.IsNullOrWhiteSpace(NameCell?.TextField?.Text))
{
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
AppResources.Name), AppResources.Ok);
return;
}
if (!IsCreatingPasskey && string.IsNullOrWhiteSpace(PasswordCell?.TextField?.Text))
{
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
AppResources.Password), AppResources.Ok);
return;
}

var cipher = new CipherView
{
Name = NameCell.TextField.Text,
Notes = string.IsNullOrWhiteSpace(NotesCell?.TextView?.Text) ? null : NotesCell.TextView.Text,
Favorite = FavoriteCell.Switch.On,
FolderId = FolderCell.SelectedIndex == 0 ?
null : _folders.ElementAtOrDefault(FolderCell.SelectedIndex - 1)?.Id,
Type = Bit.Core.Enums.CipherType.Login,
Login = new LoginView
if (string.IsNullOrWhiteSpace(NameCell?.TextField?.Text))
{
Uris = null,
Username = string.IsNullOrWhiteSpace(UsernameCell?.TextField?.Text) ?
null : UsernameCell.TextField.Text,
Password = string.IsNullOrWhiteSpace(PasswordCell.TextField.Text) ?
null : PasswordCell.TextField.Text,
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
AppResources.Name), AppResources.Ok);
return;
}
};

if (!string.IsNullOrWhiteSpace(UriCell?.TextField?.Text))
{
cipher.Login.Uris = new List<LoginUriView>
var cipher = new CipherView
{
new LoginUriView
Name = NameCell.TextField.Text,
Notes = string.IsNullOrWhiteSpace(NotesCell?.TextView?.Text) ? null : NotesCell.TextView.Text,
Favorite = FavoriteCell.Switch.On,
FolderId = FolderCell.SelectedIndex == 0 ?
null : _folders.ElementAtOrDefault(FolderCell.SelectedIndex - 1)?.Id,
Type = Bit.Core.Enums.CipherType.Login,
Login = new LoginView
{
Uri = UriCell.TextField.Text
Uris = null,
Username = string.IsNullOrWhiteSpace(UsernameCell?.TextField?.Text) ?
null : UsernameCell.TextField.Text,
Password = string.IsNullOrWhiteSpace(PasswordCell.TextField.Text) ?
null : PasswordCell.TextField.Text,
}
};

if (!string.IsNullOrWhiteSpace(UriCell?.TextField?.Text))
{
cipher.Login.Uris = new List<LoginUriView>
{
new LoginUriView
{
Uri = UriCell.TextField.Text
}
};
}

await EncryptAndSaveAsync(cipher);
}
catch (ApiException e)
{
if (e?.Error != null)
{
DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
}
}

protected virtual async Task EncryptAndSaveAsync(CipherView cipher)
{
var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving);
PresentViewController(loadingAlert, true, null);
try
{
PresentViewController(loadingAlert, true, null);

var cipherDomain = await _cipherService.EncryptAsync(cipher);
await _cipherService.SaveWithServerAsync(cipherDomain);
await loadingAlert.DismissViewControllerAsync(true);
Expand All @@ -202,12 +221,10 @@ protected async Task SaveAsync()
}
Success(cipherDomain.Id);
}
catch (ApiException e)
catch
{
if (e?.Error != null)
{
DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
}
await loadingAlert.DismissViewControllerAsync(false);
throw;
}
}

Expand Down

0 comments on commit db4219a

Please sign in to comment.