[PM-4650] Provide user interaction for adding domain to excluded domains for passkeys (#7041)

* Added new locales text

* expose the sender url to be used in the use browser link component

* Modified use browser link to have a dropdown of two options, just once or always for this site

* modified component to use the use browser link component

* refactored method

* Made style changes and also updated the windows popout height

* ran prettier

* corrected google domain

* [PM-5281] [PM-5282] Disable User Interaction Post 'Always for this Site' Selection and Preserve Prior Exclusions (#7237)

* Added new domain alongside existing domains when saving to state

* Added an overlay whne user clicks always for this site to prevent further interaction on the page

* changed opacity

* moved overlay to fido2-use-browser-link

* removed private method and renamed variable
This commit is contained in:
SmithThe4th 2023-12-20 10:38:35 -05:00 committed by GitHub
parent 8036113e46
commit c1d856430a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 218 additions and 30 deletions

View File

@ -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"
}
}
}
}

View File

@ -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;

View File

@ -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,
})),
);
}

View File

@ -1,5 +1,52 @@
<div class="useBrowserlink" *ngIf="(fido2PopoutSessionData$ | async).fallbackSupported">
<button appStopClick type="button" (click)="abort()">
{{ "useBrowserName" | i18n }}
</button>
</div>
<ng-container *ngIf="(fido2PopoutSessionData$ | async).fallbackSupported">
<div class="useBrowserlink">
<button
type="button"
(click)="toggle()"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
aria-haspopup="dialog"
aria-controls="cdk-overlay-container"
>
<span class="text-primary">
{{ "useDeviceOrHardwareKey" | i18n }}
</span>
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
</button>
</div>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
[cdkConnectedOverlayPositions]="overlayPosition"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
(backdropClick)="isOpen = false"
(detach)="close()"
>
<div class="box-content">
<div
class="fido2-browser-selector-dropdown"
[@transformPanel]="'open'"
cdkTrapFocus
cdkTrapFocusAutoCapture
role="dialog"
aria-modal="true"
>
<button type="button" class="fido2-browser-selector-dropdown-item" (click)="abort(false)">
<span>{{ "justOnce" | i18n }}</span>
</button>
<br />
<button type="button" class="fido2-browser-selector-dropdown-item" (click)="abort()">
<span>{{ "alwaysForThisSite" | i18n }}</span>
</button>
</div>
</div>
</ng-template>
<div
*ngIf="showOverlay"
class="tw-absolute tw-w-full tw-h-full tw-bg-background-alt tw-inset-0 tw-bg-opacity-80 tw-z-50"
></div>
</ng-container>

View File

@ -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);
}
}

View File

@ -130,10 +130,7 @@
</button>
</ng-container>
</ng-container>
<div class="useBrowserlink">
<button *ngIf="data.fallbackSupported" appStopClick type="button" (click)="abort(true)">
{{ "useBrowserName" | i18n }}
</button>
</div>
<app-fido2-use-browser-link></app-fido2-use-browser-link>
</div>
</ng-container>

View File

@ -49,8 +49,6 @@ interface ViewData {
export class Fido2Component implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
private hasSearched = false;
private searchTimeout: any = null;
private hasLoadedAllCiphers = false;
protected cipher: CipherView;
protected searchTypeSearch = false;

View File

@ -161,7 +161,7 @@ describe("VaultPopoutWindow", () => {
singleActionKey: `${VaultPopoutType.fido2Popout}_sessionId`,
senderWindowId: 1,
forceCloseExistingWindows: true,
windowOptions: { height: 450 },
windowOptions: { height: 570 },
},
);
expect(returnedWindowId).toEqual(10);

View File

@ -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;