mirror of
https://github.com/bitwarden/mobile
synced 2025-12-11 13:53:29 +00:00
[PM-5154] Implement iOS Passkey -> Add login item (#3019)
* PM-5154 Implement iOS passkey add login * PM-5154 Added Username to Create new login for passkey, for this the param was changed to the Fido2ConfirmNewCredentialParams object so we have access to the proper values. Also added back RpId to the params to have access to it when creating the vault item. Finally added loading to saving the passkey as new login
This commit is contained in:
committed by
GitHub
parent
9f92fdeb29
commit
e34a58e875
@@ -34,6 +34,6 @@ namespace Bit.Core.Abstractions
|
||||
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
|
||||
Task SoftDeleteWithServerAsync(string id);
|
||||
Task RestoreWithServerAsync(string id);
|
||||
Task<string> CreateNewLoginForPasskeyAsync(string rpId);
|
||||
Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ namespace Bit.Core.Abstractions
|
||||
/// Whether or not the user must be verified before completing the operation.
|
||||
/// </summary>
|
||||
public bool UserVerification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The relying party identifier
|
||||
/// </summary>
|
||||
public string RpId { get; set; }
|
||||
}
|
||||
|
||||
public interface IFido2MakeCredentialUserInterface : IFido2UserInterface
|
||||
|
||||
@@ -1286,17 +1286,18 @@ namespace Bit.Core.Services
|
||||
cipher.PasswordHistory = encPhs;
|
||||
}
|
||||
|
||||
public async Task<string> CreateNewLoginForPasskeyAsync(string rpId)
|
||||
public async Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams)
|
||||
{
|
||||
var newCipher = new CipherView
|
||||
{
|
||||
Name = rpId,
|
||||
Name = newPasskeyParams.CredentialName,
|
||||
Type = CipherType.Login,
|
||||
Login = new LoginView
|
||||
{
|
||||
Username = newPasskeyParams.UserName,
|
||||
Uris = new List<LoginUriView>
|
||||
{
|
||||
new LoginUriView { Uri = rpId }
|
||||
new LoginUriView { Uri = newPasskeyParams.RpId }
|
||||
}
|
||||
},
|
||||
Card = new CardView(),
|
||||
|
||||
@@ -47,7 +47,8 @@ namespace Bit.Core.Services
|
||||
{
|
||||
CredentialName = makeCredentialParams.RpEntity.Name,
|
||||
UserName = makeCredentialParams.UserEntity.Name,
|
||||
UserVerification = makeCredentialParams.RequireUserVerification
|
||||
UserVerification = makeCredentialParams.RequireUserVerification,
|
||||
RpId = makeCredentialParams.RpEntity.Id
|
||||
});
|
||||
|
||||
var cipherId = response.CipherId;
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
_context.ConfirmNewCredentialTcs?.SetCanceled();
|
||||
_context.ConfirmNewCredentialTcs = new TaskCompletionSource<(string CipherId, bool UserVerified)>();
|
||||
_context.PasskeyCreationParams = confirmNewCredentialParams;
|
||||
|
||||
_onConfirmingNewCredential();
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
{
|
||||
@@ -20,12 +30,32 @@ namespace Bit.iOS.Autofill
|
||||
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();
|
||||
@@ -36,6 +66,39 @@ namespace Bit.iOS.Autofill
|
||||
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();
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
@@ -16,6 +17,7 @@ using CoreFoundation;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
@@ -147,7 +149,13 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
SavePasskeyAsNewLoginAsync().FireAndForget(ex =>
|
||||
{
|
||||
_platformUtilsService.Value.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred).FireAndForget();
|
||||
var message = AppResources.AnErrorHasOccurred;
|
||||
if (ex is ApiException apiEx && apiEx.Error != null)
|
||||
{
|
||||
message = apiEx.Error.GetSingleMessage();
|
||||
}
|
||||
|
||||
_platformUtilsService.Value.ShowDialogAsync(AppResources.GenericErrorMessage, message).FireAndForget();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -159,9 +167,27 @@ namespace Bit.iOS.Autofill
|
||||
return;
|
||||
}
|
||||
|
||||
var cipherId = await _cipherService.Value.CreateNewLoginForPasskeyAsync(Context.PasskeyCredentialIdentity.RelyingPartyIdentifier);
|
||||
if (Context.PasskeyCreationParams is null)
|
||||
{
|
||||
Context?.ConfirmNewCredentialTcs?.TrySetException(new InvalidOperationException("Trying to save passkey as new login wihout creation params."));
|
||||
return;
|
||||
}
|
||||
|
||||
var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving);
|
||||
|
||||
try
|
||||
{
|
||||
PresentViewController(loadingAlert, true, null);
|
||||
|
||||
var cipherId = await _cipherService.Value.CreateNewLoginForPasskeyAsync(Context.PasskeyCreationParams.Value);
|
||||
Context.ConfirmNewCredentialTcs.TrySetResult((cipherId, true));
|
||||
}
|
||||
catch
|
||||
{
|
||||
await loadingAlert.DismissViewControllerAsync(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
||||
{
|
||||
@@ -290,7 +316,7 @@ namespace Bit.iOS.Autofill
|
||||
return headerItemView;
|
||||
}
|
||||
|
||||
return new UIView(CGRect.Empty);// base.GetViewForHeader(tableView, section);
|
||||
return new UIView(CGRect.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,9 @@ namespace Bit.iOS.Autofill.Models
|
||||
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; }
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -28,7 +24,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
private IStorageService _storageService;
|
||||
private IEnumerable<FolderView> _folders;
|
||||
|
||||
public LoginAddViewController(IntPtr handle)
|
||||
protected LoginAddViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
{ }
|
||||
|
||||
@@ -47,6 +43,8 @@ namespace Bit.iOS.Core.Controllers
|
||||
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");
|
||||
@@ -127,7 +125,9 @@ namespace Bit.iOS.Core.Controllers
|
||||
base.ViewDidAppear(animated);
|
||||
}
|
||||
|
||||
protected async Task SaveAsync()
|
||||
protected virtual async Task SaveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
/*
|
||||
if (!_connectivity.IsConnected)
|
||||
@@ -137,7 +137,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
}
|
||||
*/
|
||||
|
||||
if (string.IsNullOrWhiteSpace(PasswordCell?.TextField?.Text))
|
||||
if (!IsCreatingPasskey && string.IsNullOrWhiteSpace(PasswordCell?.TextField?.Text))
|
||||
{
|
||||
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Password), AppResources.Ok);
|
||||
@@ -180,10 +180,29 @@ namespace Bit.iOS.Core.Controllers
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -202,12 +221,10 @@ namespace Bit.iOS.Core.Controllers
|
||||
}
|
||||
Success(cipherDomain.Id);
|
||||
}
|
||||
catch (ApiException e)
|
||||
catch
|
||||
{
|
||||
if (e?.Error != null)
|
||||
{
|
||||
DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
|
||||
}
|
||||
await loadingAlert.DismissViewControllerAsync(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user