diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 9a69d5f108..0217e3f633 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3928,11 +3928,11 @@ }, "passkeys": { "message": "Passkeys", - "description": "A section header for a list of passkeys. Used in the inline menu list." + "description": "A section header for a list of passkeys." }, "passwords": { "message": "Passwords", - "description": "A section header for a list of passwords. Used in the inline menu list." + "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { "message": "Log in with passkey", diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index e29cc8331a..2ca0920a38 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -13,6 +13,7 @@ import { DomainSettingsService, } from "@bitwarden/common/autofill/services/domain-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { EnvironmentService, Region, @@ -75,6 +76,7 @@ describe("OverlayBackground", () => { let accountService: FakeAccountService; let fakeStateProvider: FakeStateProvider; let showFaviconsMock$: BehaviorSubject; + let neverDomainsMock$: BehaviorSubject; let domainSettingsService: DomainSettingsService; let logService: MockProxy; let cipherService: MockProxy; @@ -133,8 +135,10 @@ describe("OverlayBackground", () => { accountService = mockAccountServiceWith(mockUserId); fakeStateProvider = new FakeStateProvider(accountService); showFaviconsMock$ = new BehaviorSubject(true); + neverDomainsMock$ = new BehaviorSubject({}); domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); domainSettingsService.showFavicons$ = showFaviconsMock$; + domainSettingsService.neverDomains$ = neverDomainsMock$; logService = mock(); cipherService = mock(); autofillService = mock(); @@ -754,6 +758,7 @@ describe("OverlayBackground", () => { credentialId: "credential-id", rpName: "credential-name", userName: "credential-username", + rpId: "jest-testing-website.com", }), ], }, @@ -1183,6 +1188,64 @@ describe("OverlayBackground", () => { showPasskeysLabels: true, }); }); + + it("does not add a passkey to the inline menu when its rpId is part of the neverDomains exclusion list", async () => { + availableAutofillCredentialsMock$.next(passkeyCipher.login.fido2Credentials); + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + tabId: tab.id, + filledByCipherType: CipherType.Login, + }); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, passkeyCipher]); + cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); + getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); + neverDomainsMock$.next({ "jest-testing-website.com": null }); + + await overlayBackground.updateOverlayCiphers(); + + expect(listPortSpy.postMessage).toHaveBeenCalledWith({ + command: "updateAutofillInlineMenuListCiphers", + ciphers: [ + { + id: "inline-menu-cipher-0", + name: passkeyCipher.name, + type: CipherType.Login, + reprompt: passkeyCipher.reprompt, + favorite: passkeyCipher.favorite, + icon: { + fallbackImage: "images/bwi-globe.png", + icon: "bwi-globe", + image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", + imageEnabled: true, + }, + accountCreationFieldType: undefined, + login: { + username: passkeyCipher.login.username, + passkey: null, + }, + }, + { + id: "inline-menu-cipher-1", + name: loginCipher1.name, + type: CipherType.Login, + reprompt: loginCipher1.reprompt, + favorite: loginCipher1.favorite, + icon: { + fallbackImage: "images/bwi-globe.png", + icon: "bwi-globe", + image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", + imageEnabled: true, + }, + accountCreationFieldType: undefined, + login: { + username: loginCipher1.login.username, + passkey: null, + }, + }, + ], + showInlineMenuAccountCreation: false, + showPasskeysLabels: false, + }); + }); }); describe("extension message handlers", () => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 3bb80b09b2..2cef0b0e78 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -7,6 +7,7 @@ import { switchMap, debounceTime, } from "rxjs"; +import { parse } from "tldts"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -17,6 +18,7 @@ import { import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { Fido2ClientService } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -364,7 +366,10 @@ export class OverlayBackground implements OverlayBackgroundInterface { true, ); } else { - inlineMenuCipherData = this.buildInlineMenuCiphers(inlineMenuCiphersArray, showFavicons); + inlineMenuCipherData = await this.buildInlineMenuCiphers( + inlineMenuCiphersArray, + showFavicons, + ); } this.currentInlineMenuCiphersCount = inlineMenuCipherData.length; @@ -432,12 +437,17 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param inlineMenuCiphersArray - Array of inline menu ciphers * @param showFavicons - Identifies whether favicons should be shown */ - private buildInlineMenuCiphers( + private async buildInlineMenuCiphers( inlineMenuCiphersArray: [string, CipherView][], showFavicons: boolean, ) { const inlineMenuCipherData: InlineMenuCipherData[] = []; const passkeyCipherData: InlineMenuCipherData[] = []; + const domainExclusions = await this.getExcludedDomains(); + let domainExclusionsSet: Set | null = null; + if (domainExclusions) { + domainExclusionsSet = new Set(Object.keys(await this.getExcludedDomains())); + } for (let cipherIndex = 0; cipherIndex < inlineMenuCiphersArray.length; cipherIndex++) { const [inlineMenuCipherId, cipher] = inlineMenuCiphersArray[cipherIndex]; @@ -445,7 +455,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { continue; } - if (this.showCipherAsPasskey(cipher)) { + if (this.showCipherAsPasskey(cipher, domainExclusionsSet)) { passkeyCipherData.push( this.buildCipherData({ inlineMenuCipherId, @@ -472,13 +482,28 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Identifies whether we should show the cipher as a passkey in the inline menu list. * * @param cipher - The cipher to check + * @param domainExclusions - The domain exclusions to check against */ - private showCipherAsPasskey(cipher: CipherView): boolean { + private showCipherAsPasskey(cipher: CipherView, domainExclusions: Set | null): boolean { + if (cipher.type !== CipherType.Login) { + return false; + } + + const fido2Credentials = cipher.login.fido2Credentials; + if (!fido2Credentials?.length) { + return false; + } + + const credentialId = fido2Credentials[0].credentialId; + const rpId = fido2Credentials[0].rpId; + const parsedRpId = parse(rpId, { allowPrivateDomains: true }); + if (domainExclusions?.has(parsedRpId.domain)) { + return false; + } + return ( - cipher.type === CipherType.Login && - cipher.login.fido2Credentials?.length > 0 && - (this.inlineMenuFido2Credentials.size === 0 || - this.inlineMenuFido2Credentials.has(cipher.login.fido2Credentials[0].credentialId)) + this.inlineMenuFido2Credentials.size === 0 || + this.inlineMenuFido2Credentials.has(credentialId) ); } @@ -597,9 +622,17 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param credentials - The FIDO2 credentials to store */ private storeInlineMenuFido2Credentials(credentials: Fido2CredentialView[]) { - credentials - .map((credential) => credential.credentialId) - .forEach((credentialId) => this.inlineMenuFido2Credentials.add(credentialId)); + credentials.forEach( + (credential) => + credential?.credentialId && this.inlineMenuFido2Credentials.add(credential.credentialId), + ); + } + + /** + * Gets the neverDomains setting from the domain settings service. + */ + async getExcludedDomains(): Promise { + return await firstValueFrom(this.domainSettingsService.neverDomains$); } /** diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index 7275ced37b..4631b78ddb 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -1,4 +1,4 @@ -import { WebauthnUtils } from "../../../vault/fido2/webauthn-utils"; +import { WebauthnUtils } from "../utils/webauthn-utils"; import { MessageType } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index 21f5a1d701..31e8c941e8 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -5,7 +5,7 @@ import { createCredentialRequestOptionsMock, setupMockedWebAuthnSupport, } from "../../../autofill/spec/fido2-testing-utils"; -import { WebauthnUtils } from "../../../vault/fido2/webauthn-utils"; +import { WebauthnUtils } from "../utils/webauthn-utils"; import { MessageType } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; @@ -41,7 +41,7 @@ jest.mock("./messaging/messenger", () => { }, }; }); -jest.mock("../../../vault/fido2/webauthn-utils"); +jest.mock("../utils/webauthn-utils"); describe("Fido2 page script with native WebAuthn support", () => { (jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation( diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts index a1e7006b04..e354453ca5 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts @@ -4,7 +4,7 @@ import { createCredentialCreationOptionsMock, createCredentialRequestOptionsMock, } from "../../../autofill/spec/fido2-testing-utils"; -import { WebauthnUtils } from "../../../vault/fido2/webauthn-utils"; +import { WebauthnUtils } from "../utils/webauthn-utils"; import { MessageType } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; @@ -39,7 +39,7 @@ jest.mock("./messaging/messenger", () => { }, }; }); -jest.mock("../../../vault/fido2/webauthn-utils"); +jest.mock("../utils/webauthn-utils"); describe("Fido2 page script without native WebAuthn support", () => { (jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation( diff --git a/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts similarity index 98% rename from apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts rename to apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts index c618c3dd14..f373494d52 100644 --- a/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts @@ -25,8 +25,8 @@ import { } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { BrowserApi } from "../../platform/browser/browser-api"; -import { closeFido2Popout, openFido2Popout } from "../popup/utils/vault-popout-window"; +import { BrowserApi } from "../../../platform/browser/browser-api"; +import { closeFido2Popout, openFido2Popout } from "../../../vault/popup/utils/vault-popout-window"; const BrowserFido2MessageName = "BrowserFido2UserInterfaceServiceMessage"; diff --git a/apps/browser/src/vault/fido2/webauthn-utils.ts b/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts similarity index 98% rename from apps/browser/src/vault/fido2/webauthn-utils.ts rename to apps/browser/src/autofill/fido2/utils/webauthn-utils.ts index b880b3c790..71ed5dc000 100644 --- a/apps/browser/src/vault/fido2/webauthn-utils.ts +++ b/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts @@ -7,7 +7,7 @@ import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-util import { InsecureAssertCredentialParams, InsecureCreateCredentialParams, -} from "../../autofill/fido2/content/messaging/message"; +} from "../content/messaging/message"; export class WebauthnUtils { static mapCredentialCreationOptions( diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts index b97c4102fe..d9a7c7c9cb 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts @@ -9,8 +9,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { BrowserFido2UserInterfaceSession } from "../../../vault/fido2/browser-fido2-user-interface.service"; import { fido2PopoutSessionData$ } from "../../../vault/popup/utils/fido2-popout-session-data"; +import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-fido2-user-interface.service"; @Component({ selector: "app-fido2-use-browser-link", diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index d720a5240f..43e8ce6809 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -30,12 +30,12 @@ import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; import { ZonedMessageListenerService } from "../../../platform/browser/zoned-message-listener.service"; +import { VaultPopoutType } from "../../../vault/popup/utils/vault-popout-window"; +import { Fido2UserVerificationService } from "../../../vault/services/fido2-user-verification.service"; import { BrowserFido2Message, BrowserFido2UserInterfaceSession, -} from "../../../vault/fido2/browser-fido2-user-interface.service"; -import { VaultPopoutType } from "../../../vault/popup/utils/vault-popout-window"; -import { Fido2UserVerificationService } from "../../../vault/services/fido2-user-verification.service"; +} from "../../fido2/services/browser-fido2-user-interface.service"; interface ViewData { message: BrowserFido2Message; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 27007e2021..6faa038956 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -213,6 +213,7 @@ import { MainContextMenuHandler } from "../autofill/browser/main-context-menu-ha import LegacyOverlayBackground from "../autofill/deprecated/background/overlay.background.deprecated"; import { Fido2Background as Fido2BackgroundAbstraction } from "../autofill/fido2/background/abstractions/fido2.background"; import { Fido2Background } from "../autofill/fido2/background/fido2.background"; +import { BrowserFido2UserInterfaceService } from "../autofill/fido2/services/browser-fido2-user-interface.service"; import { AutofillService as AutofillServiceAbstraction } from "../autofill/services/abstractions/autofill.service"; import AutofillService from "../autofill/services/autofill.service"; import { SafariApp } from "../browser/safariApp"; @@ -243,7 +244,6 @@ import { SyncServiceListener } from "../platform/sync/sync-service.listener"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; import FilelessImporterBackground from "../tools/background/fileless-importer.background"; -import { BrowserFido2UserInterfaceService } from "../vault/fido2/browser-fido2-user-interface.service"; import { VaultFilterService } from "../vault/services/vault-filter.service"; import CommandsBackground from "./commands.background"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index 9d42d6b604..938cc7d8e7 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -21,13 +21,13 @@ import { TotpCaptureService, } from "@bitwarden/vault"; +import { BrowserFido2UserInterfaceSession } from "../../../../../autofill/fido2/services/browser-fido2-user-interface.service"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; import { PopupCloseWarningService } from "../../../../../popup/services/popup-close-warning.service"; -import { BrowserFido2UserInterfaceSession } from "../../../../fido2/browser-fido2-user-interface.service"; import { BrowserCipherFormGenerationService } from "../../../services/browser-cipher-form-generation.service"; import { BrowserTotpCaptureService } from "../../../services/browser-totp-capture.service"; import { diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts index 2bc74f7992..1a944d5599 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -26,10 +26,10 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; +import { BrowserFido2UserInterfaceSession } from "../../../../autofill/fido2/services/browser-fido2-user-interface.service"; import { BrowserApi } from "../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; import { PopupCloseWarningService } from "../../../../popup/services/popup-close-warning.service"; -import { BrowserFido2UserInterfaceSession } from "../../../fido2/browser-fido2-user-interface.service"; import { Fido2UserVerificationService } from "../../../services/fido2-user-verification.service"; import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data"; import { closeAddEditVaultItemPopout, VaultPopoutType } from "../../utils/vault-popout-window"; diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts index e48c0adc0c..f3d95d3d20 100644 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ b/apps/browser/src/vault/popup/components/vault/view.component.ts @@ -27,10 +27,10 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; +import { BrowserFido2UserInterfaceSession } from "../../../../autofill/fido2/services/browser-fido2-user-interface.service"; import { AutofillService } from "../../../../autofill/services/abstractions/autofill.service"; import { BrowserApi } from "../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { BrowserFido2UserInterfaceSession } from "../../../fido2/browser-fido2-user-interface.service"; import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data"; import { closeViewVaultItemPopout, VaultPopoutType } from "../../utils/vault-popout-window"; diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index e82a2e32c9..70907f9865 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -234,15 +234,14 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); } - let response; + let response = { cipherId: cipherOptions[0].id, userVerified: false }; if (this.requiresUserVerificationPrompt(params, cipherOptions)) { response = await userInterfaceSession.pickCredential({ cipherIds: cipherOptions.map((cipher) => cipher.id), userVerification: params.requireUserVerification, }); - } else { - response = { cipherId: cipherOptions[0].id, userVerified: false }; } + const selectedCipherId = response.cipherId; const userVerified = response.userVerified; const selectedCipher = cipherOptions.find((c) => c.id === selectedCipherId);