mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-27 22:11:38 +01:00
[PM-16470] Remove v1 autofill owned refresh code (#12579)
* Remove v1 autofill owned settings * Remove Fido2 v1 components * Remove conditional to enable AutofillOnPageLoadOrgPolicy --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
parent
fe9b0976ee
commit
22f4822efc
@ -1,36 +0,0 @@
|
||||
<div
|
||||
role="group"
|
||||
appA11yTitle="{{ cipher.name }} {{ cipher.subTitle }}"
|
||||
class="virtual-scroll-item"
|
||||
[ngClass]="{ 'override-last': !last }"
|
||||
>
|
||||
<div class="box-content-row box-content-row-flex">
|
||||
<button
|
||||
type="button"
|
||||
(click)="selectCipher(cipher)"
|
||||
tabindex="0"
|
||||
appStopClick
|
||||
title="{{ title }} - {{ cipher.name }}"
|
||||
[ngClass]="{ 'row-main': true, 'row-selected': isSelected && !isSearching }"
|
||||
>
|
||||
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
||||
<div class="row-main-content">
|
||||
<span class="text">
|
||||
<span class="truncate-box">
|
||||
<span class="truncate">{{ cipher.name }}</span>
|
||||
<ng-container *ngIf="cipher.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection text-muted"
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
</span>
|
||||
</span>
|
||||
<span class="detail" *ngIf="getSubName(cipher)">{{ getSubName(cipher) }}</span>
|
||||
<span class="detail" *ngIf="cipher.subTitle">{{ cipher.subTitle }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
@ -1,41 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from "@angular/core";
|
||||
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
@Component({
|
||||
selector: "app-fido2-cipher-row-v1",
|
||||
templateUrl: "fido2-cipher-row-v1.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class Fido2CipherRowV1Component {
|
||||
@Output() onSelected = new EventEmitter<CipherView>();
|
||||
@Input() cipher: CipherView;
|
||||
@Input() last: boolean;
|
||||
@Input() title: string;
|
||||
@Input() isSearching: boolean;
|
||||
@Input() isSelected: boolean;
|
||||
|
||||
protected selectCipher(c: CipherView) {
|
||||
this.onSelected.emit(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a subname for the cipher.
|
||||
* If this has a FIDO2 credential, and the cipher.name is different from the FIDO2 credential's rpId, return the rpId.
|
||||
* @param c Cipher
|
||||
* @returns
|
||||
*/
|
||||
protected getSubName(c: CipherView): string | null {
|
||||
const fido2Credentials = c.login?.fido2Credentials;
|
||||
|
||||
if (!fido2Credentials || fido2Credentials.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [fido2Credential] = fido2Credentials;
|
||||
|
||||
return c.name !== fido2Credential.rpId ? fido2Credential.rpId : null;
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
<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,115 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
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 { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
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-v1",
|
||||
templateUrl: "fido2-use-browser-link-v1.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 Fido2UseBrowserLinkV1Component {
|
||||
showOverlay = false;
|
||||
isOpen = false;
|
||||
overlayPosition: ConnectedPosition[] = [
|
||||
{
|
||||
originX: "start",
|
||||
originY: "bottom",
|
||||
overlayX: "start",
|
||||
overlayY: "top",
|
||||
offsetY: 5,
|
||||
},
|
||||
];
|
||||
|
||||
protected fido2PopoutSessionData$ = fido2PopoutSessionData$();
|
||||
|
||||
constructor(
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
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$);
|
||||
|
||||
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 existingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$);
|
||||
|
||||
const validDomain = Utils.getHostname(uri);
|
||||
const savedDomains: NeverDomains = {
|
||||
...existingDomains,
|
||||
};
|
||||
savedDomains[validDomain] = null;
|
||||
|
||||
await this.domainSettingsService.setNeverDomains(savedDomains);
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("domainAddedToExcludedDomains", validDomain),
|
||||
);
|
||||
}
|
||||
|
||||
private abortSession(sessionId: string) {
|
||||
BrowserFido2UserInterfaceSession.abortPopout(sessionId, true);
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
<ng-container *ngIf="data$ | async as data">
|
||||
<div class="auth-wrapper">
|
||||
<div class="auth-header">
|
||||
<div class="left">
|
||||
<ng-container *ngIf="data.message.type != BrowserFido2MessageTypes.PickCredentialRequest">
|
||||
<div class="logo">
|
||||
<i class="bwi bwi-shield"></i>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="data.message.type === BrowserFido2MessageTypes.PickCredentialRequest">
|
||||
<div class="logo">
|
||||
<i class="bwi bwi-shield"></i><span><strong>bit</strong>warden</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container
|
||||
*ngIf="data.message.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest"
|
||||
>
|
||||
<div class="search">
|
||||
<input
|
||||
type="{{ searchTypeSearch ? 'search' : 'text' }}"
|
||||
placeholder="{{ 'searchVault' | i18n }}"
|
||||
id="search"
|
||||
[(ngModel)]="searchText"
|
||||
(input)="search()"
|
||||
autocomplete="off"
|
||||
appAutofocus
|
||||
/>
|
||||
<i class="bwi bwi-search" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="button" (click)="addCipher()" appA11yTitle="{{ 'addItem' | i18n }}">
|
||||
<i class="bwi bwi-plus bwi-lg bwi-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
data.message.type === BrowserFido2MessageTypes.PickCredentialRequest ||
|
||||
data.message.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest
|
||||
"
|
||||
>
|
||||
<div class="auth-flow">
|
||||
<p class="subtitle" appA11yTitle="{{ subtitleText | i18n }}">
|
||||
{{ subtitleText | i18n }}
|
||||
</p>
|
||||
<!-- Display when ciphers exist -->
|
||||
<ng-container *ngIf="displayedCiphers.length > 0">
|
||||
<div class="box list">
|
||||
<div class="box-content">
|
||||
<app-fido2-cipher-row-v1
|
||||
*ngFor="let cipherItem of displayedCiphers"
|
||||
[cipher]="cipherItem"
|
||||
[isSearching]="searchPending"
|
||||
title="{{ 'passkeyItem' | i18n }}"
|
||||
(onSelected)="selectedPasskey($event)"
|
||||
[isSelected]="cipher === cipherItem"
|
||||
></app-fido2-cipher-row-v1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<button
|
||||
type="submit"
|
||||
(click)="submit()"
|
||||
class="btn primary block"
|
||||
appA11yTitle="{{ credentialText | i18n }}"
|
||||
>
|
||||
<span [hidden]="loading">
|
||||
{{ credentialText | i18n }}
|
||||
</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-lg bwi-spin"
|
||||
[hidden]="!loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!displayedCiphers.length">
|
||||
<div class="box">
|
||||
<button
|
||||
type="submit"
|
||||
(click)="saveNewLogin()"
|
||||
class="btn primary block"
|
||||
appA11yTitle="{{ 'savePasskeyNewLogin' | i18n }}"
|
||||
>
|
||||
<span [hidden]="loading">
|
||||
{{ "savePasskeyNewLogin" | i18n }}
|
||||
</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-lg bwi-spin"
|
||||
[hidden]="!loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="data.message.type === BrowserFido2MessageTypes.InformExcludedCredentialRequest"
|
||||
>
|
||||
<div class="auth-flow">
|
||||
<p class="subtitle">{{ "passkeyAlreadyExists" | i18n }}</p>
|
||||
<div class="box list">
|
||||
<div class="box-content">
|
||||
<app-fido2-cipher-row-v1
|
||||
*ngFor="let cipherItem of displayedCiphers"
|
||||
[cipher]="cipherItem"
|
||||
title="{{ 'passkeyItem' | i18n }}"
|
||||
(onSelected)="selectedPasskey($event)"
|
||||
[isSelected]="cipher === cipherItem"
|
||||
></app-fido2-cipher-row-v1>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn primary block" (click)="viewPasskey()">
|
||||
<span [hidden]="loading">{{ "viewItem" | i18n }}</span>
|
||||
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="data.message.type === BrowserFido2MessageTypes.InformCredentialNotFoundRequest"
|
||||
>
|
||||
<div class="auth-flow">
|
||||
<p class="subtitle">{{ "noPasskeysFoundForThisApplication" | i18n }}</p>
|
||||
</div>
|
||||
<button type="button" class="btn primary block" (click)="abort(false)">
|
||||
<span [hidden]="loading">{{ "close" | i18n }}</span>
|
||||
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<app-fido2-use-browser-link-v1></app-fido2-use-browser-link-v1>
|
||||
</div>
|
||||
</ng-container>
|
@ -1,445 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
concatMap,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
Subject,
|
||||
take,
|
||||
takeUntil,
|
||||
} from "rxjs";
|
||||
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view";
|
||||
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
|
||||
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,
|
||||
BrowserFido2MessageTypes,
|
||||
} from "../../fido2/services/browser-fido2-user-interface.service";
|
||||
|
||||
interface ViewData {
|
||||
message: BrowserFido2Message;
|
||||
fallbackSupported: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "app-fido2-v1",
|
||||
templateUrl: "fido2-v1.component.html",
|
||||
styleUrls: [],
|
||||
})
|
||||
export class Fido2V1Component implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
private hasSearched = false;
|
||||
|
||||
protected cipher: CipherView;
|
||||
protected searchTypeSearch = false;
|
||||
protected searchPending = false;
|
||||
protected searchText: string;
|
||||
protected url: string;
|
||||
protected hostname: string;
|
||||
protected data$: Observable<ViewData>;
|
||||
protected sessionId?: string;
|
||||
protected senderTabId?: string;
|
||||
protected ciphers?: CipherView[] = [];
|
||||
protected displayedCiphers?: CipherView[] = [];
|
||||
protected loading = false;
|
||||
protected subtitleText: string;
|
||||
protected credentialText: string;
|
||||
protected BrowserFido2MessageTypes = BrowserFido2MessageTypes;
|
||||
|
||||
private message$ = new BehaviorSubject<BrowserFido2Message>(null);
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private cipherService: CipherService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
private searchService: SearchService,
|
||||
private logService: LogService,
|
||||
private dialogService: DialogService,
|
||||
private browserMessagingApi: ZonedMessageListenerService,
|
||||
private passwordRepromptService: PasswordRepromptService,
|
||||
private fido2UserVerificationService: Fido2UserVerificationService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
||||
|
||||
const queryParams$ = this.activatedRoute.queryParamMap.pipe(
|
||||
take(1),
|
||||
map((queryParamMap) => ({
|
||||
sessionId: queryParamMap.get("sessionId"),
|
||||
senderTabId: queryParamMap.get("senderTabId"),
|
||||
senderUrl: queryParamMap.get("senderUrl"),
|
||||
})),
|
||||
);
|
||||
|
||||
combineLatest([
|
||||
queryParams$,
|
||||
this.browserMessagingApi.messageListener$() as Observable<BrowserFido2Message>,
|
||||
])
|
||||
.pipe(
|
||||
concatMap(async ([queryParams, message]) => {
|
||||
this.sessionId = queryParams.sessionId;
|
||||
this.senderTabId = queryParams.senderTabId;
|
||||
this.url = queryParams.senderUrl;
|
||||
// For a 'NewSessionCreatedRequest', abort if it doesn't belong to the current session.
|
||||
if (
|
||||
message.type === BrowserFido2MessageTypes.NewSessionCreatedRequest &&
|
||||
message.sessionId !== queryParams.sessionId
|
||||
) {
|
||||
this.abort(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore messages that don't belong to the current session.
|
||||
if (message.sessionId !== queryParams.sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === BrowserFido2MessageTypes.AbortRequest) {
|
||||
this.abort(false);
|
||||
return;
|
||||
}
|
||||
|
||||
return message;
|
||||
}),
|
||||
filter((message) => !!message),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe((message) => {
|
||||
this.message$.next(message);
|
||||
});
|
||||
|
||||
this.data$ = this.message$.pipe(
|
||||
filter((message) => message != undefined),
|
||||
concatMap(async (message) => {
|
||||
switch (message.type) {
|
||||
case BrowserFido2MessageTypes.ConfirmNewCredentialRequest: {
|
||||
const equivalentDomains = await firstValueFrom(
|
||||
this.domainSettingsService.getUrlEquivalentDomains(this.url),
|
||||
);
|
||||
|
||||
this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
|
||||
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted,
|
||||
);
|
||||
this.displayedCiphers = this.ciphers.filter(
|
||||
(cipher) =>
|
||||
cipher.login.matchesUri(this.url, equivalentDomains) &&
|
||||
this.hasNoOtherPasskeys(cipher, message.userHandle),
|
||||
);
|
||||
|
||||
if (this.displayedCiphers.length > 0) {
|
||||
this.selectedPasskey(this.displayedCiphers[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BrowserFido2MessageTypes.PickCredentialRequest: {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
|
||||
this.ciphers = await Promise.all(
|
||||
message.cipherIds.map(async (cipherId) => {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
return cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
}),
|
||||
);
|
||||
this.displayedCiphers = [...this.ciphers];
|
||||
if (this.displayedCiphers.length > 0) {
|
||||
this.selectedPasskey(this.displayedCiphers[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BrowserFido2MessageTypes.InformExcludedCredentialRequest: {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
|
||||
this.ciphers = await Promise.all(
|
||||
message.existingCipherIds.map(async (cipherId) => {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
return cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
}),
|
||||
);
|
||||
this.displayedCiphers = [...this.ciphers];
|
||||
|
||||
if (this.displayedCiphers.length > 0) {
|
||||
this.selectedPasskey(this.displayedCiphers[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.subtitleText =
|
||||
this.displayedCiphers.length > 0
|
||||
? this.getCredentialSubTitleText(message.type)
|
||||
: "noMatchingPasskeyLogin";
|
||||
|
||||
this.credentialText = this.getCredentialButtonText(message.type);
|
||||
return {
|
||||
message,
|
||||
fallbackSupported: "fallbackSupported" in message && message.fallbackSupported,
|
||||
};
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
|
||||
queryParams$.pipe(takeUntil(this.destroy$)).subscribe((queryParams) => {
|
||||
this.send({
|
||||
sessionId: queryParams.sessionId,
|
||||
type: BrowserFido2MessageTypes.ConnectResponse,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected async submit() {
|
||||
const data = this.message$.value;
|
||||
if (data?.type === BrowserFido2MessageTypes.PickCredentialRequest) {
|
||||
// TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production.
|
||||
// PM-4577 - https://github.com/bitwarden/clients/pull/8746
|
||||
const userVerified = await this.handleUserVerification(data.userVerification, this.cipher);
|
||||
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
cipherId: this.cipher.id,
|
||||
type: BrowserFido2MessageTypes.PickCredentialResponse,
|
||||
userVerified,
|
||||
});
|
||||
} else if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) {
|
||||
if (this.cipher.login.hasFido2Credentials) {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "overwritePasskey" },
|
||||
content: { key: "overwritePasskeyAlert" },
|
||||
type: "info",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production.
|
||||
// PM-4577 - https://github.com/bitwarden/clients/pull/8746
|
||||
const userVerified = await this.handleUserVerification(data.userVerification, this.cipher);
|
||||
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
cipherId: this.cipher.id,
|
||||
type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse,
|
||||
userVerified,
|
||||
});
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
}
|
||||
|
||||
protected async saveNewLogin() {
|
||||
const data = this.message$.value;
|
||||
if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) {
|
||||
const name = data.credentialName || data.rpId;
|
||||
// TODO: Revert to check for user verification once user verification for passkeys is approved for production.
|
||||
// PM-4577 - https://github.com/bitwarden/clients/pull/8746
|
||||
await this.createNewCipher(name, data.userName);
|
||||
|
||||
// We are bypassing user verification pending approval.
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
cipherId: this.cipher?.id,
|
||||
type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse,
|
||||
userVerified: data.userVerification,
|
||||
});
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
}
|
||||
|
||||
getCredentialSubTitleText(messageType: string): string {
|
||||
return messageType == BrowserFido2MessageTypes.ConfirmNewCredentialRequest
|
||||
? "chooseCipherForPasskeySave"
|
||||
: "logInWithPasskeyQuestion";
|
||||
}
|
||||
|
||||
getCredentialButtonText(messageType: string): string {
|
||||
return messageType == BrowserFido2MessageTypes.ConfirmNewCredentialRequest
|
||||
? "savePasskey"
|
||||
: "confirm";
|
||||
}
|
||||
|
||||
selectedPasskey(item: CipherView) {
|
||||
this.cipher = item;
|
||||
}
|
||||
|
||||
viewPasskey() {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/view-cipher"], {
|
||||
queryParams: {
|
||||
cipherId: this.cipher.id,
|
||||
uilocation: "popout",
|
||||
senderTabId: this.senderTabId,
|
||||
sessionId: this.sessionId,
|
||||
singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
addCipher() {
|
||||
const data = this.message$.value;
|
||||
|
||||
if (data?.type !== BrowserFido2MessageTypes.ConfirmNewCredentialRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/add-cipher"], {
|
||||
queryParams: {
|
||||
name: data.credentialName || data.rpId,
|
||||
uri: this.url,
|
||||
type: CipherType.Login.toString(),
|
||||
uilocation: "popout",
|
||||
username: data.userName,
|
||||
senderTabId: this.senderTabId,
|
||||
sessionId: this.sessionId,
|
||||
userVerification: data.userVerification,
|
||||
singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected async search() {
|
||||
this.hasSearched = await this.searchService.isSearchable(this.searchText);
|
||||
this.searchPending = true;
|
||||
if (this.hasSearched) {
|
||||
this.displayedCiphers = await this.searchService.searchCiphers(
|
||||
this.searchText,
|
||||
null,
|
||||
this.ciphers,
|
||||
);
|
||||
} else {
|
||||
const equivalentDomains = await firstValueFrom(
|
||||
this.domainSettingsService.getUrlEquivalentDomains(this.url),
|
||||
);
|
||||
this.displayedCiphers = this.ciphers.filter((cipher) =>
|
||||
cipher.login.matchesUri(this.url, equivalentDomains),
|
||||
);
|
||||
}
|
||||
this.searchPending = false;
|
||||
this.selectedPasskey(this.displayedCiphers[0]);
|
||||
}
|
||||
|
||||
abort(fallback: boolean) {
|
||||
this.unload(fallback);
|
||||
window.close();
|
||||
}
|
||||
|
||||
unload(fallback = false) {
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
type: BrowserFido2MessageTypes.AbortResponse,
|
||||
fallbackRequested: fallback,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private buildCipher(name: string, username: string) {
|
||||
this.cipher = new CipherView();
|
||||
this.cipher.name = name;
|
||||
|
||||
this.cipher.type = CipherType.Login;
|
||||
this.cipher.login = new LoginView();
|
||||
this.cipher.login.username = username;
|
||||
this.cipher.login.uris = [new LoginUriView()];
|
||||
this.cipher.login.uris[0].uri = this.url;
|
||||
this.cipher.card = new CardView();
|
||||
this.cipher.identity = new IdentityView();
|
||||
this.cipher.secureNote = new SecureNoteView();
|
||||
this.cipher.secureNote.type = SecureNoteType.Generic;
|
||||
this.cipher.reprompt = CipherRepromptType.None;
|
||||
}
|
||||
|
||||
private async createNewCipher(name: string, username: string) {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
|
||||
this.buildCipher(name, username);
|
||||
const cipher = await this.cipherService.encrypt(this.cipher, activeUserId);
|
||||
try {
|
||||
await this.cipherService.createWithServer(cipher);
|
||||
this.cipher.id = cipher.id;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove and use fido2 user verification service once user verification for passkeys is approved for production.
|
||||
private async handleUserVerification(
|
||||
userVerificationRequested: boolean,
|
||||
cipher: CipherView,
|
||||
): Promise<boolean> {
|
||||
const masterPasswordRepromptRequired = cipher && cipher.reprompt !== 0;
|
||||
|
||||
if (masterPasswordRepromptRequired) {
|
||||
return await this.passwordRepromptService.showPasswordPrompt();
|
||||
}
|
||||
|
||||
return userVerificationRequested;
|
||||
}
|
||||
|
||||
private send(msg: BrowserFido2Message) {
|
||||
BrowserFido2UserInterfaceSession.sendMessage({
|
||||
sessionId: this.sessionId,
|
||||
...msg,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle
|
||||
* @param userHandle
|
||||
*/
|
||||
private hasNoOtherPasskeys(cipher: CipherView, userHandle: string): boolean {
|
||||
if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return cipher.login.fido2Credentials.some((passkey) => passkey.userHandle === userHandle);
|
||||
}
|
||||
}
|
@ -1,270 +0,0 @@
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "autofill" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right"></div>
|
||||
</header>
|
||||
<main tabindex="-1">
|
||||
<div class="box tw-mt-4">
|
||||
<div class="box-content">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-link box-content-row-flex"
|
||||
(click)="commandSettings()"
|
||||
>
|
||||
<div class="row-main">{{ "autofillShortcut" | i18n }}</div>
|
||||
<i class="bwi bwi-external-link bwi-lg bwi-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="autofillKeyboardHelp" class="box-footer">
|
||||
{{ autofillKeyboardHelperText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="autofill-overlay-settings">{{ "showAutoFillMenuOnFormFields" | i18n }}</label>
|
||||
<select
|
||||
id="autofill-overlay-settings"
|
||||
name="autofill-overlay-settings"
|
||||
[(ngModel)]="autoFillOverlayVisibility"
|
||||
(change)="updateAutoFillOverlayVisibility()"
|
||||
>
|
||||
<option *ngFor="let o of autoFillOverlayVisibilityOptions" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="box-footer" *ngIf="accountSwitcherEnabled || !canOverrideBrowserAutofillSetting">
|
||||
<span *ngIf="accountSwitcherEnabled">{{ "showInlineMenuOnFormFieldsDescAlt" | i18n }}</span>
|
||||
<span *ngIf="!canOverrideBrowserAutofillSetting">
|
||||
{{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }}
|
||||
<a
|
||||
[attr.href]="disablePasswordManagerLink"
|
||||
(click)="openDisablePasswordManagerLink($event)"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box tw-mb-5" *ngIf="inlineMenuPositioningImprovementsEnabled && inlineMenuIsEnabled">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="show-inline-menu-identities" class="!tw-mr-0">{{
|
||||
"showInlineMenuIdentitiesLabel" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="show-inline-menu-identities"
|
||||
type="checkbox"
|
||||
(change)="updateShowInlineMenuIdentities()"
|
||||
[(ngModel)]="showInlineMenuIdentities"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="show-inline-menu-cards" class="!tw-mr-0">{{
|
||||
"showInlineMenuCardsLabel" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="show-inline-menu-cards"
|
||||
type="checkbox"
|
||||
(change)="updateShowInlineMenuCards()"
|
||||
[(ngModel)]="showInlineMenuCards"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content" *ngIf="canOverrideBrowserAutofillSetting">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="overrideBrowserAutofill" class="!tw-mr-0">{{
|
||||
"overrideDefaultBrowserAutoFillSettings" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="overrideBrowserAutofill"
|
||||
type="checkbox"
|
||||
(change)="updateDefaultBrowserAutofillDisabled()"
|
||||
[(ngModel)]="defaultBrowserAutofillDisabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer" *ngIf="canOverrideBrowserAutofillSetting">
|
||||
<span *ngIf="accountSwitcherEnabled">{{ "showInlineMenuOnFormFieldsDescAlt" | i18n }}</span>
|
||||
{{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }}
|
||||
<a
|
||||
[attr.href]="disablePasswordManagerLink"
|
||||
(click)="openDisablePasswordManagerLink($event)"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box tw-mt-4">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="autofill">{{ "enableAutoFillOnPageLoad" | i18n }}</label>
|
||||
<input
|
||||
id="autofill"
|
||||
type="checkbox"
|
||||
aria-describedby="autofillHelp"
|
||||
(change)="updateAutoFillOnPageLoad()"
|
||||
[(ngModel)]="enableAutoFillOnPageLoad"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="autofillHelp" class="box-footer">
|
||||
{{ "enableAutoFillOnPageLoadDesc" | i18n }}
|
||||
<b>{{ "warning" | i18n }}</b
|
||||
>: {{ "experimentalFeature" | i18n }}
|
||||
<a href="https://bitwarden.com/help/auto-fill-browser/" target="_blank" rel="noreferrer">
|
||||
{{ "learnMoreAboutAutofill" | i18n }}.
|
||||
<i
|
||||
[attr.aria-label]="'opensInANewWindow' | i18n"
|
||||
class="bwi bwi-external-link bwi-sm bwi-fw"
|
||||
></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="defaultAutofill">{{ "defaultAutoFillOnPageLoad" | i18n }}</label>
|
||||
<select
|
||||
id="defaultAutofill"
|
||||
name="DefaultAutofill"
|
||||
aria-describedby="defaultAutofillHelp"
|
||||
[(ngModel)]="autoFillOnPageLoadDefault"
|
||||
(change)="updateAutoFillOnPageLoadDefault()"
|
||||
[disabled]="!enableAutoFillOnPageLoad"
|
||||
>
|
||||
<option *ngFor="let o of autoFillOnPageLoadOptions" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="defaultAutofillHelp" class="box-footer">
|
||||
{{ "defaultAutoFillOnPageLoadDesc" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h2 class="box-header">{{ "additionalOptions" | i18n }}</h2>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="context-menu">{{ "enableContextMenuItem" | i18n }}</label>
|
||||
<input
|
||||
id="context-menu"
|
||||
type="checkbox"
|
||||
aria-describedby="context-menuHelp"
|
||||
(change)="updateContextMenuItem()"
|
||||
[(ngModel)]="enableContextMenuItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="context-menuHelp" class="box-footer">
|
||||
{{
|
||||
accountSwitcherEnabled ? ("contextMenuItemDescAlt" | i18n) : ("contextMenuItemDesc" | i18n)
|
||||
}}
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="totp">{{ "enableAutoTotpCopy" | i18n }}</label>
|
||||
<input
|
||||
id="totp"
|
||||
type="checkbox"
|
||||
aria-describedby="totpHelp"
|
||||
(change)="updateAutoTotpCopy()"
|
||||
[(ngModel)]="enableAutoTotpCopy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="totpHelp" class="box-footer">{{ "disableAutoTotpCopyDesc" | i18n }}</div>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
|
||||
<select
|
||||
id="clearClipboard"
|
||||
name="ClearClipboard"
|
||||
aria-describedby="clearClipboardHelp"
|
||||
[(ngModel)]="clearClipboard"
|
||||
(change)="saveClearClipboard()"
|
||||
>
|
||||
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="clearClipboardHelp" class="box-footer">{{ "clearClipboardDesc" | i18n }}</div>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="defaultUriMatch">{{ "defaultUriMatchDetection" | i18n }}</label>
|
||||
<select
|
||||
id="defaultUriMatch"
|
||||
name="DefaultUriMatch"
|
||||
aria-describedby="defaultUriMatchHelp"
|
||||
[(ngModel)]="defaultUriMatch"
|
||||
(change)="saveDefaultUriMatch()"
|
||||
>
|
||||
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="defaultUriMatchHelp" class="box-footer">
|
||||
{{ "defaultUriMatchDetectionDesc" | i18n }}
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="showCardsCurrentTab">{{ "showCardsCurrentTab" | i18n }}</label>
|
||||
<input
|
||||
id="showCardsCurrentTab"
|
||||
type="checkbox"
|
||||
aria-describedby="showCardsCurrentTabHelp"
|
||||
(change)="updateShowCardsCurrentTab()"
|
||||
[(ngModel)]="showCardsCurrentTab"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="showCardsCurrentTabHelp" class="box-footer">
|
||||
{{ "showCardsCurrentTabDesc" | i18n }}
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="showIdentitiesCurrentTab">{{ "showIdentitiesCurrentTab" | i18n }}</label>
|
||||
<input
|
||||
id="showIdentitiesCurrentTab"
|
||||
type="checkbox"
|
||||
aria-describedby="showIdentitiesCurrentTabHelp"
|
||||
(change)="updateShowIdentitiesCurrentTab()"
|
||||
[(ngModel)]="showIdentitiesCurrentTab"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="showIdentitiesCurrentTabHelp" class="box-footer">
|
||||
{{ "showIdentitiesCurrentTabDesc" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list" *ngIf="blockBrowserInjectionsByDomainEnabled">
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/blocked-domains"
|
||||
>
|
||||
<div class="row-main">{{ "blockedDomains" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
@ -1,344 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import {
|
||||
InlineMenuVisibilitySetting,
|
||||
ClearClipboardDelaySetting,
|
||||
} from "@bitwarden/common/autofill/types";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import {
|
||||
UriMatchStrategy,
|
||||
UriMatchStrategySetting,
|
||||
} from "@bitwarden/common/models/domain/domain-service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
|
||||
@Component({
|
||||
selector: "app-autofill-v1",
|
||||
templateUrl: "autofill-v1.component.html",
|
||||
})
|
||||
export class AutofillV1Component implements OnInit {
|
||||
protected canOverrideBrowserAutofillSetting = false;
|
||||
protected defaultBrowserAutofillDisabled = false;
|
||||
protected autoFillOverlayVisibility: InlineMenuVisibilitySetting;
|
||||
protected autoFillOverlayVisibilityOptions: any[];
|
||||
protected disablePasswordManagerLink: string;
|
||||
protected inlineMenuPositioningImprovementsEnabled: boolean = false;
|
||||
protected blockBrowserInjectionsByDomainEnabled: boolean = false;
|
||||
protected showInlineMenuIdentities: boolean = true;
|
||||
protected showInlineMenuCards: boolean = true;
|
||||
inlineMenuIsEnabled: boolean = false;
|
||||
enableAutoFillOnPageLoad = false;
|
||||
autoFillOnPageLoadDefault = false;
|
||||
autoFillOnPageLoadOptions: any[];
|
||||
enableContextMenuItem = false;
|
||||
enableAutoTotpCopy = false; // TODO: Does it matter if this is set to false or true?
|
||||
clearClipboard: ClearClipboardDelaySetting;
|
||||
clearClipboardOptions: any[];
|
||||
defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain;
|
||||
uriMatchOptions: any[];
|
||||
showCardsCurrentTab = false;
|
||||
showIdentitiesCurrentTab = false;
|
||||
autofillKeyboardHelperText: string;
|
||||
accountSwitcherEnabled = false;
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
private configService: ConfigService,
|
||||
private dialogService: DialogService,
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private messagingService: MessagingService,
|
||||
private vaultSettingsService: VaultSettingsService,
|
||||
) {
|
||||
this.autoFillOverlayVisibilityOptions = [
|
||||
{
|
||||
name: i18nService.t("autofillOverlayVisibilityOff"),
|
||||
value: AutofillOverlayVisibility.Off,
|
||||
},
|
||||
{
|
||||
name: i18nService.t("autofillOverlayVisibilityOnFieldFocus"),
|
||||
value: AutofillOverlayVisibility.OnFieldFocus,
|
||||
},
|
||||
{
|
||||
name: i18nService.t("autofillOverlayVisibilityOnButtonClick"),
|
||||
value: AutofillOverlayVisibility.OnButtonClick,
|
||||
},
|
||||
];
|
||||
this.autoFillOnPageLoadOptions = [
|
||||
{ name: i18nService.t("autoFillOnPageLoadYes"), value: true },
|
||||
{ name: i18nService.t("autoFillOnPageLoadNo"), value: false },
|
||||
];
|
||||
this.clearClipboardOptions = [
|
||||
{ name: i18nService.t("never"), value: null },
|
||||
{ name: i18nService.t("tenSeconds"), value: 10 },
|
||||
{ name: i18nService.t("twentySeconds"), value: 20 },
|
||||
{ name: i18nService.t("thirtySeconds"), value: 30 },
|
||||
{ name: i18nService.t("oneMinute"), value: 60 },
|
||||
{ name: i18nService.t("twoMinutes"), value: 120 },
|
||||
{ name: i18nService.t("fiveMinutes"), value: 300 },
|
||||
];
|
||||
this.uriMatchOptions = [
|
||||
{ name: i18nService.t("baseDomainOptionRecommended"), value: UriMatchStrategy.Domain },
|
||||
{ name: i18nService.t("host"), value: UriMatchStrategy.Host },
|
||||
{ name: i18nService.t("startsWith"), value: UriMatchStrategy.StartsWith },
|
||||
{ name: i18nService.t("regEx"), value: UriMatchStrategy.RegularExpression },
|
||||
{ name: i18nService.t("exact"), value: UriMatchStrategy.Exact },
|
||||
{ name: i18nService.t("never"), value: UriMatchStrategy.Never },
|
||||
];
|
||||
|
||||
this.accountSwitcherEnabled = enableAccountSwitching();
|
||||
this.disablePasswordManagerLink = this.getDisablePasswordManagerLink();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.canOverrideBrowserAutofillSetting =
|
||||
this.platformUtilsService.isChrome() ||
|
||||
this.platformUtilsService.isEdge() ||
|
||||
this.platformUtilsService.isOpera() ||
|
||||
this.platformUtilsService.isVivaldi();
|
||||
|
||||
this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden();
|
||||
|
||||
this.autoFillOverlayVisibility = await firstValueFrom(
|
||||
this.autofillSettingsService.inlineMenuVisibility$,
|
||||
);
|
||||
|
||||
this.inlineMenuPositioningImprovementsEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.InlineMenuPositioningImprovements,
|
||||
);
|
||||
|
||||
this.blockBrowserInjectionsByDomainEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.BlockBrowserInjectionsByDomain,
|
||||
);
|
||||
|
||||
this.inlineMenuIsEnabled = this.isInlineMenuEnabled();
|
||||
|
||||
this.showInlineMenuIdentities =
|
||||
this.inlineMenuPositioningImprovementsEnabled &&
|
||||
(await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$));
|
||||
|
||||
this.showInlineMenuCards =
|
||||
this.inlineMenuPositioningImprovementsEnabled &&
|
||||
(await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$));
|
||||
|
||||
this.enableAutoFillOnPageLoad = await firstValueFrom(
|
||||
this.autofillSettingsService.autofillOnPageLoad$,
|
||||
);
|
||||
|
||||
this.autoFillOnPageLoadDefault = await firstValueFrom(
|
||||
this.autofillSettingsService.autofillOnPageLoadDefault$,
|
||||
);
|
||||
|
||||
this.enableContextMenuItem = await firstValueFrom(
|
||||
this.autofillSettingsService.enableContextMenu$,
|
||||
);
|
||||
|
||||
this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$);
|
||||
|
||||
this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$);
|
||||
|
||||
const defaultUriMatch = await firstValueFrom(
|
||||
this.domainSettingsService.defaultUriMatchStrategy$,
|
||||
);
|
||||
this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch;
|
||||
|
||||
const command = await this.platformUtilsService.getAutofillKeyboardShortcut();
|
||||
await this.setAutofillKeyboardHelperText(command);
|
||||
|
||||
this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$);
|
||||
|
||||
this.showIdentitiesCurrentTab = await firstValueFrom(
|
||||
this.vaultSettingsService.showIdentitiesCurrentTab$,
|
||||
);
|
||||
}
|
||||
|
||||
isInlineMenuEnabled() {
|
||||
return (
|
||||
this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnFieldFocus ||
|
||||
this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnButtonClick
|
||||
);
|
||||
}
|
||||
|
||||
async updateAutoFillOverlayVisibility() {
|
||||
await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility);
|
||||
await this.requestPrivacyPermission();
|
||||
|
||||
this.inlineMenuIsEnabled = this.isInlineMenuEnabled();
|
||||
}
|
||||
|
||||
async updateAutoFillOnPageLoad() {
|
||||
await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad);
|
||||
}
|
||||
|
||||
async updateAutoFillOnPageLoadDefault() {
|
||||
await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault);
|
||||
}
|
||||
|
||||
async saveDefaultUriMatch() {
|
||||
await this.domainSettingsService.setDefaultUriMatchStrategy(this.defaultUriMatch);
|
||||
}
|
||||
|
||||
private async setAutofillKeyboardHelperText(command: string) {
|
||||
if (command) {
|
||||
this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutText", command);
|
||||
} else {
|
||||
this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutNotSet");
|
||||
}
|
||||
}
|
||||
|
||||
async commandSettings() {
|
||||
if (this.platformUtilsService.isChrome()) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
BrowserApi.createNewTab("chrome://extensions/shortcuts");
|
||||
} else if (this.platformUtilsService.isOpera()) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
BrowserApi.createNewTab("opera://extensions/shortcuts");
|
||||
} else if (this.platformUtilsService.isEdge()) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
BrowserApi.createNewTab("edge://extensions/shortcuts");
|
||||
} else if (this.platformUtilsService.isVivaldi()) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
BrowserApi.createNewTab("vivaldi://extensions/shortcuts");
|
||||
} else {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
BrowserApi.createNewTab("https://bitwarden.com/help/keyboard-shortcuts");
|
||||
}
|
||||
}
|
||||
|
||||
private getDisablePasswordManagerLink(): string {
|
||||
if (this.platformUtilsService.isChrome()) {
|
||||
return "chrome://settings/autofill";
|
||||
}
|
||||
if (this.platformUtilsService.isOpera()) {
|
||||
return "opera://settings/autofill";
|
||||
}
|
||||
if (this.platformUtilsService.isEdge()) {
|
||||
return "edge://settings/passwords";
|
||||
}
|
||||
if (this.platformUtilsService.isVivaldi()) {
|
||||
return "vivaldi://settings/autofill";
|
||||
}
|
||||
|
||||
return "https://bitwarden.com/help/disable-browser-autofill/";
|
||||
}
|
||||
|
||||
protected openDisablePasswordManagerLink(event: Event) {
|
||||
event.preventDefault();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
BrowserApi.createNewTab(this.disablePasswordManagerLink);
|
||||
}
|
||||
|
||||
async requestPrivacyPermission() {
|
||||
if (
|
||||
this.autoFillOverlayVisibility === AutofillOverlayVisibility.Off ||
|
||||
!this.canOverrideBrowserAutofillSetting ||
|
||||
(await this.browserAutofillSettingCurrentlyOverridden())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: { key: "overrideDefaultBrowserAutofillTitle" },
|
||||
content: { key: "overrideDefaultBrowserAutofillDescription" },
|
||||
acceptButtonText: { key: "makeDefault" },
|
||||
acceptAction: async () => await this.handleOverrideDialogAccept(),
|
||||
cancelButtonText: { key: "ignore" },
|
||||
type: "info",
|
||||
});
|
||||
}
|
||||
|
||||
async updateDefaultBrowserAutofillDisabled() {
|
||||
const privacyPermissionGranted = await this.privacyPermissionGranted();
|
||||
if (!this.defaultBrowserAutofillDisabled && !privacyPermissionGranted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!privacyPermissionGranted &&
|
||||
!(await BrowserApi.requestPermission({ permissions: ["privacy"] }))
|
||||
) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: { key: "privacyPermissionAdditionNotGrantedTitle" },
|
||||
content: { key: "privacyPermissionAdditionNotGrantedDescription" },
|
||||
acceptButtonText: { key: "ok" },
|
||||
cancelButtonText: null,
|
||||
type: "warning",
|
||||
});
|
||||
this.defaultBrowserAutofillDisabled = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled);
|
||||
}
|
||||
|
||||
private handleOverrideDialogAccept = async () => {
|
||||
this.defaultBrowserAutofillDisabled = true;
|
||||
await this.updateDefaultBrowserAutofillDisabled();
|
||||
};
|
||||
|
||||
async browserAutofillSettingCurrentlyOverridden() {
|
||||
if (!this.canOverrideBrowserAutofillSetting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(await this.privacyPermissionGranted())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await BrowserApi.browserAutofillSettingsOverridden();
|
||||
}
|
||||
|
||||
async privacyPermissionGranted(): Promise<boolean> {
|
||||
return await BrowserApi.permissionsGranted(["privacy"]);
|
||||
}
|
||||
|
||||
async updateContextMenuItem() {
|
||||
await this.autofillSettingsService.setEnableContextMenu(this.enableContextMenuItem);
|
||||
this.messagingService.send("bgUpdateContextMenu");
|
||||
}
|
||||
|
||||
async updateAutoTotpCopy() {
|
||||
await this.autofillSettingsService.setAutoCopyTotp(this.enableAutoTotpCopy);
|
||||
}
|
||||
|
||||
async saveClearClipboard() {
|
||||
await this.autofillSettingsService.setClearClipboardDelay(this.clearClipboard);
|
||||
}
|
||||
|
||||
async updateShowCardsCurrentTab() {
|
||||
await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab);
|
||||
}
|
||||
|
||||
async updateShowIdentitiesCurrentTab() {
|
||||
await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
|
||||
}
|
||||
|
||||
async updateShowInlineMenuCards() {
|
||||
await this.autofillSettingsService.setShowInlineMenuCards(this.showInlineMenuCards);
|
||||
}
|
||||
|
||||
async updateShowInlineMenuIdentities() {
|
||||
await this.autofillSettingsService.setShowInlineMenuIdentities(this.showInlineMenuIdentities);
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
<form #form (ngSubmit)="submit()">
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/notifications">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "excludedDomains" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<button type="submit">{{ "save" | i18n }}</button>
|
||||
</div>
|
||||
</header>
|
||||
<main tabindex="-1">
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-footer" [ngStyle]="{ marginTop: '10px' }">
|
||||
{{
|
||||
accountSwitcherEnabled
|
||||
? ("excludedDomainsDescAlt" | i18n)
|
||||
: ("excludedDomainsDesc" | i18n)
|
||||
}}
|
||||
</div>
|
||||
<ng-container *ngIf="excludedDomains">
|
||||
<div
|
||||
class="box-content-row box-content-row-multi"
|
||||
appBoxRow
|
||||
*ngFor="let domain of excludedDomains; let i = index; trackBy: trackByFunction"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
appStopClick
|
||||
(click)="removeUri(i)"
|
||||
appA11yTitle="{{ 'remove' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="row-main">
|
||||
<label for="excludedDomain{{ i }}">{{ "uriPosition" | i18n: i + 1 }}</label>
|
||||
<input
|
||||
id="excludedDomain{{ i }}"
|
||||
name="excludedDomain{{ i }}"
|
||||
type="text"
|
||||
[(ngModel)]="domain.uri"
|
||||
placeholder="{{ 'ex' | i18n }} https://google.com"
|
||||
inputmode="url"
|
||||
appInputVerbatim
|
||||
/>
|
||||
<label for="currentUris{{ i }}" class="sr-only">
|
||||
{{ "currentUri" | i18n }} {{ i + 1 }}
|
||||
</label>
|
||||
<select
|
||||
*ngIf="currentUris && currentUris.length"
|
||||
id="currentUris{{ i }}"
|
||||
name="currentUris{{ i }}"
|
||||
[(ngModel)]="domain.uri"
|
||||
[hidden]="!domain.showCurrentUris"
|
||||
>
|
||||
<option [ngValue]="null">-- {{ "select" | i18n }} --</option>
|
||||
<option *ngFor="let u of currentUris" [ngValue]="u">{{ u }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="currentUris && currentUris.length"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleCurrentUris' | i18n }}"
|
||||
(click)="toggleUriInput(domain)"
|
||||
[attr.aria-pressed]="domain.showCurrentUris === true"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-lg bwi-list"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<button
|
||||
type="button"
|
||||
appStopClick
|
||||
(click)="addUri()"
|
||||
class="box-content-row box-content-row-newmulti single-line"
|
||||
>
|
||||
<i class="bwi bwi-plus-circle bwi-fw bwi-lg" aria-hidden="true"></i> {{ "newUri" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</form>
|
@ -1,143 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
|
||||
interface ExcludedDomain {
|
||||
uri: string;
|
||||
showCurrentUris: boolean;
|
||||
}
|
||||
|
||||
const BroadcasterSubscriptionId = "excludedDomains";
|
||||
|
||||
@Component({
|
||||
selector: "app-excluded-domains-v1",
|
||||
templateUrl: "excluded-domains-v1.component.html",
|
||||
})
|
||||
export class ExcludedDomainsV1Component implements OnInit, OnDestroy {
|
||||
excludedDomains: ExcludedDomain[] = [];
|
||||
existingExcludedDomains: ExcludedDomain[] = [];
|
||||
currentUris: string[];
|
||||
loadCurrentUrisTimeout: number;
|
||||
accountSwitcherEnabled = false;
|
||||
|
||||
constructor(
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
private i18nService: I18nService,
|
||||
private router: Router,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {
|
||||
this.accountSwitcherEnabled = enableAccountSwitching();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const savedDomains = await firstValueFrom(this.domainSettingsService.neverDomains$);
|
||||
if (savedDomains) {
|
||||
for (const uri of Object.keys(savedDomains)) {
|
||||
this.excludedDomains.push({ uri: uri, showCurrentUris: false });
|
||||
this.existingExcludedDomains.push({ uri: uri, showCurrentUris: false });
|
||||
}
|
||||
}
|
||||
|
||||
await this.loadCurrentUris();
|
||||
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "tabChanged":
|
||||
case "windowChanged":
|
||||
if (this.loadCurrentUrisTimeout != null) {
|
||||
window.clearTimeout(this.loadCurrentUrisTimeout);
|
||||
}
|
||||
this.loadCurrentUrisTimeout = window.setTimeout(
|
||||
async () => await this.loadCurrentUris(),
|
||||
500,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async addUri() {
|
||||
this.excludedDomains.push({ uri: "", showCurrentUris: false });
|
||||
}
|
||||
|
||||
async removeUri(i: number) {
|
||||
this.excludedDomains.splice(i, 1);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const savedDomains: { [name: string]: null } = {};
|
||||
const newExcludedDomains = this.getNewlyAddedDomains(this.excludedDomains);
|
||||
for (const domain of this.excludedDomains) {
|
||||
const resp = newExcludedDomains.filter((e) => e.uri === domain.uri);
|
||||
if (resp.length === 0) {
|
||||
savedDomains[domain.uri] = null;
|
||||
} else {
|
||||
if (domain.uri && domain.uri !== "") {
|
||||
const validDomain = Utils.getHostname(domain.uri);
|
||||
if (!validDomain) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("excludedDomainsInvalidDomain", domain.uri),
|
||||
);
|
||||
return;
|
||||
}
|
||||
savedDomains[validDomain] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.domainSettingsService.setNeverDomains(savedDomains);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/tabs/settings"]);
|
||||
}
|
||||
|
||||
trackByFunction(index: number, item: any) {
|
||||
return index;
|
||||
}
|
||||
|
||||
getNewlyAddedDomains(domain: ExcludedDomain[]): ExcludedDomain[] {
|
||||
const result = this.excludedDomains.filter(
|
||||
(newDomain) =>
|
||||
!this.existingExcludedDomains.some((oldDomain) => newDomain.uri === oldDomain.uri),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
toggleUriInput(domain: ExcludedDomain) {
|
||||
domain.showCurrentUris = !domain.showCurrentUris;
|
||||
}
|
||||
|
||||
async loadCurrentUris() {
|
||||
const tabs = await BrowserApi.tabsQuery({ windowType: "normal" });
|
||||
if (tabs) {
|
||||
const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url)));
|
||||
uriSet.delete(null);
|
||||
this.currentUris = Array.from(uriSet);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "notifications" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<app-pop-out></app-pop-out>
|
||||
</div>
|
||||
</header>
|
||||
<main tabindex="-1">
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="use-passkeys">{{ "enableUsePasskeys" | i18n }}</label>
|
||||
<input
|
||||
id="use-passkeys"
|
||||
type="checkbox"
|
||||
aria-describedby="use-passkeysHelp"
|
||||
(change)="updateEnablePasskeys()"
|
||||
[(ngModel)]="enablePasskeys"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="use-passkeysHelp" class="box-footer">
|
||||
{{ "usePasskeysDesc" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="addlogin-notification-bar">{{ "enableAddLoginNotification" | i18n }}</label>
|
||||
<input
|
||||
id="addlogin-notification-bar"
|
||||
type="checkbox"
|
||||
aria-describedby="addlogin-notification-barHelp"
|
||||
(change)="updateAddLoginNotification()"
|
||||
[(ngModel)]="enableAddLoginNotification"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addlogin-notification-barHelp" class="box-footer">
|
||||
{{
|
||||
accountSwitcherEnabled
|
||||
? ("addLoginNotificationDescAlt" | i18n)
|
||||
: ("addLoginNotificationDesc" | i18n)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="changedpass-notification-bar">{{
|
||||
"enableChangedPasswordNotification" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="changedpass-notification-bar"
|
||||
type="checkbox"
|
||||
aria-describedby="changedpass-notification-barHelp"
|
||||
(change)="updateChangedPasswordNotification()"
|
||||
[(ngModel)]="enableChangedPasswordNotification"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="changedpass-notification-barHelp" class="box-footer">
|
||||
{{
|
||||
accountSwitcherEnabled
|
||||
? ("changedPasswordNotificationDescAlt" | i18n)
|
||||
: ("changedPasswordNotificationDesc" | i18n)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/excluded-domains"
|
||||
>
|
||||
<div class="row-main">{{ "excludedDomains" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
@ -1,53 +0,0 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
|
||||
@Component({
|
||||
selector: "autofill-notification-v1-settings",
|
||||
templateUrl: "notifications-v1.component.html",
|
||||
})
|
||||
export class NotificationsSettingsV1Component implements OnInit {
|
||||
enableAddLoginNotification = false;
|
||||
enableChangedPasswordNotification = false;
|
||||
enablePasskeys = true;
|
||||
accountSwitcherEnabled = false;
|
||||
|
||||
constructor(
|
||||
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
||||
private vaultSettingsService: VaultSettingsService,
|
||||
) {
|
||||
this.accountSwitcherEnabled = enableAccountSwitching();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.enableAddLoginNotification = await firstValueFrom(
|
||||
this.userNotificationSettingsService.enableAddedLoginPrompt$,
|
||||
);
|
||||
|
||||
this.enableChangedPasswordNotification = await firstValueFrom(
|
||||
this.userNotificationSettingsService.enableChangedPasswordPrompt$,
|
||||
);
|
||||
|
||||
this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$);
|
||||
}
|
||||
|
||||
async updateAddLoginNotification() {
|
||||
await this.userNotificationSettingsService.setEnableAddedLoginPrompt(
|
||||
this.enableAddLoginNotification,
|
||||
);
|
||||
}
|
||||
|
||||
async updateChangedPasswordNotification() {
|
||||
await this.userNotificationSettingsService.setEnableChangedPasswordPrompt(
|
||||
this.enableChangedPasswordNotification,
|
||||
);
|
||||
}
|
||||
|
||||
async updateEnablePasskeys() {
|
||||
await this.vaultSettingsService.setEnablePasskeys(this.enablePasskeys);
|
||||
}
|
||||
}
|
@ -259,9 +259,7 @@ export default class RuntimeBackground {
|
||||
await this.main.refreshBadge();
|
||||
await this.main.refreshMenu(false);
|
||||
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) {
|
||||
await this.autofillService.setAutoFillOnPageLoadOrgPolicy();
|
||||
}
|
||||
await this.autofillService.setAutoFillOnPageLoadOrgPolicy();
|
||||
break;
|
||||
}
|
||||
case "addToLockedVaultPendingNotifications":
|
||||
@ -288,9 +286,7 @@ export default class RuntimeBackground {
|
||||
await this.configService.ensureConfigFetched();
|
||||
await this.main.updateOverlayCiphers();
|
||||
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) {
|
||||
await this.autofillService.setAutoFillOnPageLoadOrgPolicy();
|
||||
}
|
||||
await this.autofillService.setAutoFillOnPageLoadOrgPolicy();
|
||||
}
|
||||
break;
|
||||
case "openPopup":
|
||||
|
@ -17,7 +17,6 @@ import {
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap";
|
||||
import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap";
|
||||
import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards";
|
||||
import {
|
||||
@ -71,14 +70,10 @@ import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component"
|
||||
import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component";
|
||||
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
|
||||
import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component";
|
||||
import { Fido2Component } from "../autofill/popup/fido2/fido2.component";
|
||||
import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component";
|
||||
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
|
||||
import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-domains.component";
|
||||
import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component";
|
||||
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
|
||||
import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component";
|
||||
import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
|
||||
import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component";
|
||||
import BrowserPopupUtils from "../platform/popup/browser-popup-utils";
|
||||
@ -148,11 +143,12 @@ const routes: Routes = [
|
||||
canActivate: [unauthGuardFn(unauthRouteOverrides), unauthUiRefreshRedirect("/login")],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
},
|
||||
...extensionRefreshSwap(Fido2V1Component, Fido2Component, {
|
||||
{
|
||||
path: "fido2",
|
||||
component: Fido2Component,
|
||||
canActivate: [fido2AuthGuard],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
}),
|
||||
},
|
||||
...twofactorRefactorSwap(
|
||||
TwoFactorComponent,
|
||||
AnonLayoutWrapperComponent,
|
||||
@ -321,22 +317,24 @@ const routes: Routes = [
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 2 } satisfies RouteDataProperties,
|
||||
},
|
||||
...extensionRefreshSwap(AutofillV1Component, AutofillComponent, {
|
||||
{
|
||||
path: "autofill",
|
||||
component: AutofillComponent,
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "account-security",
|
||||
component: AccountSecurityComponent,
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
},
|
||||
...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, {
|
||||
{
|
||||
path: "notifications",
|
||||
component: NotificationsSettingsComponent,
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "vault-settings",
|
||||
component: VaultSettingsV2Component,
|
||||
@ -355,11 +353,12 @@ const routes: Routes = [
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 2 } satisfies RouteDataProperties,
|
||||
},
|
||||
...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, {
|
||||
{
|
||||
path: "excluded-domains",
|
||||
component: ExcludedDomainsComponent,
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 2 } satisfies RouteDataProperties,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "premium",
|
||||
component: PremiumV2Component,
|
||||
|
@ -35,17 +35,7 @@ import { SsoComponentV1 } from "../auth/popup/sso-v1.component";
|
||||
import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component";
|
||||
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
|
||||
import { Fido2CipherRowV1Component } from "../autofill/popup/fido2/fido2-cipher-row-v1.component";
|
||||
import { Fido2CipherRowComponent } from "../autofill/popup/fido2/fido2-cipher-row.component";
|
||||
import { Fido2UseBrowserLinkV1Component } from "../autofill/popup/fido2/fido2-use-browser-link-v1.component";
|
||||
import { Fido2UseBrowserLinkComponent } from "../autofill/popup/fido2/fido2-use-browser-link.component";
|
||||
import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component";
|
||||
import { Fido2Component } from "../autofill/popup/fido2/fido2.component";
|
||||
import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component";
|
||||
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
|
||||
import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component";
|
||||
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
|
||||
import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component";
|
||||
import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
|
||||
import { PopOutComponent } from "../platform/popup/components/pop-out.component";
|
||||
import { HeaderComponent } from "../platform/popup/header.component";
|
||||
@ -87,10 +77,6 @@ import "../platform/popup/locales";
|
||||
ScrollingModule,
|
||||
ServicesModule,
|
||||
DialogModule,
|
||||
ExcludedDomainsComponent,
|
||||
Fido2CipherRowComponent,
|
||||
Fido2Component,
|
||||
Fido2UseBrowserLinkComponent,
|
||||
FilePopoutCalloutComponent,
|
||||
AvatarModule,
|
||||
AccountComponent,
|
||||
@ -112,15 +98,11 @@ import "../platform/popup/locales";
|
||||
ColorPasswordPipe,
|
||||
ColorPasswordCountPipe,
|
||||
EnvironmentComponent,
|
||||
ExcludedDomainsV1Component,
|
||||
Fido2CipherRowV1Component,
|
||||
Fido2UseBrowserLinkV1Component,
|
||||
HintComponent,
|
||||
HomeComponent,
|
||||
LoginViaAuthRequestComponentV1,
|
||||
LoginComponentV1,
|
||||
LoginDecryptionOptionsComponentV1,
|
||||
NotificationsSettingsV1Component,
|
||||
RegisterComponent,
|
||||
SetPasswordComponent,
|
||||
SsoComponentV1,
|
||||
@ -131,8 +113,6 @@ import "../platform/popup/locales";
|
||||
UserVerificationComponent,
|
||||
VaultTimeoutInputComponent,
|
||||
RemovePasswordComponent,
|
||||
Fido2V1Component,
|
||||
AutofillV1Component,
|
||||
EnvironmentSelectorComponent,
|
||||
],
|
||||
exports: [],
|
||||
|
Loading…
Reference in New Issue
Block a user