diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts index 08f8abb1549..b81d4aa5ff8 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts @@ -85,6 +85,7 @@ describe("FidoAuthenticatorService", () => { configService.serverConfig$ = of({ environment: { vault: VaultUrl } } as any); vaultSettingsService.enablePasskeys$ = of(true); domainSettingsService.neverDomains$ = of({}); + domainSettingsService.blockedInteractionsUris$ = of({}); authService.activeAccountStatus$ = of(AuthenticationStatus.Unlocked); windowReference = Utils.newGuid(); }); @@ -710,6 +711,52 @@ describe("FidoAuthenticatorService", () => { }; } }); + + describe("isFido2FeatureEnabled", () => { + const hostname = "sub.example.com"; + const origin = "https://sub.example.com"; + + it("returns false when the hostname exactly matches a `blockedInteractionsUris` entry", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ "sub.example.com": null }); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(false); + }); + + it("returns true when the hostname is a subdomain of a `blockedInteractionsUris` entry", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ "example.com": null }); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(true); + }); + + it("returns true when `blockedInteractionsUris` is empty", async () => { + domainSettingsService.blockedInteractionsUris$ = of({}); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(true); + }); + + it("returns true when no `blockedInteractionsUris` entry matches the hostname", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ "bitwarden.com": null }); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(true); + }); + + it("rejects via `blockedInteractionsUris` regardless of `neverDomains` state", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ "sub.example.com": null }); + domainSettingsService.neverDomains$ = of({}); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(false); + }); + }); }); /** This is a fake function that always returns the same byte sequence */ diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.ts b/libs/common/src/platform/services/fido2/fido2-client.service.ts index b80023b8bc2..8d724134261 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.ts @@ -90,6 +90,14 @@ export class Fido2ClientService< return false; } + const blockedInteractionsUris = await firstValueFrom( + this.domainSettingsService.blockedInteractionsUris$, + ); + const isBlockedDomain = blockedInteractionsUris != null && hostname in blockedInteractionsUris; + if (isBlockedDomain) { + return false; + } + const neverDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); const isExcludedDomain = neverDomains != null && hostname in neverDomains;