mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-4688] Automatically fallback on passkey retrieval if no passkeys are found (#6787)
* [PM-4688] feat: auto-fallback when credential not found * [PM-4688] fix: don't show popup unless needed
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
Fido2AuthenticatorGetAssertionParams,
|
||||
Fido2AuthenticatorMakeCredentialsParams,
|
||||
} from "../../abstractions/fido2/fido2-authenticator.service.abstraction";
|
||||
import { FallbackRequestedError } from "../../abstractions/fido2/fido2-client.service.abstraction";
|
||||
import {
|
||||
Fido2UserInterfaceService,
|
||||
Fido2UserInterfaceSession,
|
||||
@@ -469,7 +470,8 @@ describe("FidoAuthenticatorService", () => {
|
||||
* Spec: If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation.
|
||||
* Deviation: We do not throw error but instead inform the user and allow the user to fallback to browser implementation.
|
||||
**/
|
||||
it("should inform user if no credential exists", async () => {
|
||||
it("should inform user if no credential exists when fallback is not supported", async () => {
|
||||
params.fallbackSupported = false;
|
||||
cipherService.getAllDecrypted.mockResolvedValue([]);
|
||||
userInterfaceSession.informCredentialNotFound.mockResolvedValue();
|
||||
|
||||
@@ -481,6 +483,17 @@ describe("FidoAuthenticatorService", () => {
|
||||
expect(userInterfaceSession.informCredentialNotFound).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should automatically fallback if no credential exists when fallback is supported", async () => {
|
||||
params.fallbackSupported = true;
|
||||
cipherService.getAllDecrypted.mockResolvedValue([]);
|
||||
userInterfaceSession.informCredentialNotFound.mockResolvedValue();
|
||||
|
||||
const result = async () => await authenticator.getAssertion(params, tab);
|
||||
|
||||
await expect(result).rejects.toThrowError(FallbackRequestedError);
|
||||
expect(userInterfaceSession.informCredentialNotFound).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should inform user if credential exists but rpId does not match", async () => {
|
||||
const cipher = await createCipherView({ type: CipherType.Login });
|
||||
cipher.login.fido2Credentials[0].credentialId = credentialId;
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction,
|
||||
PublicKeyCredentialDescriptor,
|
||||
} from "../../abstractions/fido2/fido2-authenticator.service.abstraction";
|
||||
import { FallbackRequestedError } from "../../abstractions/fido2/fido2-client.service.abstraction";
|
||||
import { Fido2UserInterfaceService } from "../../abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||
import { SyncService } from "../../abstractions/sync/sync.service.abstraction";
|
||||
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
||||
@@ -221,6 +222,11 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
||||
this.logService?.info(
|
||||
`[Fido2Authenticator] Aborting because no matching credentials were found in the vault.`
|
||||
);
|
||||
|
||||
if (params.fallbackSupported) {
|
||||
throw new FallbackRequestedError();
|
||||
}
|
||||
|
||||
await userInterfaceSession.informCredentialNotFound();
|
||||
throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed);
|
||||
}
|
||||
|
||||
@@ -267,6 +267,11 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
|
||||
abortController
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof FallbackRequestedError) {
|
||||
this.logService?.info(`[Fido2Client] Aborting because of auto fallback`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (
|
||||
abortController.signal.aborted &&
|
||||
abortController.signal.reason === UserRequestedFallbackAbortReason
|
||||
|
||||
Reference in New Issue
Block a user