1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-27 04:03:00 +02:00

purge vault and delete account features

This commit is contained in:
Kyle Spearrin 2018-06-21 22:40:01 -04:00
parent cb1a62ee27
commit cccd2abb55
14 changed files with 214 additions and 11 deletions

2
jslib

@ -1 +1 @@
Subproject commit dc01f0701ea7905d2da7c3babb19870e212d2337 Subproject commit 99e522a5d119c1ac6917f1013a73d90770999b5a

View File

@ -35,7 +35,9 @@ import { AccountComponent } from './settings/account.component';
import { ChangeEmailComponent } from './settings/change-email.component'; import { ChangeEmailComponent } from './settings/change-email.component';
import { ChangePasswordComponent } from './settings/change-password.component'; import { ChangePasswordComponent } from './settings/change-password.component';
import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.component'; import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.component';
import { DeleteAccountComponent } from './settings/delete-account.component';
import { ProfileComponent } from './settings/profile.component'; import { ProfileComponent } from './settings/profile.component';
import { PurgeVaultComponent } from './settings/purge-vault.component';
import { SettingsComponent } from './settings/settings.component'; import { SettingsComponent } from './settings/settings.component';
import { ExportComponent } from './tools/export.component'; import { ExportComponent } from './tools/export.component';
@ -104,6 +106,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
CiphersComponent, CiphersComponent,
CollectionsComponent, CollectionsComponent,
DeauthorizeSessionsComponent, DeauthorizeSessionsComponent,
DeleteAccountComponent,
ExportComponent, ExportComponent,
FallbackSrcDirective, FallbackSrcDirective,
FolderAddEditComponent, FolderAddEditComponent,
@ -124,6 +127,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
PasswordGeneratorComponent, PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent, PasswordGeneratorHistoryComponent,
ProfileComponent, ProfileComponent,
PurgeVaultComponent,
RegisterComponent, RegisterComponent,
SearchCiphersPipe, SearchCiphersPipe,
SettingsComponent, SettingsComponent,
@ -145,9 +149,11 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
BulkShareComponent, BulkShareComponent,
CollectionsComponent, CollectionsComponent,
DeauthorizeSessionsComponent, DeauthorizeSessionsComponent,
DeleteAccountComponent,
FolderAddEditComponent, FolderAddEditComponent,
ModalComponent, ModalComponent,
PasswordGeneratorHistoryComponent, PasswordGeneratorHistoryComponent,
PurgeVaultComponent,
ShareComponent, ShareComponent,
TwoFactorOptionsComponent, TwoFactorOptionsComponent,
], ],

View File

@ -9,7 +9,7 @@
<a class="nav-link" routerLink="/vault">{{'myVault' | i18n}}</a> <a class="nav-link" routerLink="/vault">{{'myVault' | i18n}}</a>
</li> </li>
<li class="nav-item" routerLinkActive="active"> <li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">Tools</a> <a class="nav-link" routerLink="/tools">{{'tools' | i18n}}</a>
</li> </li>
<li class="nav-item" routerLinkActive="active"> <li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/settings">{{'settings' | i18n}}</a> <a class="nav-link" routerLink="/settings">{{'settings' | i18n}}</a>

View File

@ -19,3 +19,5 @@
<button type="button" class="btn btn-outline-secondary" (click)="deleteAccount()">{{'deleteAccount' | i18n}}</button> <button type="button" class="btn btn-outline-secondary" (click)="deleteAccount()">{{'deleteAccount' | i18n}}</button>
<ng-template #deauthorizeSessionsTemplate></ng-template> <ng-template #deauthorizeSessionsTemplate></ng-template>
<ng-template #purgeVaultTemplate></ng-template>
<ng-template #deleteAccountTemplate></ng-template>

View File

