From 86226990ee226b42d669347c8a960077d4d16762 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 5 Apr 2018 23:50:06 -0400 Subject: [PATCH] move component code out to jslib --- jslib | 2 +- src/app/vault/add-edit.component.ts | 267 +----------------- src/app/vault/attachments.component.ts | 122 +------- .../password-generator-history.component.ts | 34 +-- src/app/vault/password-generator.component.ts | 123 +------- src/app/vault/view.component.html | 2 +- src/app/vault/view.component.ts | 184 +----------- src/locales/en/messages.json | 3 + 8 files changed, 54 insertions(+), 683 deletions(-) diff --git a/jslib b/jslib index 22f0f97cda0..db83cab5522 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 22f0f97cda0286859ceb889b9c80b9b5bb88affa +Subproject commit db83cab552296c609bc3dc6841070ec7fa231818 diff --git a/src/app/vault/add-edit.component.ts b/src/app/vault/add-edit.component.ts index 1a2801c478a..722edaebcce 100644 --- a/src/app/vault/add-edit.component.ts +++ b/src/app/vault/add-edit.component.ts @@ -2,283 +2,34 @@ import * as template from './add-edit.component.html'; import { Component, - EventEmitter, - Input, OnChanges, - Output, } from '@angular/core'; import { ToasterService } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; -import { CipherType } from 'jslib/enums/cipherType'; -import { FieldType } from 'jslib/enums/fieldType'; -import { SecureNoteType } from 'jslib/enums/secureNoteType'; -import { UriMatchType } from 'jslib/enums/uriMatchType'; - import { AuditService } from 'jslib/abstractions/audit.service'; import { CipherService } from 'jslib/abstractions/cipher.service'; import { FolderService } from 'jslib/abstractions/folder.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; -import { CardView } from 'jslib/models/view/cardView'; -import { CipherView } from 'jslib/models/view/cipherView'; -import { FieldView } from 'jslib/models/view/fieldView'; -import { FolderView } from 'jslib/models/view/folderView'; -import { IdentityView } from 'jslib/models/view/identityView'; -import { LoginUriView } from 'jslib/models/view/loginUriView'; -import { LoginView } from 'jslib/models/view/loginView'; -import { SecureNoteView } from 'jslib/models/view/secureNoteView'; +import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/add-edit.component'; @Component({ selector: 'app-vault-add-edit', template: template, }) -export class AddEditComponent implements OnChanges { - @Input() folderId: string; - @Input() cipherId: string; - @Input() type: CipherType; - @Output() onSavedCipher = new EventEmitter(); - @Output() onDeletedCipher = new EventEmitter(); - @Output() onCancelled = new EventEmitter(); - @Output() onEditAttachments = new EventEmitter(); - @Output() onGeneratePassword = new EventEmitter(); - - editMode: boolean = false; - cipher: CipherView; - folders: FolderView[]; - title: string; - formPromise: Promise; - deletePromise: Promise; - checkPasswordPromise: Promise; - showPassword: boolean = false; - cipherType = CipherType; - fieldType = FieldType; - addFieldType: FieldType = FieldType.Text; - typeOptions: any[]; - cardBrandOptions: any[]; - cardExpMonthOptions: any[]; - identityTitleOptions: any[]; - addFieldTypeOptions: any[]; - uriMatchOptions: any[]; - - constructor(private cipherService: CipherService, private folderService: FolderService, - private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private analytics: Angulartics2, private toasterService: ToasterService, - private auditService: AuditService) { - this.typeOptions = [ - { name: i18nService.t('typeLogin'), value: CipherType.Login }, - { name: i18nService.t('typeCard'), value: CipherType.Card }, - { name: i18nService.t('typeIdentity'), value: CipherType.Identity }, - { name: i18nService.t('typeSecureNote'), value: CipherType.SecureNote }, - ]; - this.cardBrandOptions = [ - { name: '-- ' + i18nService.t('select') + ' --', value: null }, - { name: 'Visa', value: 'Visa' }, - { name: 'Mastercard', value: 'Mastercard' }, - { name: 'American Express', value: 'Amex' }, - { name: 'Discover', value: 'Discover' }, - { name: 'Diners Club', value: 'Diners Club' }, - { name: 'JCB', value: 'JCB' }, - { name: 'Maestro', value: 'Maestro' }, - { name: 'UnionPay', value: 'UnionPay' }, - { name: i18nService.t('other'), value: 'Other' }, - ]; - this.cardExpMonthOptions = [ - { name: '-- ' + i18nService.t('select') + ' --', value: null }, - { name: '01 - ' + i18nService.t('january'), value: '1' }, - { name: '02 - ' + i18nService.t('february'), value: '2' }, - { name: '03 - ' + i18nService.t('march'), value: '3' }, - { name: '04 - ' + i18nService.t('april'), value: '4' }, - { name: '05 - ' + i18nService.t('may'), value: '5' }, - { name: '06 - ' + i18nService.t('june'), value: '6' }, - { name: '07 - ' + i18nService.t('july'), value: '7' }, - { name: '08 - ' + i18nService.t('august'), value: '8' }, - { name: '09 - ' + i18nService.t('september'), value: '9' }, - { name: '10 - ' + i18nService.t('october'), value: '10' }, - { name: '11 - ' + i18nService.t('november'), value: '11' }, - { name: '12 - ' + i18nService.t('december'), value: '12' }, - ]; - this.identityTitleOptions = [ - { name: '-- ' + i18nService.t('select') + ' --', value: null }, - { name: i18nService.t('mr'), value: i18nService.t('mr') }, - { name: i18nService.t('mrs'), value: i18nService.t('mrs') }, - { name: i18nService.t('ms'), value: i18nService.t('ms') }, - { name: i18nService.t('dr'), value: i18nService.t('dr') }, - ]; - this.addFieldTypeOptions = [ - { name: i18nService.t('cfTypeText'), value: FieldType.Text }, - { name: i18nService.t('cfTypeHidden'), value: FieldType.Hidden }, - { name: i18nService.t('cfTypeBoolean'), value: FieldType.Boolean }, - ]; - this.uriMatchOptions = [ - { name: i18nService.t('defaultMatchDetection'), value: null }, - { name: i18nService.t('baseDomain'), value: UriMatchType.Domain }, - { name: i18nService.t('host'), value: UriMatchType.Host }, - { name: i18nService.t('startsWith'), value: UriMatchType.StartsWith }, - { name: i18nService.t('regEx'), value: UriMatchType.RegularExpression }, - { name: i18nService.t('exact'), value: UriMatchType.Exact }, - { name: i18nService.t('never'), value: UriMatchType.Never }, - ]; +export class AddEditComponent extends BaseAddEditComponent implements OnChanges { + constructor(cipherService: CipherService, folderService: FolderService, + i18nService: I18nService, platformUtilsService: PlatformUtilsService, + analytics: Angulartics2, toasterService: ToasterService, + auditService: AuditService) { + super(cipherService, folderService, i18nService, platformUtilsService, analytics, + toasterService, auditService); } async ngOnChanges() { - this.editMode = this.cipherId != null; - - if (this.editMode) { - this.editMode = true; - this.title = this.i18nService.t('editItem'); - const cipher = await this.cipherService.get(this.cipherId); - this.cipher = await cipher.decrypt(); - } else { - this.title = this.i18nService.t('addItem'); - this.cipher = new CipherView(); - this.cipher.folderId = this.folderId; - this.cipher.type = this.type == null ? CipherType.Login : this.type; - this.cipher.login = new LoginView(); - this.cipher.login.uris = [new LoginUriView()]; - this.cipher.card = new CardView(); - this.cipher.identity = new IdentityView(); - this.cipher.secureNote = new SecureNoteView(); - this.cipher.secureNote.type = SecureNoteType.Generic; - } - - this.folders = await this.folderService.getAllDecrypted(); - } - - async submit() { - if (this.cipher.name == null || this.cipher.name === '') { - this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), - this.i18nService.t('nameRequired')); - return; - } - - const cipher = await this.cipherService.encrypt(this.cipher); - - try { - this.formPromise = this.cipherService.saveWithServer(cipher); - await this.formPromise; - this.cipher.id = cipher.id; - this.analytics.eventTrack.next({ action: this.editMode ? 'Edited Cipher' : 'Added Cipher' }); - this.toasterService.popAsync('success', null, - this.i18nService.t(this.editMode ? 'editedItem' : 'addedItem')); - this.onSavedCipher.emit(this.cipher); - } catch { } - } - - addUri() { - if (this.cipher.type !== CipherType.Login) { - return; - } - - if (this.cipher.login.uris == null) { - this.cipher.login.uris = []; - } - - this.cipher.login.uris.push(new LoginUriView()); - } - - removeUri(uri: LoginUriView) { - if (this.cipher.type !== CipherType.Login || this.cipher.login.uris == null) { - return; - } - - const i = this.cipher.login.uris.indexOf(uri); - if (i > -1) { - this.cipher.login.uris.splice(i, 1); - } - } - - addField() { - if (this.cipher.fields == null) { - this.cipher.fields = []; - } - - const f = new FieldView(); - f.type = this.addFieldType; - this.cipher.fields.push(f); - } - - removeField(field: FieldView) { - const i = this.cipher.fields.indexOf(field); - if (i > -1) { - this.cipher.fields.splice(i, 1); - } - } - - cancel() { - this.onCancelled.emit(this.cipher); - } - - attachments() { - this.onEditAttachments.emit(this.cipher); - } - - async delete() { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('deleteItemConfirmation'), this.i18nService.t('deleteItem'), - this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); - if (!confirmed) { - return; - } - - try { - this.deletePromise = this.cipherService.deleteWithServer(this.cipher.id); - await this.deletePromise; - this.analytics.eventTrack.next({ action: 'Deleted Cipher' }); - this.toasterService.popAsync('success', null, this.i18nService.t('deletedItem')); - this.onDeletedCipher.emit(this.cipher); - } catch { } - } - - async generatePassword() { - if (this.cipher.login != null && this.cipher.login.password != null && this.cipher.login.password.length) { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('overwritePasswordConfirmation'), this.i18nService.t('overwritePassword'), - this.i18nService.t('yes'), this.i18nService.t('no')); - if (!confirmed) { - return; - } - } - - this.onGeneratePassword.emit(); - } - - togglePassword() { - this.analytics.eventTrack.next({ action: 'Toggled Password on Edit' }); - this.showPassword = !this.showPassword; - document.getElementById('loginPassword').focus(); - } - - toggleFieldValue(field: FieldView) { - const f = (field as any); - f.showValue = !f.showValue; - } - - toggleUriOptions(uri: LoginUriView) { - const u = (uri as any); - u.showOptions = u.showOptions == null && uri.match != null ? false : !u.showOptions; - } - - loginUriMatchChanged(uri: LoginUriView) { - const u = (uri as any); - u.showOptions = u.showOptions == null ? true : u.showOptions; - } - - async checkPassword() { - if (this.cipher.login == null || this.cipher.login.password == null || this.cipher.login.password === '') { - return; - } - - this.analytics.eventTrack.next({ action: 'Check Password' }); - this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password); - const matches = await this.checkPasswordPromise; - - if (matches > 0) { - this.toasterService.popAsync('warning', null, this.i18nService.t('passwordExposed', matches.toString())); - } else { - this.toasterService.popAsync('success', null, this.i18nService.t('passwordSafe')); - } + await super.load(); } } diff --git a/src/app/vault/attachments.component.ts b/src/app/vault/attachments.component.ts index ec3a56f49c9..62324573e57 100644 --- a/src/app/vault/attachments.component.ts +++ b/src/app/vault/attachments.component.ts @@ -1,12 +1,6 @@ import * as template from './attachments.component.html'; -import { - Component, - EventEmitter, - Input, - OnInit, - Output, -} from '@angular/core'; +import { Component } from '@angular/core'; import { ToasterService } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; @@ -17,116 +11,18 @@ import { I18nService } from 'jslib/abstractions/i18n.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { TokenService } from 'jslib/abstractions/token.service'; -import { Cipher } from 'jslib/models/domain/cipher'; - -import { AttachmentView } from 'jslib/models/view/attachmentView'; -import { CipherView } from 'jslib/models/view/cipherView'; +import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib/angular/components/attachments.component'; @Component({ selector: 'app-vault-attachments', template: template, }) -export class AttachmentsComponent implements OnInit { - @Input() cipherId: string; - - cipher: CipherView; - cipherDomain: Cipher; - hasUpdatedKey: boolean; - canAccessAttachments: boolean; - formPromise: Promise; - deletePromises: { [id: string]: Promise; } = {}; - - constructor(private cipherService: CipherService, private analytics: Angulartics2, - private toasterService: ToasterService, private i18nService: I18nService, - private cryptoService: CryptoService, private tokenService: TokenService, - private platformUtilsService: PlatformUtilsService) { } - - async ngOnInit() { - this.cipherDomain = await this.cipherService.get(this.cipherId); - this.cipher = await this.cipherDomain.decrypt(); - - const key = await this.cryptoService.getEncKey(); - this.hasUpdatedKey = key != null; - const isPremium = this.tokenService.getPremium(); - this.canAccessAttachments = isPremium || this.cipher.organizationId != null; - - if (!this.canAccessAttachments) { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'), - this.i18nService.t('learnMore'), this.i18nService.t('cancel')); - if (confirmed) { - this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=purchase'); - } - } else if (!this.hasUpdatedKey) { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('updateKey'), this.i18nService.t('featureUnavailable'), - this.i18nService.t('learnMore'), this.i18nService.t('cancel'), 'warning'); - if (confirmed) { - this.platformUtilsService.launchUri('https://help.bitwarden.com/article/update-encryption-key/'); - } - } - } - - async submit() { - if (!this.hasUpdatedKey) { - this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), - this.i18nService.t('updateKey')); - return; - } - - const fileEl = document.getElementById('file') as HTMLInputElement; - const files = fileEl.files; - if (files == null || files.length === 0) { - this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), - this.i18nService.t('selectFile')); - return; - } - - if (files[0].size > 104857600) { // 100 MB - this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), - this.i18nService.t('maxFileSize')); - return; - } - - try { - this.formPromise = this.cipherService.saveAttachmentWithServer(this.cipherDomain, files[0]); - this.cipherDomain = await this.formPromise; - this.cipher = await this.cipherDomain.decrypt(); - this.analytics.eventTrack.next({ action: 'Added Attachment' }); - this.toasterService.popAsync('success', null, this.i18nService.t('attachmentSaved')); - } catch { } - - // reset file input - // ref: https://stackoverflow.com/a/20552042 - fileEl.type = ''; - fileEl.type = 'file'; - fileEl.value = ''; - } - - async delete(attachment: AttachmentView) { - if (this.deletePromises[attachment.id] != null) { - return; - } - - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('deleteAttachmentConfirmation'), this.i18nService.t('deleteAttachment'), - this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); - if (!confirmed) { - return; - } - - try { - this.deletePromises[attachment.id] = this.cipherService.deleteAttachmentWithServer( - this.cipher.id, attachment.id); - await this.deletePromises[attachment.id]; - this.analytics.eventTrack.next({ action: 'Deleted Attachment' }); - this.toasterService.popAsync('success', null, this.i18nService.t('deletedAttachment')); - const i = this.cipher.attachments.indexOf(attachment); - if (i > -1) { - this.cipher.attachments.splice(i, 1); - } - } catch { } - - this.deletePromises[attachment.id] = null; +export class AttachmentsComponent extends BaseAttachmentsComponent { + constructor(cipherService: CipherService, analytics: Angulartics2, + toasterService: ToasterService, i18nService: I18nService, + cryptoService: CryptoService, tokenService: TokenService, + platformUtilsService: PlatformUtilsService) { + super(cipherService, analytics, toasterService, i18nService, cryptoService, tokenService, + platformUtilsService); } } diff --git a/src/app/vault/password-generator-history.component.ts b/src/app/vault/password-generator-history.component.ts index f3591688698..90144ee5611 100644 --- a/src/app/vault/password-generator-history.component.ts +++ b/src/app/vault/password-generator-history.component.ts @@ -3,40 +3,24 @@ import * as template from './password-generator-history.component.html'; import { ToasterService } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; -import { - Component, - OnInit, -} from '@angular/core'; +import { Component } from '@angular/core'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; -import { PasswordHistory } from 'jslib/models/domain/passwordHistory'; +import { + PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent +} from 'jslib/angular/components/password-generator-history.component'; @Component({ selector: 'app-password-generator-history', template: template, }) -export class PasswordGeneratorHistoryComponent implements OnInit { - history: PasswordHistory[] = []; - - constructor(private passwordGenerationService: PasswordGenerationService, private analytics: Angulartics2, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, - private toasterService: ToasterService) { } - - async ngOnInit() { - this.history = await this.passwordGenerationService.getHistory(); - } - - clear() { - this.history = []; - this.passwordGenerationService.clear(); - } - - copy(password: string) { - this.analytics.eventTrack.next({ action: 'Copied Historical Password' }); - this.platformUtilsService.copyToClipboard(password); - this.toasterService.popAsync('info', null, this.i18nService.t('valueCopied', this.i18nService.t('password'))); +export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { + constructor(passwordGenerationService: PasswordGenerationService, analytics: Angulartics2, + platformUtilsService: PlatformUtilsService, i18nService: I18nService, + toasterService: ToasterService) { + super(passwordGenerationService, analytics, platformUtilsService, i18nService, toasterService); } } diff --git a/src/app/vault/password-generator.component.ts b/src/app/vault/password-generator.component.ts index 2a1a991459f..33612c73e0e 100644 --- a/src/app/vault/password-generator.component.ts +++ b/src/app/vault/password-generator.component.ts @@ -6,128 +6,27 @@ import { Angulartics2 } from 'angulartics2'; import { ChangeDetectorRef, Component, - EventEmitter, - Input, NgZone, - OnInit, - Output, } from '@angular/core'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { + PasswordGeneratorComponent as BasePasswordGeneratorComponent +} from 'jslib/angular/components/password-generator.component'; + @Component({ selector: 'app-password-generator', template: template, }) -export class PasswordGeneratorComponent implements OnInit { - @Input() showSelect: boolean = false; - @Output() onSelected = new EventEmitter(); - - options: any = {}; - password: string = '-'; - showOptions = false; - avoidAmbiguous = false; - - constructor(private passwordGenerationService: PasswordGenerationService, private analytics: Angulartics2, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, - private toasterService: ToasterService, private ngZone: NgZone, - private changeDetectorRef: ChangeDetectorRef) { } - - async ngOnInit() { - this.options = await this.passwordGenerationService.getOptions(); - this.avoidAmbiguous = !this.options.ambiguous; - this.password = this.passwordGenerationService.generatePassword(this.options); - this.analytics.eventTrack.next({ action: 'Generated Password' }); - await this.passwordGenerationService.addHistory(this.password); - } - - async sliderChanged() { - this.saveOptions(false); - await this.passwordGenerationService.addHistory(this.password); - this.analytics.eventTrack.next({ action: 'Regenerated Password' }); - } - - async sliderInput() { - this.normalizeOptions(); - this.password = this.passwordGenerationService.generatePassword(this.options); - } - - async saveOptions(regenerate: boolean = true) { - this.normalizeOptions(); - await this.passwordGenerationService.saveOptions(this.options); - - if (regenerate) { - await this.regenerate(); - } - } - - async regenerate() { - this.password = this.passwordGenerationService.generatePassword(this.options); - await this.passwordGenerationService.addHistory(this.password); - this.analytics.eventTrack.next({ action: 'Regenerated Password' }); - } - - copy() { - this.analytics.eventTrack.next({ action: 'Copied Generated Password' }); - this.platformUtilsService.copyToClipboard(this.password); - this.toasterService.popAsync('info', null, this.i18nService.t('valueCopied', this.i18nService.t('password'))); - } - - select() { - this.analytics.eventTrack.next({ action: 'Selected Generated Password' }); - this.onSelected.emit(this.password); - } - - toggleOptions() { - this.showOptions = !this.showOptions; - } - - private normalizeOptions() { - this.options.minLowercase = 0; - this.options.minUppercase = 0; - this.options.ambiguous = !this.avoidAmbiguous; - - if (!this.options.uppercase && !this.options.lowercase && !this.options.number && !this.options.special) { - this.options.lowercase = true; - const lowercase = document.querySelector('#lowercase') as HTMLInputElement; - if (lowercase) { - lowercase.checked = true; - } - } - - if (!this.options.length) { - this.options.length = 5; - } else if (this.options.length > 128) { - this.options.length = 128; - } - - if (!this.options.minNumber) { - this.options.minNumber = 0; - } else if (this.options.minNumber > this.options.length) { - this.options.minNumber = this.options.length; - } else if (this.options.minNumber > 9) { - this.options.minNumber = 9; - } - - if (!this.options.minSpecial) { - this.options.minSpecial = 0; - } else if (this.options.minSpecial > this.options.length) { - this.options.minSpecial = this.options.length; - } else if (this.options.minSpecial > 9) { - this.options.minSpecial = 9; - } - - if (this.options.minSpecial + this.options.minNumber > this.options.length) { - this.options.minSpecial = this.options.length - this.options.minNumber; - } - } - - private functionWithChangeDetection(func: Function) { - this.ngZone.run(async () => { - func(); - this.changeDetectorRef.detectChanges(); - }); +export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent { + constructor(passwordGenerationService: PasswordGenerationService, analytics: Angulartics2, + platformUtilsService: PlatformUtilsService, i18nService: I18nService, + toasterService: ToasterService, ngZone: NgZone, + changeDetectorRef: ChangeDetectorRef) { + super(passwordGenerationService, analytics, platformUtilsService, i18nService, + toasterService, ngZone, changeDetectorRef); } } diff --git a/src/app/vault/view.component.html b/src/app/vault/view.component.html index 894718c82cc..3833ace98a5 100644 --- a/src/app/vault/view.component.html +++ b/src/app/vault/view.component.html @@ -173,7 +173,7 @@ *ngIf="u.canLaunch" (click)="launch(u)"> - diff --git a/src/app/vault/view.component.ts b/src/app/vault/view.component.ts index c323233b071..46bf66cf81d 100644 --- a/src/app/vault/view.component.ts +++ b/src/app/vault/view.component.ts @@ -2,19 +2,12 @@ import * as template from './view.component.html'; import { Component, - EventEmitter, - Input, OnChanges, - OnDestroy, - Output, } from '@angular/core'; import { ToasterService } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; -import { CipherType } from 'jslib/enums/cipherType'; -import { FieldType } from 'jslib/enums/fieldType'; - import { AuditService } from 'jslib/abstractions/audit.service'; import { CipherService } from 'jslib/abstractions/cipher.service'; import { CryptoService } from 'jslib/abstractions/crypto.service'; @@ -23,178 +16,23 @@ import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { TokenService } from 'jslib/abstractions/token.service'; import { TotpService } from 'jslib/abstractions/totp.service'; -import { AttachmentView } from 'jslib/models/view/attachmentView'; -import { CipherView } from 'jslib/models/view/cipherView'; -import { FieldView } from 'jslib/models/view/fieldView'; -import { LoginUriView } from 'jslib/models/view/loginUriView'; +import { ViewComponent as BaseViewComponent } from 'jslib/angular/components/view.component'; @Component({ selector: 'app-vault-view', template: template, }) -export class ViewComponent implements OnChanges, OnDestroy { - @Input() cipherId: string; - @Output() onEditCipher = new EventEmitter(); - cipher: CipherView; - showPassword: boolean; - isPremium: boolean; - totpCode: string; - totpCodeFormatted: string; - totpDash: number; - totpSec: number; - totpLow: boolean; - fieldType = FieldType; - checkPasswordPromise: Promise; - - private totpInterval: any; - - constructor(private cipherService: CipherService, private totpService: TotpService, - private tokenService: TokenService, private toasterService: ToasterService, - private cryptoService: CryptoService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, private analytics: Angulartics2, - private auditService: AuditService) { } +export class ViewComponent extends BaseViewComponent implements OnChanges { + constructor(cipherService: CipherService, totpService: TotpService, + tokenService: TokenService, toasterService: ToasterService, + cryptoService: CryptoService, platformUtilsService: PlatformUtilsService, + i18nService: I18nService, analytics: Angulartics2, + auditService: AuditService) { + super(cipherService, totpService, tokenService, toasterService, cryptoService, platformUtilsService, + i18nService, analytics, auditService); + } async ngOnChanges() { - this.cleanUp(); - - const cipher = await this.cipherService.get(this.cipherId); - this.cipher = await cipher.decrypt(); - - this.isPremium = this.tokenService.getPremium(); - - if (this.cipher.type === CipherType.Login && this.cipher.login.totp && - (cipher.organizationUseTotp || this.isPremium)) { - await this.totpUpdateCode(); - await this.totpTick(); - - this.totpInterval = setInterval(async () => { - await this.totpTick(); - }, 1000); - } - } - - ngOnDestroy() { - this.cleanUp(); - } - - edit() { - this.onEditCipher.emit(this.cipher); - } - - togglePassword() { - this.analytics.eventTrack.next({ action: 'Toggled Password' }); - this.showPassword = !this.showPassword; - } - - async checkPassword() { - if (this.cipher.login == null || this.cipher.login.password == null || this.cipher.login.password === '') { - return; - } - - this.analytics.eventTrack.next({ action: 'Check Password' }); - this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password); - const matches = await this.checkPasswordPromise; - - if (matches > 0) { - this.toasterService.popAsync('warning', null, this.i18nService.t('passwordExposed', matches.toString())); - } else { - this.toasterService.popAsync('success', null, this.i18nService.t('passwordSafe')); - } - } - - toggleFieldValue(field: FieldView) { - const f = (field as any); - f.showValue = !f.showValue; - } - - launch(uri: LoginUriView) { - if (!uri.canLaunch) { - return; - } - - this.analytics.eventTrack.next({ action: 'Launched Login URI' }); - this.platformUtilsService.launchUri(uri.uri); - } - - copy(value: string, typeI18nKey: string, aType: string) { - if (value == null) { - return; - } - - this.analytics.eventTrack.next({ action: 'Copied ' + aType }); - this.platformUtilsService.copyToClipboard(value); - this.toasterService.popAsync('info', null, - this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey))); - } - - async downloadAttachment(attachment: AttachmentView) { - const a = (attachment as any); - if (a.downloading) { - return; - } - - if (this.cipher.organizationId == null && !this.isPremium) { - this.toasterService.popAsync('error', this.i18nService.t('premiumRequired'), - this.i18nService.t('premiumRequiredDesc')); - return; - } - - a.downloading = true; - const response = await fetch(new Request(attachment.url, { cache: 'no-cache' })); - if (response.status !== 200) { - this.toasterService.popAsync('error', null, this.i18nService.t('errorOccurred')); - a.downloading = false; - return; - } - - try { - const buf = await response.arrayBuffer(); - const key = await this.cryptoService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.cryptoService.decryptFromBytes(buf, key); - this.platformUtilsService.saveFile(window, decBuf, null, attachment.fileName); - } catch (e) { - this.toasterService.popAsync('error', null, this.i18nService.t('errorOccurred')); - } - - a.downloading = false; - } - - private cleanUp() { - this.cipher = null; - this.showPassword = false; - if (this.totpInterval) { - clearInterval(this.totpInterval); - } - } - - private async totpUpdateCode() { - if (this.cipher == null || this.cipher.type !== CipherType.Login || this.cipher.login.totp == null) { - if (this.totpInterval) { - clearInterval(this.totpInterval); - } - return; - } - - this.totpCode = await this.totpService.getCode(this.cipher.login.totp); - if (this.totpCode != null) { - this.totpCodeFormatted = this.totpCode.substring(0, 3) + ' ' + this.totpCode.substring(3); - } else { - this.totpCodeFormatted = null; - if (this.totpInterval) { - clearInterval(this.totpInterval); - } - } - } - - private async totpTick() { - const epoch = Math.round(new Date().getTime() / 1000.0); - const mod = epoch % 30; - - this.totpSec = 30 - mod; - this.totpDash = +(Math.round(((2.62 * mod) + 'e+2') as any) + 'e-2'); - this.totpLow = this.totpSec <= 7; - if (mod === 0) { - await this.totpUpdateCode(); - } + await super.load(); } } diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 7dcf6e6a3d1..47fbfbc264e 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -332,6 +332,9 @@ "copyPassword": { "message": "Copy Password" }, + "copyUri": { + "message": "Copy URI" + }, "length": { "message": "Length" },