diff --git a/libs/common/src/webauthn/services/fido2-client.service.spec.ts b/libs/common/src/webauthn/services/fido2-client.service.spec.ts index 56d1808af97..1fb615d1e15 100644 --- a/libs/common/src/webauthn/services/fido2-client.service.spec.ts +++ b/libs/common/src/webauthn/services/fido2-client.service.spec.ts @@ -18,18 +18,18 @@ describe("FidoAuthenticatorService", () => { describe("createCredential", () => { describe("invalid input parameters", () => { - /** Spec: If sameOriginWithAncestors is false, return a "NotAllowedError" DOMException. */ + // Spec: If sameOriginWithAncestors is false, return a "NotAllowedError" DOMException. it("should throw error if sameOriginWithAncestors is false", async () => { const params = createParams({ sameOriginWithAncestors: false }); const result = async () => await client.createCredential(params); - const rejects = await expect(result).rejects; - rejects.toMatchObject({ name: "NotAllowedError" }); - rejects.toBeInstanceOf(DOMException); + const rejects = expect(result).rejects; + await rejects.toMatchObject({ name: "NotAllowedError" }); + await rejects.toBeInstanceOf(DOMException); }); - /** Spec: If the length of options.user.id is not between 1 and 64 bytes (inclusive) then return a TypeError. */ + // Spec: If the length of options.user.id is not between 1 and 64 bytes (inclusive) then return a TypeError. it("should throw error if user.id is too small", async () => { const params = createParams({ user: { id: "", displayName: "name" } }); @@ -38,7 +38,7 @@ describe("FidoAuthenticatorService", () => { await expect(result).rejects.toBeInstanceOf(TypeError); }); - /** Spec: If the length of options.user.id is not between 1 and 64 bytes (inclusive) then return a TypeError. */ + // Spec: If the length of options.user.id is not between 1 and 64 bytes (inclusive) then return a TypeError. it("should throw error if user.id is too large", async () => { const params = createParams({ user: { @@ -51,6 +51,37 @@ describe("FidoAuthenticatorService", () => { await expect(result).rejects.toBeInstanceOf(TypeError); }); + + // Spec: If callerOrigin is an opaque origin, return a DOMException whose name is "NotAllowedError", and terminate this algorithm. + // Not sure how to check this, or if it matters. + it.todo("should throw error if origin is an opaque origin"); + + // Spec: Let effectiveDomain be the callerOrigin’s effective domain. If effective domain is not a valid domain, then return a DOMException whose name is "SecurityError" and terminate this algorithm. + it("should throw error if origin is not a valid domain name", async () => { + const params = createParams({ + origin: "invalid-domain-name", + }); + + const result = async () => await client.createCredential(params); + + const rejects = expect(result).rejects; + await rejects.toMatchObject({ name: "SecurityError" }); + await rejects.toBeInstanceOf(DOMException); + }); + + // Spec: If options.rp.id is not a registrable domain suffix of and is not equal to effectiveDomain, return a DOMException whose name is "SecurityError", and terminate this algorithm. + it("should throw error if rp.id does not match origin effective domain", async () => { + const params = createParams({ + origin: "passwordless.dev", + rp: { id: "bitwarden.com", name: "Bitwarden" }, + }); + + const result = async () => await client.createCredential(params); + + const rejects = expect(result).rejects; + await rejects.toMatchObject({ name: "SecurityError" }); + await rejects.toBeInstanceOf(DOMException); + }); }); function createParams(params: Partial = {}): CreateCredentialParams { diff --git a/libs/common/src/webauthn/services/fido2-client.service.ts b/libs/common/src/webauthn/services/fido2-client.service.ts index d633faa2405..3e898bf422f 100644 --- a/libs/common/src/webauthn/services/fido2-client.service.ts +++ b/libs/common/src/webauthn/services/fido2-client.service.ts @@ -1,3 +1,5 @@ +import { parse } from "tldts"; + import { Fido2AuthenticatorService } from "../abstractions/fido2-authenticator.service.abstraction"; import { AssertCredentialParams, @@ -24,6 +26,16 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { throw new TypeError("Invalid 'user.id' length"); } + const { domain: effectiveDomain } = parse(params.origin, { allowPrivateDomains: true }); + if (effectiveDomain == undefined) { + throw new DOMException("'origin' is not a valid domain", "SecurityError"); + } + + const rpId = params.rp.id ?? effectiveDomain; + if (effectiveDomain !== rpId) { + throw new DOMException("'rp.id' does not match origin effective domain", "SecurityError"); + } + throw new Error("Not implemented"); }