@ -7,6 +7,8 @@ import {
import { ModalComponent } from '../modal.component'; import { ModalComponent } from '../modal.component';
import { DeauthorizeSessionsComponent } from './deauthorize-sessions.component'; import { DeauthorizeSessionsComponent } from './deauthorize-sessions.component';
import { DeleteAccountComponent } from './delete-account.component';
import { PurgeVaultComponent } from './purge-vault.component';
@Component({ @Component({
selector: 'app-account', selector: 'app-account',
@ -14,6 +16,8 @@ import { DeauthorizeSessionsComponent } from './deauthorize-sessions.component';
}) })
export class AccountComponent { export class AccountComponent {
@ViewChild('deauthorizeSessionsTemplate', { read: ViewContainerRef }) deauthModalRef: ViewContainerRef; @ViewChild('deauthorizeSessionsTemplate', { read: ViewContainerRef }) deauthModalRef: ViewContainerRef;
@ViewChild('purgeVaultTemplate', { read: ViewContainerRef }) purgeModalRef: ViewContainerRef;
@ViewChild('deleteAccountTemplate', { read: ViewContainerRef }) deleteModalRef: ViewContainerRef;
private modal: ModalComponent = null; private modal: ModalComponent = null;
@ -34,10 +38,30 @@ export class AccountComponent {
} }
purgeVault() { purgeVault() {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.purgeModalRef.createComponent(factory).instance;
this.modal.show<PurgeVaultComponent>(PurgeVaultComponent, this.purgeModalRef);
this.modal.onClosed.subscribe(async () => {
this.modal = null;
});
} }
deleteAccount() { deleteAccount() {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.deleteModalRef.createComponent(factory).instance;
this.modal.show<DeleteAccountComponent>(DeleteAccountComponent, this.deleteModalRef);
this.modal.onClosed.subscribe(async () => {
this.modal = null;
});
} }
} }

View File

@ -18,9 +18,9 @@
appAutoFocus> appAutoFocus>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button appBlurClick type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button appBlurClick type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
<span>{{'submit' | i18n}}</span> <span>{{'deauthorize' | i18n}}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button> <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div> </div>

View File

