mirror of
https://github.com/bitwarden/browser.git
synced 2024-08-26 23:09:46 +02:00
move vault components to jslib
This commit is contained in:
parent
22f0f97cda
commit
45ba629371
277
src/angular/components/add-edit.component.ts
Normal file
277
src/angular/components/add-edit.component.ts
Normal file
|
@ -0,0 +1,277 @@
|
|||
import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { CipherType } from '../../enums/cipherType';
|
||||
import { FieldType } from '../../enums/fieldType';
|
||||
import { SecureNoteType } from '../../enums/secureNoteType';
|
||||
import { UriMatchType } from '../../enums/uriMatchType';
|
||||
|
||||
import { AuditService } from '../../abstractions/audit.service';
|
||||
import { CipherService } from '../../abstractions/cipher.service';
|
||||
import { FolderService } from '../../abstractions/folder.service';
|
||||
import { I18nService } from '../../abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
||||
|
||||
import { CardView } from '../../models/view/cardView';
|
||||
import { CipherView } from '../../models/view/cipherView';
|
||||
import { FieldView } from '../../models/view/fieldView';
|
||||
import { FolderView } from '../../models/view/folderView';
|
||||
import { IdentityView } from '../../models/view/identityView';
|
||||
import { LoginUriView } from '../../models/view/loginUriView';
|
||||
import { LoginView } from '../../models/view/loginView';
|
||||
import { SecureNoteView } from '../../models/view/secureNoteView';
|
||||
|
||||
export class AddEditComponent implements OnChanges {
|
||||
@Input() folderId: string;
|
||||
@Input() cipherId: string;
|
||||
@Input() type: CipherType;
|
||||
@Output() onSavedCipher = new EventEmitter<CipherView>();
|
||||
@Output() onDeletedCipher = new EventEmitter<CipherView>();
|
||||
@Output() onCancelled = new EventEmitter<CipherView>();
|
||||
@Output() onEditAttachments = new EventEmitter<CipherView>();
|
||||
@Output() onGeneratePassword = new EventEmitter();
|
||||
|
||||
editMode: boolean = false;
|
||||
cipher: CipherView;
|
||||
folders: FolderView[];
|
||||
title: string;
|
||||
formPromise: Promise<any>;
|
||||
deletePromise: Promise<any>;
|
||||
checkPasswordPromise: Promise<number>;
|
||||
showPassword: boolean = false;
|
||||
cipherType = CipherType;
|
||||
fieldType = FieldType;
|
||||
addFieldType: FieldType = FieldType.Text;
|
||||
typeOptions: any[];
|
||||
cardBrandOptions: any[];
|
||||
cardExpMonthOptions: any[];
|
||||
identityTitleOptions: any[];
|
||||
addFieldTypeOptions: any[];
|
||||
uriMatchOptions: any[];
|
||||
|
||||
constructor(protected cipherService: CipherService, protected folderService: FolderService,
|
||||
protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
|
||||
protected analytics: Angulartics2, protected toasterService: ToasterService,
|
||||
protected 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 },
|
||||
];
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
||||
}
|
125
src/angular/components/attachments.component.ts
Normal file
125
src/angular/components/attachments.component.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { CipherService } from '../../abstractions/cipher.service';
|
||||
import { CryptoService } from '../../abstractions/crypto.service';
|
||||
import { I18nService } from '../../abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
||||
import { TokenService } from '../../abstractions/token.service';
|
||||
|
||||
import { Cipher } from '../../models/domain/cipher';
|
||||
|
||||
import { AttachmentView } from '../../models/view/attachmentView';
|
||||
import { CipherView } from '../../models/view/cipherView';
|
||||
|
||||
export class AttachmentsComponent implements OnInit {
|
||||
@Input() cipherId: string;
|
||||
|
||||
cipher: CipherView;
|
||||
cipherDomain: Cipher;
|
||||
hasUpdatedKey: boolean;
|
||||
canAccessAttachments: boolean;
|
||||
formPromise: Promise<any>;
|
||||
deletePromises: { [id: string]: Promise<any>; } = {};
|
||||
|
||||
constructor(protected cipherService: CipherService, protected analytics: Angulartics2,
|
||||
protected toasterService: ToasterService, protected i18nService: I18nService,
|
||||
protected cryptoService: CryptoService, protected tokenService: TokenService,
|
||||
protected 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { OnInit } from '@angular/core';
|
||||
|
||||
import { I18nService } from '../../abstractions/i18n.service';
|
||||
import { PasswordGenerationService } from '../../abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
||||
|
||||
import { PasswordHistory } from '../../models/domain/passwordHistory';
|
||||
|
||||
export class PasswordGeneratorHistoryComponent implements OnInit {
|
||||
history: PasswordHistory[] = [];
|
||||
|
||||
constructor(protected passwordGenerationService: PasswordGenerationService, protected analytics: Angulartics2,
|
||||
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
|
||||
protected 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')));
|
||||
}
|
||||
}
|
128
src/angular/components/password-generator.component.ts
Normal file
128
src/angular/components/password-generator.component.ts
Normal file
|
@ -0,0 +1,128 @@
|
|||
import * as template from './password-generator.component.html';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
NgZone,
|
||||
OnInit,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { I18nService } from '../../abstractions/i18n.service';
|
||||
import { PasswordGenerationService } from '../../abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
||||
|
||||
export class PasswordGeneratorComponent implements OnInit {
|
||||
@Input() showSelect: boolean = false;
|
||||
@Output() onSelected = new EventEmitter<string>();
|
||||
|
||||
options: any = {};
|
||||
password: string = '-';
|
||||
showOptions = false;
|
||||
avoidAmbiguous = false;
|
||||
|
||||
constructor(protected passwordGenerationService: PasswordGenerationService, protected analytics: Angulartics2,
|
||||
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
|
||||
protected toasterService: ToasterService, protected ngZone: NgZone,
|
||||
protected 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();
|
||||
});
|
||||
}
|
||||
}
|
194
src/angular/components/view.component.ts
Normal file
194
src/angular/components/view.component.ts
Normal file
|
@ -0,0 +1,194 @@
|
|||
import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { CipherType } from '../../enums/cipherType';
|
||||
import { FieldType } from '../../enums/fieldType';
|
||||
|
||||
import { AuditService } from '../../abstractions/audit.service';
|
||||
import { CipherService } from '../../abstractions/cipher.service';
|
||||
import { CryptoService } from '../../abstractions/crypto.service';
|
||||
import { I18nService } from '../../abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
||||
import { TokenService } from '../../abstractions/token.service';
|
||||
import { TotpService } from '../../abstractions/totp.service';
|
||||
|
||||
import { AttachmentView } from '../../models/view/attachmentView';
|
||||
import { CipherView } from '../../models/view/cipherView';
|
||||
import { FieldView } from '../../models/view/fieldView';
|
||||
import { LoginUriView } from '../../models/view/loginUriView';
|
||||
|
||||
export class ViewComponent implements OnChanges, OnDestroy {
|
||||
@Input() cipherId: string;
|
||||
@Output() onEditCipher = new EventEmitter<CipherView>();
|
||||
|
||||
cipher: CipherView;
|
||||
showPassword: boolean;
|
||||
isPremium: boolean;
|
||||
totpCode: string;
|
||||
totpCodeFormatted: string;
|
||||
totpDash: number;
|
||||
totpSec: number;
|
||||
totpLow: boolean;
|
||||
fieldType = FieldType;
|
||||
checkPasswordPromise: Promise<number>;
|
||||
|
||||
private totpInterval: any;
|
||||
|
||||
constructor(protected cipherService: CipherService, protected totpService: TotpService,
|
||||
protected tokenService: TokenService, protected toasterService: ToasterService,
|
||||
protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
|
||||
protected i18nService: I18nService, protected analytics: Angulartics2,
|
||||
protected auditService: 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user