diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts
index cbc73a8d70d..cbe3fec016b 100644
--- a/apps/browser/src/background/runtime.background.ts
+++ b/apps/browser/src/background/runtime.background.ts
@@ -209,9 +209,17 @@ export default class RuntimeBackground {
this.platformUtilsService.copyToClipboard(msg.identifier, { window: window });
break;
case "fido2RegisterCredentialRequest":
- return await this.main.fido2Service.createCredential(msg.data);
+ try {
+ return { result: await this.main.fido2Service.createCredential(msg.data) };
+ } catch (error) {
+ return { error: { ...error, message: error.message } };
+ }
case "fido2GetCredentialRequest":
- return await this.main.fido2Service.assertCredential(msg.data);
+ try {
+ return { result: await this.main.fido2Service.assertCredential(msg.data) };
+ } catch (error) {
+ return { error: { ...error, message: error.message } };
+ }
}
return undefined;
}
diff --git a/apps/browser/src/content/webauthn/content-script.ts b/apps/browser/src/content/webauthn/content-script.ts
index 6e3c04f1b54..51b32a29314 100644
--- a/apps/browser/src/content/webauthn/content-script.ts
+++ b/apps/browser/src/content/webauthn/content-script.ts
@@ -21,8 +21,8 @@ messenger.addHandler(async (message) => {
(response) => {
resolve({
type: MessageType.CredentialCreationResponse,
- approved: true,
- result: response,
+ result: response.result,
+ error: response.error,
});
}
);
@@ -39,7 +39,8 @@ messenger.addHandler(async (message) => {
(response) => {
resolve({
type: MessageType.CredentialGetResponse,
- result: response,
+ result: response.result,
+ error: response.error,
});
}
);
diff --git a/apps/browser/src/content/webauthn/messaging/error.ts b/apps/browser/src/content/webauthn/messaging/error.ts
new file mode 100644
index 00000000000..1513777f3c1
--- /dev/null
+++ b/apps/browser/src/content/webauthn/messaging/error.ts
@@ -0,0 +1,10 @@
+export enum MessageErrorType {
+ RequestAborted,
+}
+
+export type RequestAbortedMessageError = {
+ fallbackRequested: boolean;
+ message: string;
+};
+
+export type MessageError = RequestAbortedMessageError;
diff --git a/apps/browser/src/content/webauthn/messaging/message.ts b/apps/browser/src/content/webauthn/messaging/message.ts
index 06d0c296749..ffde8346786 100644
--- a/apps/browser/src/content/webauthn/messaging/message.ts
+++ b/apps/browser/src/content/webauthn/messaging/message.ts
@@ -5,6 +5,8 @@ import {
CredentialRegistrationResult,
} from "@bitwarden/common/abstractions/fido2/fido2.service.abstraction";
+import { MessageError } from "./error";
+
export enum MessageType {
CredentialCreationRequest,
CredentialCreationResponse,
@@ -21,8 +23,8 @@ export type CredentialCreationRequest = {
export type CredentialCreationResponse = {
type: MessageType.CredentialCreationResponse;
- approved: boolean;
result?: CredentialRegistrationResult;
+ error?: MessageError;
};
export type CredentialGetRequest = {
@@ -33,6 +35,7 @@ export type CredentialGetRequest = {
export type CredentialGetResponse = {
type: MessageType.CredentialGetResponse;
result?: CredentialAssertResult;
+ error?: MessageError;
};
export type AbortRequest = {
diff --git a/apps/browser/src/content/webauthn/page-script.ts b/apps/browser/src/content/webauthn/page-script.ts
index b5438cdfc81..bc214784069 100644
--- a/apps/browser/src/content/webauthn/page-script.ts
+++ b/apps/browser/src/content/webauthn/page-script.ts
@@ -19,10 +19,18 @@ navigator.credentials.create = async (options?: CredentialCreationOptions): Prom
data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin),
});
- if (response.type !== MessageType.CredentialCreationResponse || !response.approved) {
+ if (response.type !== MessageType.CredentialCreationResponse) {
return await browserCredentials.create(options);
}
+ if (response.error && response.error.fallbackRequested) {
+ return await browserCredentials.create(options);
+ }
+
+ if (response.error) {
+ throw new Error(response.error.message ?? "The request was aborted.");
+ }
+
return WebauthnUtils.mapCredentialRegistrationResult(response.result);
};
@@ -36,5 +44,13 @@ navigator.credentials.get = async (options?: CredentialRequestOptions): Promise<
return await browserCredentials.get(options);
}
+ if (response.error && response.error.fallbackRequested) {
+ return await browserCredentials.create(options);
+ }
+
+ if (response.error) {
+ throw new Error(response.error.message ?? "The request was aborted.");
+ }
+
return WebauthnUtils.mapCredentialAssertResult(response.result);
};
diff --git a/apps/browser/src/popup/fido2/fido2.component.html b/apps/browser/src/popup/fido2/fido2.component.html
index b7c0068b2df..efed0eb967d 100644
--- a/apps/browser/src/popup/fido2/fido2.component.html
+++ b/apps/browser/src/popup/fido2/fido2.component.html
@@ -9,5 +9,8 @@
Authenticate
Create
-
+
+
diff --git a/apps/browser/src/popup/fido2/fido2.component.ts b/apps/browser/src/popup/fido2/fido2.component.ts
index d59806fed9c..7695001dbad 100644
--- a/apps/browser/src/popup/fido2/fido2.component.ts
+++ b/apps/browser/src/popup/fido2/fido2.component.ts
@@ -35,23 +35,25 @@ export class Fido2Component {
BrowserFido2UserInterfaceService.sendMessage({
requestId: data.requestId,
type: "RequestCancelled",
+ fallbackRequested: true,
});
}
window.close();
}
- cancel() {
- this.unload();
+ cancel(fallback: boolean) {
+ this.unload(fallback);
window.close();
}
@HostListener("window:unload")
- unload() {
+ unload(fallback = true) {
const data = this.data;
BrowserFido2UserInterfaceService.sendMessage({
requestId: data.requestId,
type: "RequestCancelled",
+ fallbackRequested: fallback,
});
}
}
diff --git a/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts b/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts
index cf4c1d70278..e8f18923f52 100644
--- a/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts
+++ b/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts
@@ -3,6 +3,7 @@ import { filter, first, lastValueFrom, Subject, takeUntil } from "rxjs";
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/abstractions/fido2/fido2-user-interface.service.abstraction";
import { Utils } from "@bitwarden/common/misc/utils";
+import { RequestAbortedError } from "../../../../../libs/common/src/abstractions/fido2/fido2.service.abstraction";
import { BrowserApi } from "../../browser/browserApi";
import { PopupUtilsService } from "../../popup/services/popup-utils.service";
@@ -23,6 +24,7 @@ export type BrowserFido2Message = { requestId: string } & (
}
| {
type: "RequestCancelled";
+ fallbackRequested: boolean;
}
);
@@ -68,6 +70,10 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
return true;
}
+ if (response.type === "RequestCancelled") {
+ throw new RequestAbortedError(response.fallbackRequested);
+ }
+
return false;
}
@@ -93,6 +99,10 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
return true;
}
+ if (response.type === "RequestCancelled") {
+ throw new RequestAbortedError(response.fallbackRequested);
+ }
+
return false;
}
diff --git a/libs/common/src/abstractions/fido2/fido2.service.abstraction.ts b/libs/common/src/abstractions/fido2/fido2.service.abstraction.ts
index 9466cb6cb27..1e9d9d382e7 100644
--- a/libs/common/src/abstractions/fido2/fido2.service.abstraction.ts
+++ b/libs/common/src/abstractions/fido2/fido2.service.abstraction.ts
@@ -54,6 +54,12 @@ export interface CredentialAssertResult {
userHandle: string;
}
+export class RequestAbortedError extends Error {
+ constructor(readonly fallbackRequested = false) {
+ super("Fido2 request was aborted");
+ }
+}
+
export abstract class Fido2Service {
createCredential: (params: CredentialRegistrationParams) => Promise;
assertCredential: (params: CredentialAssertParams) => Promise;