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;