diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 6607550454..c438df932f 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1725,7 +1725,7 @@ "placeholders": { "domain": { "content": "$1", - "example": "googlecom" + "example": "google.com" } } }, @@ -2727,9 +2727,6 @@ "yourPasskeyIsLocked": { "message": "Authentication required to use passkey. Verify your identity to continue." }, - "useBrowserName": { - "message": "Use browser" - }, "multifactorAuthenticationCancelled": { "message": "Multifactor authentication cancelled" }, @@ -2823,5 +2820,23 @@ }, "hostedAt": { "message": "hosted at" + }, + "useDeviceOrHardwareKey": { + "message": "Use your device or hardware key" + }, + "justOnce": { + "message": "Just once" + }, + "alwaysForThisSite": { + "message": "Always for this site" + }, + "domainAddedToExcludedDomains": { + "message": "$DOMAIN$ added to excluded domains.", + "placeholders": { + "domain": { + "content": "$1", + "example": "google.com" + } + } } } diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss index 8c2e69092b..3c651682c1 100644 --- a/apps/browser/src/popup/scss/pages.scss +++ b/apps/browser/src/popup/scss/pages.scss @@ -153,14 +153,6 @@ body.body-full { margin: 15px 0 15px 0; } -.useBrowserlink { - padding: 0 10px 5px 10px; - position: fixed; - bottom: 10px; - left: 0; - right: 0; -} - app-options { .box { margin: 10px 0; @@ -184,6 +176,52 @@ app-vault-attachments { } } +.useBrowserlink { + margin-left: 5px; + margin-top: 20px; + + span { + font-weight: 700; + font-size: $font-size-small; + } +} + +.fido2-browser-selector-dropdown { + @include themify($themes) { + background-color: themed("boxBackgroundColor"); + } + padding: 8px; + width: 100%; + box-shadow: + 0 2px 2px 0 rgba(0, 0, 0, 0.14), + 0 3px 1px -2px rgba(0, 0, 0, 0.12), + 0 1px 5px 0 rgba(0, 0, 0, 0.2); + border-radius: $border-radius; +} + +.fido2-browser-selector-dropdown-item { + @include themify($themes) { + color: themed("textColor") !important; + } + width: 100%; + text-align: left; + padding: 0px 15px 0px 5px; + margin-bottom: 5px; + border-radius: 3px; + border: 1px solid transparent; + transition: all 0.2s ease-in-out; + + &:hover { + @include themify($themes) { + background-color: themed("listItemBackgroundHoverColor") !important; + } + } + + &:last-child { + margin-bottom: 0; + } +} + app-fido2 { .auth-wrapper { display: flex; @@ -266,7 +304,6 @@ app-fido2 { align-items: flex-start; flex-direction: column; margin-top: 32px; - margin-bottom: 32px; .subtitle { font-family: Open Sans; diff --git a/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts b/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts index c22c043fb9..a45b0153a3 100644 --- a/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts @@ -46,6 +46,7 @@ export function fido2PopoutSessionData$() { sessionId: queryParams.sessionId as string, fallbackSupported: queryParams.fallbackSupported === "true", userVerification: queryParams.userVerification === "true", + senderUrl: queryParams.senderUrl as string, })), ); } diff --git a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.html b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.html index 3e71675aa2..9f6c0aca50 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.html +++ b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.html @@ -1,5 +1,52 @@ - + + + + +
+ +
+
+ +
+
diff --git a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts index aaebb225b8..7af90125db 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts @@ -1,6 +1,13 @@ +import { animate, state, style, transition, trigger } from "@angular/animations"; +import { ConnectedPosition } from "@angular/cdk/overlay"; import { Component } from "@angular/core"; import { firstValueFrom } from "rxjs"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + import { BrowserFido2UserInterfaceSession, fido2PopoutSessionData$, @@ -9,13 +16,99 @@ import { @Component({ selector: "app-fido2-use-browser-link", templateUrl: "fido2-use-browser-link.component.html", + animations: [ + trigger("transformPanel", [ + state( + "void", + style({ + opacity: 0, + }), + ), + transition( + "void => open", + animate( + "100ms linear", + style({ + opacity: 1, + }), + ), + ), + transition("* => void", animate("100ms linear", style({ opacity: 0 }))), + ]), + ], }) export class Fido2UseBrowserLinkComponent { + showOverlay: boolean = false; + isOpen = false; + overlayPosition: ConnectedPosition[] = [ + { + originX: "start", + originY: "bottom", + overlayX: "start", + overlayY: "top", + offsetY: 5, + }, + ]; + protected fido2PopoutSessionData$ = fido2PopoutSessionData$(); - protected async abort() { + constructor( + private stateService: StateService, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + ) {} + + toggle() { + this.isOpen = !this.isOpen; + } + + close() { + this.isOpen = false; + } + + /** + * Aborts the current FIDO2 session and fallsback to the browser. + * @param excludeDomain - Identifies if the domain should be excluded from future FIDO2 prompts. + */ + protected async abort(excludeDomain = true) { + this.close(); const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - BrowserFido2UserInterfaceSession.abortPopout(sessionData.sessionId, true); - return; + + if (!excludeDomain) { + this.abortSession(sessionData.sessionId); + return; + } + // Show overlay to prevent the user from interacting with the page. + this.showOverlay = true; + await this.handleDomainExclusion(sessionData.senderUrl); + // Give the user a chance to see the toast before closing the popout. + await Utils.delay(2000); + this.abortSession(sessionData.sessionId); + } + + /** + * Excludes the domain from future FIDO2 prompts. + * @param uri - The domain uri to exclude from future FIDO2 prompts. + */ + private async handleDomainExclusion(uri: string) { + const exisitingDomains = await this.stateService.getNeverDomains(); + + const validDomain = Utils.getHostname(uri); + const savedDomains: { [name: string]: unknown } = { + ...exisitingDomains, + }; + savedDomains[validDomain] = null; + + this.stateService.setNeverDomains(savedDomains); + + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("domainAddedToExcludedDomains", validDomain), + ); + } + + private abortSession(sessionId: string) { + BrowserFido2UserInterfaceSession.abortPopout(sessionId, true); } } diff --git a/apps/browser/src/vault/popup/components/fido2/fido2.component.html b/apps/browser/src/vault/popup/components/fido2/fido2.component.html index fa49192c72..9036d6d991 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2.component.html +++ b/apps/browser/src/vault/popup/components/fido2/fido2.component.html @@ -130,10 +130,7 @@ - + + diff --git a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts index 9311b6f18a..a0cc808a6f 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts @@ -49,8 +49,6 @@ interface ViewData { export class Fido2Component implements OnInit, OnDestroy { private destroy$ = new Subject(); private hasSearched = false; - private searchTimeout: any = null; - private hasLoadedAllCiphers = false; protected cipher: CipherView; protected searchTypeSearch = false; diff --git a/apps/browser/src/vault/popup/utils/vault-popout-window.spec.ts b/apps/browser/src/vault/popup/utils/vault-popout-window.spec.ts index 84b6101c1c..ee0918eab0 100644 --- a/apps/browser/src/vault/popup/utils/vault-popout-window.spec.ts +++ b/apps/browser/src/vault/popup/utils/vault-popout-window.spec.ts @@ -161,7 +161,7 @@ describe("VaultPopoutWindow", () => { singleActionKey: `${VaultPopoutType.fido2Popout}_sessionId`, senderWindowId: 1, forceCloseExistingWindows: true, - windowOptions: { height: 450 }, + windowOptions: { height: 570 }, }, ); expect(returnedWindowId).toEqual(10); diff --git a/apps/browser/src/vault/popup/utils/vault-popout-window.ts b/apps/browser/src/vault/popup/utils/vault-popout-window.ts index 00cb4a71b8..36fddadef7 100644 --- a/apps/browser/src/vault/popup/utils/vault-popout-window.ts +++ b/apps/browser/src/vault/popup/utils/vault-popout-window.ts @@ -154,7 +154,7 @@ async function openFido2Popout( singleActionKey: `${VaultPopoutType.fido2Popout}_${sessionId}`, senderWindowId: senderTab.windowId, forceCloseExistingWindows: true, - windowOptions: { height: 450 }, + windowOptions: { height: 570 }, }); return popoutWindow.id;