[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:
parent
8036113e46
commit
c1d856430a
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -161,7 +161,7 @@ describe("VaultPopoutWindow", () => {
|
|||
singleActionKey: `${VaultPopoutType.fido2Popout}_sessionId`,
|
||||
senderWindowId: 1,
|
||||
forceCloseExistingWindows: true,
|
||||
windowOptions: { height: 450 },
|
||||
windowOptions: { height: 570 },
|
||||
},
|
||||
);
|
||||
expect(returnedWindowId).toEqual(10);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue