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

View File

@ -9,7 +9,7 @@
<a class="nav-link" routerLink="/vault">{{'myVault' | i18n}}</a>
</li>
<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 class="nav-item" routerLinkActive="active">
<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>
<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 { DeauthorizeSessionsComponent } from './deauthorize-sessions.component';
import { DeleteAccountComponent } from './delete-account.component';
import { PurgeVaultComponent } from './purge-vault.component';
@Component({
selector: 'app-account',
@ -14,6 +16,8 @@ import { DeauthorizeSessionsComponent } from './deauthorize-sessions.component';
})
export class AccountComponent {
@ViewChild('deauthorizeSessionsTemplate', { read: ViewContainerRef }) deauthModalRef: ViewContainerRef;
@ViewChild('purgeVaultTemplate', { read: ViewContainerRef }) purgeModalRef: ViewContainerRef;
@ViewChild('deleteAccountTemplate', { read: ViewContainerRef }) deleteModalRef: ViewContainerRef;
private modal: ModalComponent = null;
@ -34,10 +38,30 @@ export class AccountComponent {
}
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() {
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>
</div>
<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>
<span>{{'submit' | i18n}}</span>
<span>{{'deauthorize' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>

View File

@ -1,6 +1,4 @@
import {
Component,
} from '@angular/core';
import { Component } from '@angular/core';
import { ToasterService } from 'angular2-toaster';
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>
<button appBlurClick type="submit" class="btn btn-primary">
{{'submit' | i18n}}
{{'export' | i18n}}
</button>
</form>

View File

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

View File

@ -733,6 +733,9 @@
"exportVault": {
"message": "Export Vault"
},
"export": {
"message": "Export"
},
"exportSuccess": {
"message": "Your vault data has been exported."
},
@ -818,6 +821,9 @@
"deauthorizeSessions": {
"message": "Deauthorize Sessions"
},
"deauthorize": {
"message": "Deauthorize"
},
"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."
},
@ -830,10 +836,34 @@
"purgeVault": {
"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": {
"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": {
"message": "My Account"
},
"tools": {
"message": "Tools"
}
}