diff --git a/apps/browser/src/vault/fido2/content/page-script.ts b/apps/browser/src/vault/fido2/content/page-script.ts index 42d695b235b..1194e0a4e4c 100644 --- a/apps/browser/src/vault/fido2/content/page-script.ts +++ b/apps/browser/src/vault/fido2/content/page-script.ts @@ -54,6 +54,14 @@ const browserCredentials = { const messenger = Messenger.forDOMCommunication(window); +function isSameOriginWithAncestors() { + try { + return window.self === window.top; + } catch { + return false; + } +} + navigator.credentials.create = async ( options?: CredentialCreationOptions, abortController?: AbortController @@ -64,14 +72,15 @@ navigator.credentials.create = async ( (options?.publicKey?.authenticatorSelection.authenticatorAttachment !== "platform" && browserNativeWebauthnSupport); try { + const isNotIframe = isSameOriginWithAncestors(); + const response = await messenger.request( { type: MessageType.CredentialCreationRequest, - // TODO: Fix sameOriginWithAncestors! data: WebauthnUtils.mapCredentialCreationOptions( options, window.location.origin, - true, + isNotIframe, fallbackSupported ), }, @@ -99,6 +108,8 @@ navigator.credentials.get = async ( const fallbackSupported = browserNativeWebauthnSupport; try { + const isNotIframe = isSameOriginWithAncestors(); + if (options?.mediation && options.mediation !== "optional") { throw new FallbackRequestedError(); } @@ -106,11 +117,10 @@ navigator.credentials.get = async ( const response = await messenger.request( { type: MessageType.CredentialGetRequest, - // TODO: Fix sameOriginWithAncestors! data: WebauthnUtils.mapCredentialRequestOptions( options, window.location.origin, - true, + isNotIframe, fallbackSupported ), }, diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts index 8f8141df9c7..a19afa6aaa1 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts @@ -342,6 +342,18 @@ describe("FidoAuthenticatorService", () => { const rejects = expect(result).rejects; await rejects.toThrow(FallbackRequestedError); }); + + // Spec: If sameOriginWithAncestors is false, return a "NotAllowedError" DOMException. + it("should throw error if sameOriginWithAncestors is false", async () => { + const params = createParams(); + params.sameOriginWithAncestors = false; // Simulating the falsey value + + const result = async () => await client.assertCredential(params); + + const rejects = expect(result).rejects; + await rejects.toMatchObject({ name: "NotAllowedError" }); + await rejects.toBeInstanceOf(DOMException); + }); }); describe("assert non-discoverable credential", () => { diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.ts b/libs/common/src/vault/services/fido2/fido2-client.service.ts index 4733010944a..55a62949465 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.ts @@ -200,6 +200,13 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { throw new FallbackRequestedError(); } + if (!params.sameOriginWithAncestors) { + this.logService?.warning( + `[Fido2Client] Invalid 'sameOriginWithAncestors' value: ${params.sameOriginWithAncestors}` + ); + throw new DOMException("Invalid 'sameOriginWithAncestors' value", "NotAllowedError"); + } + const { domain: effectiveDomain } = parse(params.origin, { allowPrivateDomains: true }); if (effectiveDomain == undefined) { this.logService?.warning(`[Fido2Client] Invalid origin: ${params.origin}`);