diff --git a/src/Core/Abstractions/IConditionedAwaiterManager.cs b/src/Core/Abstractions/IConditionedAwaiterManager.cs
index 6eb4df5dc..3a6a0ec0f 100644
--- a/src/Core/Abstractions/IConditionedAwaiterManager.cs
+++ b/src/Core/Abstractions/IConditionedAwaiterManager.cs
@@ -1,12 +1,10 @@
-using System;
-using System.Threading.Tasks;
-
-namespace Bit.Core.Abstractions
+namespace Bit.Core.Abstractions
{
public enum AwaiterPrecondition
{
EnvironmentUrlsInited,
- AndroidWindowCreated
+ AndroidWindowCreated,
+ AutofillIOSExtensionViewDidAppear
}
public interface IConditionedAwaiterManager
@@ -14,5 +12,6 @@ namespace Bit.Core.Abstractions
Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition);
void SetAsCompleted(AwaiterPrecondition awaiterPrecondition);
void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex);
+ void Recreate(AwaiterPrecondition awaiterPrecondition);
}
}
diff --git a/src/Core/Resources/Localization/AppResources.Designer.cs b/src/Core/Resources/Localization/AppResources.Designer.cs
index 6ea8bcc87..cc9b7c6ab 100644
--- a/src/Core/Resources/Localization/AppResources.Designer.cs
+++ b/src/Core/Resources/Localization/AppResources.Designer.cs
@@ -7685,6 +7685,15 @@ namespace Bit.Core.Resources.Localization {
}
}
+ ///
+ /// Looks up a localized string similar to Verifying identity....
+ ///
+ public static string VerifyingIdentityEllipsis {
+ get {
+ return ResourceManager.GetString("VerifyingIdentityEllipsis", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Verify master password.
///
diff --git a/src/Core/Resources/Localization/AppResources.resx b/src/Core/Resources/Localization/AppResources.resx
index bfe9be087..36ffeee11 100644
--- a/src/Core/Resources/Localization/AppResources.resx
+++ b/src/Core/Resources/Localization/AppResources.resx
@@ -2936,6 +2936,9 @@ Do you want to switch to this account?
There was a problem reading your passkey for {0}. Try again later.
The parameter is the RpId
+
+ Verifying identity...
+
Passwords
diff --git a/src/Core/Services/ConditionedAwaiterManager.cs b/src/Core/Services/ConditionedAwaiterManager.cs
index 30b744923..62f959e2c 100644
--- a/src/Core/Services/ConditionedAwaiterManager.cs
+++ b/src/Core/Services/ConditionedAwaiterManager.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Threading.Tasks;
+using System.Collections.Concurrent;
using Bit.Core.Abstractions;
namespace Bit.Core.Services
@@ -11,7 +8,8 @@ namespace Bit.Core.Services
private readonly ConcurrentDictionary> _preconditionsTasks = new ConcurrentDictionary>
{
[AwaiterPrecondition.EnvironmentUrlsInited] = new TaskCompletionSource(),
- [AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource()
+ [AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource(),
+ [AwaiterPrecondition.AutofillIOSExtensionViewDidAppear] = new TaskCompletionSource()
};
public Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition)
@@ -39,5 +37,15 @@ namespace Bit.Core.Services
tcs.TrySetException(ex);
}
}
+
+ public void Recreate(AwaiterPrecondition awaiterPrecondition)
+ {
+ if (_preconditionsTasks.TryRemove(awaiterPrecondition, out var oldTcs))
+ {
+ oldTcs.TrySetCanceled();
+
+ _preconditionsTasks.TryAdd(awaiterPrecondition, new TaskCompletionSource());
+ }
+ }
}
}
diff --git a/src/Core/Services/UserVerification/Fido2UserVerificationPreferredServiceStrategy.cs b/src/Core/Services/UserVerification/Fido2UserVerificationPreferredServiceStrategy.cs
index 901ecbd2a..7f42aafa6 100644
--- a/src/Core/Services/UserVerification/Fido2UserVerificationPreferredServiceStrategy.cs
+++ b/src/Core/Services/UserVerification/Fido2UserVerificationPreferredServiceStrategy.cs
@@ -19,7 +19,10 @@ namespace Bit.Core.Services.UserVerification
return true;
}
- options.OnNeedUI?.Invoke();
+ if (options.OnNeedUITask != null)
+ {
+ await options.OnNeedUITask();
+ }
var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync();
if (canPerformOSUnlock)
diff --git a/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs b/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs
index a918f6f58..592803045 100644
--- a/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs
+++ b/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs
@@ -23,7 +23,10 @@ namespace Bit.Core.Services.UserVerification
return true;
}
- options.OnNeedUI?.Invoke();
+ if (options.OnNeedUITask != null)
+ {
+ await options.OnNeedUITask();
+ }
var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync();
if (canPerformOSUnlock)
diff --git a/src/Core/Services/UserVerification/UserVerificationMediatorService.cs b/src/Core/Services/UserVerification/UserVerificationMediatorService.cs
index 6aacff6f7..3fc13be1b 100644
--- a/src/Core/Services/UserVerification/UserVerificationMediatorService.cs
+++ b/src/Core/Services/UserVerification/UserVerificationMediatorService.cs
@@ -39,7 +39,11 @@ namespace Bit.Core.Services.UserVerification
{
if (await ShouldPerformMasterPasswordRepromptAsync(options))
{
- options.OnNeedUI?.Invoke();
+ if (options.OnNeedUITask != null)
+ {
+ await options.OnNeedUITask();
+ }
+
return await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(Enums.CipherRepromptType.Password);
}
diff --git a/src/Core/Utilities/Fido2/Fido2UserVerificationOptions.cs b/src/Core/Utilities/Fido2/Fido2UserVerificationOptions.cs
index b0e9071d2..3304e64c8 100644
--- a/src/Core/Utilities/Fido2/Fido2UserVerificationOptions.cs
+++ b/src/Core/Utilities/Fido2/Fido2UserVerificationOptions.cs
@@ -6,19 +6,19 @@
Fido2UserVerificationPreference userVerificationPreference,
bool hasVaultBeenUnlockedInTransaction,
string rpId = null,
- Action onNeedUI = null)
+ Func onNeedUITask = null)
{
ShouldCheckMasterPasswordReprompt = shouldCheckMasterPasswordReprompt;
UserVerificationPreference = userVerificationPreference;
HasVaultBeenUnlockedInTransaction = hasVaultBeenUnlockedInTransaction;
RpId = rpId;
- OnNeedUI = onNeedUI;
+ OnNeedUITask = onNeedUITask;
}
public bool ShouldCheckMasterPasswordReprompt { get; }
public Fido2UserVerificationPreference UserVerificationPreference { get; }
public bool HasVaultBeenUnlockedInTransaction { get; }
public string RpId { get; }
- public Action OnNeedUI { get; }
+ public Func OnNeedUITask { get; }
}
}
diff --git a/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs b/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs
index d5e5b02df..1b8fdf090 100644
--- a/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs
+++ b/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs
@@ -270,13 +270,21 @@ namespace Bit.iOS.Autofill
userVerificationPreference,
_context.VaultUnlockedDuringThisSession,
_context.PasskeyCredentialIdentity?.RelyingPartyIdentifier,
- () =>
+ async () =>
{
if (_context.IsExecutingWithoutUserInteraction)
{
CancelRequest(ASExtensionErrorCode.UserInteractionRequired);
throw new InvalidOperationNeedsUIException();
}
+
+ // HACK: [PM-6685] There are some devices that end up with a race condition when doing biometrics authentication
+ // that the check is trying to be done before the iOS extension UI is shown, which cause the bio check to fail.
+ // So a workaround is to show a toast which force the iOS extension UI to be shown and then awaiting for the
+ // precondition that the view did appear before continuing with the verification.
+ _platformUtilsService.Value.ShowToast(null, null, AppResources.VerifyingIdentityEllipsis);
+
+ await _conditionedAwaiterManager.Value.GetAwaiterForPrecondition(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear);
})
);
}
diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs
index f2cf4d0eb..51b753769 100644
--- a/src/iOS.Autofill/CredentialProviderViewController.cs
+++ b/src/iOS.Autofill/CredentialProviderViewController.cs
@@ -32,6 +32,7 @@ namespace Bit.iOS.Autofill
private IAccountsManager _accountsManager;
private readonly LazyResolve _stateService = new LazyResolve();
+ private readonly LazyResolve _conditionedAwaiterManager = new LazyResolve();
public CredentialProviderViewController(IntPtr handle)
: base(handle)
@@ -56,6 +57,8 @@ namespace Bit.iOS.Autofill
{
ExtContext = ExtensionContext
};
+
+ _conditionedAwaiterManager.Value.Recreate(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear);
}
catch (Exception ex)
{
@@ -63,6 +66,13 @@ namespace Bit.iOS.Autofill
}
}
+ public override void ViewDidAppear(bool animated)
+ {
+ base.ViewDidAppear(animated);
+
+ _conditionedAwaiterManager.Value.SetAsCompleted(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear);
+ }
+
public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
{
try