1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-30 13:03:53 +01:00

add settings -> Excluded Domains component

Provides a UI to edit the domains for which Bitwarden does not offer
to save login details.
This commit is contained in:
Tom Rittson 2020-08-31 17:14:50 +10:00 committed by Thomas Rittson
parent 7e41e84c7d
commit da636e26c2
6 changed files with 186 additions and 0 deletions

View File

@ -1437,5 +1437,14 @@
}, },
"personalOwnershipPolicyInEffect": { "personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options." "message": "An organization policy is affecting your ownership options."
},
"excludedDomains": {
"message": "Excluded Domains"
},
"excludedDomainsDesc": {
"message": "Bitwarden will not ask to save login details for these domains."
},
"excludedDomainsInvalidDomain": {
"message": " is not a valid domain"
} }
} }

View File

@ -40,6 +40,7 @@ import { GroupingsComponent } from './vault/groupings.component';
import { PasswordHistoryComponent } from './vault/password-history.component'; import { PasswordHistoryComponent } from './vault/password-history.component';
import { ShareComponent } from './vault/share.component'; import { ShareComponent } from './vault/share.component';
import { ViewComponent } from './vault/view.component'; import { ViewComponent } from './vault/view.component';
import { ExcludedDomainsComponent } from './settings/excluded-domains.component';
const routes: Routes = [ const routes: Routes = [
{ {
@ -200,6 +201,12 @@ const routes: Routes = [
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'sync' }, data: { state: 'sync' },
}, },
{
path: 'excluded-domains',
component: ExcludedDomainsComponent,
canActivate: [AuthGuardService],
data: { state: 'excluded-domains' },
},
{ {
path: 'premium', path: 'premium',
component: PremiumComponent, component: PremiumComponent,

View File

@ -46,6 +46,7 @@ import { GroupingsComponent } from './vault/groupings.component';
import { PasswordHistoryComponent } from './vault/password-history.component'; import { PasswordHistoryComponent } from './vault/password-history.component';
import { ShareComponent } from './vault/share.component'; import { ShareComponent } from './vault/share.component';
import { ViewComponent } from './vault/view.component'; import { ViewComponent } from './vault/view.component';
import { ExcludedDomainsComponent } from './settings/excluded-domains.component';
import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive'; import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive'; import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
@ -181,6 +182,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
ColorPasswordPipe, ColorPasswordPipe,
CurrentTabComponent, CurrentTabComponent,
EnvironmentComponent, EnvironmentComponent,
ExcludedDomainsComponent,
ExportComponent, ExportComponent,
FallbackSrcDirective, FallbackSrcDirective,
FolderAddEditComponent, FolderAddEditComponent,

View File

@ -0,0 +1,53 @@
<form #form (ngSubmit)="submit()">
<header>
<div class="left">
<a routerLink="/tabs/settings">{{'cancel' | i18n}}</a>
</div>
<div class="center">
<span class="title">{{'excludedDomains' | i18n}}</span>
</div>
<div class="right">
<button type="submit" appBlurClick>{{'save' | i18n}}</button>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<ng-container *ngIf="excludedDomains">
<div class="box-content-row box-content-row-multi" appBoxRow
*ngFor="let domain of excludedDomains; let i = index; trackBy:trackByFunction">
<a href="#" appStopClick (click)="removeUri(i)" appA11yTitle="{{'remove' | i18n}}">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
</a>
<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">
<a *ngIf="currentUris && currentUris.length" class="row-btn" href="#" appStopClick
appBlurClick appA11yTitle="{{'toggleCurrentUris' | i18n}}" (click)="toggleUriInput(domain)">
<i aria-hidden="true" class="fa fa-lg fa-list"></i>
</a>
</div>
</div>
</ng-container>
<a href="#" appStopClick appBlurClick (click)="addUri()"
class="box-content-row box-content-row-newmulti">
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i> {{'newUri' | i18n}}
</a>
</div>
<div class="box-footer">
{{'excludedDomainsDesc' | i18n}}
</div>
</div>
</content>
</form>

View File

@ -0,0 +1,111 @@
import {
Component,
OnInit,
NgZone,
OnDestroy
} from '@angular/core';
import { Router } from '@angular/router';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { ConstantsService } from 'jslib/services/constants.service';
import { BrowserApi } from '../../browser/browserApi';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { Utils } from 'jslib/misc/utils';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
interface ExcludedDomain {
uri: string,
showCurrentUris: boolean
}
const BroadcasterSubscriptionId = 'excludedDomains';
@Component({
selector: 'app-excluded-domains',
templateUrl: 'excluded-domains.component.html',
})
export class ExcludedDomainsComponent implements OnInit, OnDestroy {
excludedDomains: ExcludedDomain[] = [];
currentUris: string[];
loadCurrentUrisTimeout: number;
constructor(private storageService: StorageService,
private i18nService: I18nService, private router: Router,
private broadcasterService: BroadcasterService, private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService) {
}
async ngOnInit() {
const savedDomains = await this.storageService.get<any>(ConstantsService.neverDomainsKey);
if (savedDomains) {
for (const uri of Object.keys(savedDomains)) {
this.excludedDomains.push({uri: uri, showCurrentUris: false})
}
}
this.loadCurrentUris();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'tabChanged':
case 'windowChanged':
if (this.loadCurrentUrisTimeout != null) {
window.clearTimeout(this.loadCurrentUrisTimeout);
}
this.loadCurrentUrisTimeout = window.setTimeout(() => 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} = {};
for (const domain of this.excludedDomains) {
if (domain.uri && domain.uri !== '') {
const validDomain = Utils.getHostname(domain.uri);
if (!validDomain) {
this.platformUtilsService.showToast('error', null,
'\'' + domain.uri + '\'' + this.i18nService.t('excludedDomainsInvalidDomain'));
return;
}
savedDomains[validDomain] = null
}
}
await this.storageService.save(ConstantsService.neverDomainsKey, savedDomains);
this.router.navigate(['/tabs/settings']);
}
trackByFunction(index: number, item: any) {
return index;
}
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);
}
}
}

View File

@ -19,6 +19,10 @@
<div class="row-main">{{'sync' | i18n}}</div> <div class="row-main">{{'sync' | i18n}}</div>
<i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i> <i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i>
</a> </a>
<a class="box-content-row box-content-row-flex text-default" routerLink="/excluded-domains">
<div class="row-main">{{'excludedDomains' | i18n}}</div>
<i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i>
</a>
</div> </div>
</div> </div>
<div class="box list"> <div class="box list">