diff --git a/apps/browser/src/popup/fido2/fido2.component.html b/apps/browser/src/popup/fido2/fido2.component.html
index 2534d22ce7b..49bd634abfa 100644
--- a/apps/browser/src/popup/fido2/fido2.component.html
+++ b/apps/browser/src/popup/fido2/fido2.component.html
@@ -1,7 +1,18 @@
+
+ A site is asking for authentication using the following credential:
+
+
+ Authenticate
+
+
- A site is asking for authentication, please choose one of the following credentials to use
+ A site is asking for authentication, please choose one of the following credentials to use:
-
A site wants to create the following passkey in your vault
@@ -23,9 +30,7 @@
-
- Create
-
+ Create
Use browser built-in
diff --git a/apps/browser/src/popup/fido2/fido2.component.ts b/apps/browser/src/popup/fido2/fido2.component.ts
index eb1a0cf9adf..37a0c8a4b66 100644
--- a/apps/browser/src/popup/fido2/fido2.component.ts
+++ b/apps/browser/src/popup/fido2/fido2.component.ts
@@ -37,6 +37,9 @@ export class Fido2Component implements OnInit, OnDestroy {
cipher.type = CipherType.Fido2Key;
cipher.fido2Key = new Fido2KeyView();
this.ciphers = [cipher];
+ } else if (this.data?.type === "ConfirmCredentialRequest") {
+ const cipher = await this.cipherService.get(this.data.cipherId);
+ this.ciphers = [await cipher.decrypt()];
} else if (this.data?.type === "PickCredentialRequest") {
this.ciphers = await Promise.all(
this.data.cipherIds.map(async (cipherId) => {
@@ -62,6 +65,14 @@ export class Fido2Component implements OnInit, OnDestroy {
}
confirm() {
+ BrowserFido2UserInterfaceService.sendMessage({
+ requestId: this.data.requestId,
+ type: "ConfirmCredentialResponse",
+ });
+ window.close();
+ }
+
+ confirmNew() {
BrowserFido2UserInterfaceService.sendMessage({
requestId: this.data.requestId,
type: "ConfirmNewCredentialResponse",
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 b537cd4db5b..118a8625ee2 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
@@ -21,6 +21,13 @@ export type BrowserFido2Message = { requestId: string } & (
type: "PickCredentialResponse";
cipherId?: string;
}
+ | {
+ type: "ConfirmCredentialRequest";
+ cipherId: string;
+ }
+ | {
+ type: "ConfirmCredentialResponse";
+ }
| {
type: "ConfirmNewCredentialRequest";
name: string;
@@ -50,6 +57,35 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
BrowserApi.messageListener(BrowserFido2MessageName, this.processMessage.bind(this));
}
+ async confirmCredential(cipherId: string): Promise {
+ const requestId = Utils.newGuid();
+ const data: BrowserFido2Message = { type: "ConfirmCredentialRequest", cipherId, requestId };
+ const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
+ this.popupUtilsService.popOut(
+ null,
+ `popup/index.html?uilocation=popout#/fido2?${queryParams}`,
+ { center: true }
+ );
+
+ const response = await lastValueFrom(
+ this.messages$.pipe(
+ filter((msg) => msg.requestId === requestId),
+ first(),
+ takeUntil(this.destroy$)
+ )
+ );
+
+ if (response.type === "ConfirmCredentialResponse") {
+ return true;
+ }
+
+ if (response.type === "RequestCancelled") {
+ throw new RequestAbortedError(response.fallbackRequested);
+ }
+
+ return false;
+ }
+
async pickCredential(cipherIds: string[]): Promise {
const requestId = Utils.newGuid();
const data: BrowserFido2Message = { type: "PickCredentialRequest", cipherIds, requestId };
diff --git a/libs/common/src/abstractions/fido2/fido2-user-interface.service.abstraction.ts b/libs/common/src/abstractions/fido2/fido2-user-interface.service.abstraction.ts
index 2097d5b1859..4c354c28952 100644
--- a/libs/common/src/abstractions/fido2/fido2-user-interface.service.abstraction.ts
+++ b/libs/common/src/abstractions/fido2/fido2-user-interface.service.abstraction.ts
@@ -3,6 +3,7 @@ export interface NewCredentialParams {
}
export abstract class Fido2UserInterfaceService {
+ confirmCredential: (cipherId: string) => Promise;
pickCredential: (cipherIds: string[]) => Promise;
confirmNewCredential: (params: NewCredentialParams) => Promise;
}
diff --git a/libs/common/src/services/fido2/fido2.service.ts b/libs/common/src/services/fido2/fido2.service.ts
index f9919875a71..af7510ba0a9 100644
--- a/libs/common/src/services/fido2/fido2.service.ts
+++ b/libs/common/src/services/fido2/fido2.service.ts
@@ -129,7 +129,7 @@ export class Fido2Service implements Fido2ServiceAbstraction {
throw new OriginMismatchError();
}
- await this.fido2UserInterfaceService.pickCredential([credential.credentialId.encoded]);
+ await this.fido2UserInterfaceService.confirmCredential(credential.credentialId.encoded);
} else {
// We're looking for a resident key
const credentials = await this.getCredentialsByRp(params.rpId);
diff --git a/libs/common/src/services/fido2/noop-fido2-user-interface.service.ts b/libs/common/src/services/fido2/noop-fido2-user-interface.service.ts
index 2098886845b..91df72c6757 100644
--- a/libs/common/src/services/fido2/noop-fido2-user-interface.service.ts
+++ b/libs/common/src/services/fido2/noop-fido2-user-interface.service.ts
@@ -2,6 +2,10 @@ import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } fro
import { RequestAbortedError } from "../../abstractions/fido2/fido2.service.abstraction";
export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
+ async confirmCredential(cipherId: string): Promise {
+ return false;
+ }
+
pickCredential(cipherIds: string[]): Promise {
throw new RequestAbortedError();
}