@ -1,6 +1,4 @@
import { import { Component } from '@angular/core';
Component,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2'; import { Angulartics2 } from 'angulartics2';

View File

@ -0,0 +1,29 @@
<div class="modal fade">
<div class="modal-dialog">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h2 class="modal-title">{{'deleteAccount' | i18n}}</h2>
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{'deleteAccountDesc' | i18n}}</p>
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">{{'warning' | i18n}}</h4>
<p class="mb-0">{{'deleteAccountWarning' | i18n}}</p>
</div>
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control" [(ngModel)]="masterPassword" required
appAutofocus>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin"></i>
<span>{{'deleteAccount' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,43 @@
import { Component } from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest';
@Component({
selector: 'app-delete-account',
templateUrl: 'delete-account.component.html',
})
export class DeleteAccountComponent {
masterPassword: string;
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private cryptoService: CryptoService, private messagingService: MessagingService) { }
async submit() {
if (this.masterPassword == null || this.masterPassword === '') {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return;
}
const request = new PasswordVerificationRequest();
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
try {
this.formPromise = this.apiService.postDeleteAccount(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Deleted Account' });
this.toasterService.popAsync('success', this.i18nService.t('accountDeleted'),
this.i18nService.t('accountDeletedDesc'));
this.messagingService.send('logout');
} catch { }
}
}

View File

@ -0,0 +1,29 @@
<div class="modal fade">
<div class="modal-dialog">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h2 class="modal-title">{{'purgeVault' | i18n}}</h2>
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{'purgeVaultDesc' | i18n}}</p>
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">{{'warning' | i18n}}</h4>
<p class="mb-0">{{'purgeVaultWarning' | i18n}}</p>
</div>
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control" [(ngModel)]="masterPassword" required
appAutofocus>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin"></i>
<span>{{'purgeVault' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,42 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest';
@Component({
selector: 'app-purge-vault',
templateUrl: 'purge-vault.component.html',
})
export class PurgeVaultComponent {
masterPassword: string;
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private cryptoService: CryptoService, private router: Router) { }
async submit() {
if (this.masterPassword == null || this.masterPassword === '') {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return;
}
const request = new PasswordVerificationRequest();
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
try {
this.formPromise = this.apiService.postPurgeCiphers(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Purged Vault' });
this.toasterService.popAsync('success', null, this.i18nService.t('vaultPurged'));
this.router.navigate(['vault']);
} catch { }
}
}

View File

@ -14,6 +14,6 @@
</div> </div>
</div> </div>
<button appBlurClick type="submit" class="btn btn-primary"> <button appBlurClick type="submit" class="btn btn-primary">
{{'submit' | i18n}} {{'export' | i18n}}
</button> </button>
</form> </form>

View File

@ -2,16 +2,16 @@
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
<div class="card"> <div class="card">
<div class="card-header">Tools</div> <div class="card-header">{{'tools' | i18n}}</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a routerLink="generator" class="list-group-item" routerLinkActive="active"> <a routerLink="generator" class="list-group-item" routerLinkActive="active">
Password Generator {{'passwordGenerator' | i18n}}
</a> </a>
<a routerLink="import" class="list-group-item" routerLinkActive="active"> <a routerLink="import" class="list-group-item" routerLinkActive="active">
Import Import
</a> </a>
<a routerLink="export" class="list-group-item" routerLinkActive="active"> <a routerLink="export" class="list-group-item" routerLinkActive="active">
Export {{'exportVault' | i18n}}
</a> </a>
</div> </div>
</div> </div>

View File

@ -733,6 +733,9 @@
"exportVault": { "exportVault": {
"message": "Export Vault" "message": "Export Vault"
}, },
"export": {
"message": "Export"
},
"exportSuccess": { "exportSuccess": {
"message": "Your vault data has been exported." "message": "Your vault data has been exported."
}, },
@ -818,6 +821,9 @@
"deauthorizeSessions": { "deauthorizeSessions": {
"message": "Deauthorize Sessions" "message": "Deauthorize Sessions"
}, },
"deauthorize": {
"message": "Deauthorize"
},
"deauthorizeSessionsDesc": { "deauthorizeSessionsDesc": {
"message": "Concerned your account is logged in on another device? Proceed below to deauthorize all computers or devices that you have previously used. This security step is recommended if you previously used a public PC or accidentally saved your password on a device that isn't yours. This step will also clear all previously remembered two-step login sessions." "message": "Concerned your account is logged in on another device? Proceed below to deauthorize all computers or devices that you have previously used. This security step is recommended if you previously used a public PC or accidentally saved your password on a device that isn't yours. This step will also clear all previously remembered two-step login sessions."
}, },
@ -830,10 +836,34 @@
"purgeVault": { "purgeVault": {
"message": "Purge Vault" "message": "Purge Vault"
}, },
"purgeVaultDesc": {
"message": "Proceed below to delete all items and folders in your vault. Items that belong to an organization that you share with will not be deleted."
},
"purgeVaultWarning": {
"message": "Purging your vault is permanent. It cannot be undone."
},
"vaultPurged": {
"message": "Your vault has been purged."
},
"deleteAccount": { "deleteAccount": {
"message": "Delete Account" "message": "Delete Account"
}, },
"deleteAccountDesc": {
"message": "Proceed below to delete your account and all associated data."
},
"deleteAccountWarning": {
"message": "Deleting your account is permanent. It cannot be undone."
},
"accountDeleted": {
"message": "Account Deleted"
},
"accountDeletedDesc": {
"message": "Your account has been closed and all associated data has been deleted."
},
"myAccount": { "myAccount": {
"message": "My Account" "message": "My Account"
},
"tools": {
"message": "Tools"
} }
} }