import kdbxweb from 'kdbxweb'; import { View } from 'framework/views/view'; import { Storage } from 'storage'; import { Shortcuts } from 'comp/app/shortcuts'; import { Launcher } from 'comp/launcher'; import { Alerts } from 'comp/ui/alerts'; import { YubiKey } from 'comp/app/yubikey'; import { UsbListener } from 'comp/app/usb-listener'; import { Links } from 'const/links'; import { AppSettingsModel } from 'models/app-settings-model'; import { DateFormat } from 'comp/i18n/date-format'; import { UrlFormat } from 'util/formatting/url-format'; import { PasswordPresenter } from 'util/formatting/password-presenter'; import { Locale } from 'util/locale'; import { Features } from 'util/features'; import { FileSaver } from 'util/ui/file-saver'; import { OpenConfigView } from 'views/open-config-view'; import { omit } from 'util/fn'; import template from 'templates/settings/settings-file.hbs'; const DefaultBackupPath = 'Backups/{name}.{date}.bak'; const DefaultBackupSchedule = '1w'; class SettingsFileView extends View { template = template; yubiKeys = []; events = { 'click .settings__file-button-save-default': 'saveDefault', 'click .settings__file-button-save-choose': 'toggleChooser', 'click .settings__file-button-close': 'closeFile', 'click .settings__file-save-to-file': 'saveToFile', 'click .settings__file-save-to-xml': 'saveToXml', 'click .settings__file-save-to-html': 'saveToHtml', 'click .settings__file-save-to-storage': 'saveToStorage', 'change #settings__file-key-file': 'keyFileChange', 'click #settings__file-file-select-link': 'triggerSelectFile', 'change #settings__file-file-select': 'fileSelected', 'focus #settings__file-master-pass': 'focusMasterPass', 'input #settings__file-master-pass': 'changeMasterPass', 'blur #settings__file-master-pass': 'blurMasterPass', 'focus #settings__file-confirm-master-pass': 'focusConfirmMasterPass', 'blur #settings__file-confirm-master-pass': 'blurConfirmMasterPass', 'input #settings__file-name': 'changeName', 'input #settings__file-def-user': 'changeDefUser', 'change #settings__file-backup-enabled': 'changeBackupEnabled', 'input #settings__file-backup-path': 'changeBackupPath', 'change #settings__file-backup-storage': 'changeBackupStorage', 'change #settings__file-backup-schedule': 'changeBackupSchedule', 'click .settings__file-button-backup': 'backupFile', 'change #settings__file-trash': 'changeTrash', 'change #settings__file-hist-type': 'changeHistoryMode', 'input #settings__file-hist-len': 'changeHistoryLength', 'input #settings__file-hist-size': 'changeHistorySize', 'change #settings__file-format-version': 'changeFormatVersion', 'change #settings__file-kdf': 'changeKdf', 'input #settings__file-key-rounds': 'changeKeyRounds', 'input #settings__file-key-change-force': 'changeKeyChangeForce', 'input .settings__input-kdf': 'changeKdfParameter', 'change #settings__file-yubikey': 'changeYubiKey' }; constructor(model, options) { super(model, options); const watchedProps = ['syncing', 'syncError', 'syncDate']; for (const prop of watchedProps) { this.listenTo(this.model, 'change:' + prop, () => { setTimeout(() => this.render(), 0); }); } this.refreshYubiKeys(false); } render() { const storageProviders = []; const fileStorage = this.model.storage; let canBackup = false; Object.keys(Storage).forEach((name) => { const prv = Storage[name]; if (!canBackup && prv.backup && prv.enabled) { canBackup = true; } if (!prv.system && prv.enabled) { storageProviders.push({ name: prv.name, icon: prv.icon, own: name === fileStorage, backup: prv.backup }); } }); storageProviders.sort((x, y) => (x.uipos || Infinity) - (y.uipos || Infinity)); const backup = this.model.backup; const selectedYubiKey = this.model.chalResp ? `${this.model.chalResp.serial}:${this.model.chalResp.slot}` : ''; const showYubiKeyBlock = !!this.model.chalResp || (Launcher && AppSettingsModel.enableUsb && AppSettingsModel.yubiKeyShowChalResp); const yubiKeys = []; if (showYubiKeyBlock) { for (const yk of this.yubiKeys) { for (const slot of yk.slots.filter((s) => s.valid)) { yubiKeys.push({ value: `${yk.serial}:${slot.number}`, fullName: yk.fullName, vid: yk.vid, pid: yk.pid, serial: yk.serial, slot: slot.number }); } } if (selectedYubiKey && !yubiKeys.some((yk) => yk.value === selectedYubiKey)) { yubiKeys.push({ value: selectedYubiKey, fullName: `YubiKey ${this.model.chalResp.serial}`, vid: this.model.chalResp.vid, pid: this.model.chalResp.pid, serial: this.model.chalResp.serial, slot: this.model.chalResp.slot }); } } super.render({ cmd: Shortcuts.actionShortcutSymbol(true), supportFiles: !!Launcher, desktopLink: Links.Desktop, name: this.model.name, path: this.model.path, storage: this.model.storage, syncing: this.model.syncing, syncError: this.model.syncError, syncDate: DateFormat.dtStr(this.model.syncDate), password: PasswordPresenter.present(this.model.passwordLength), defaultUser: this.model.defaultUser, recycleBinEnabled: this.model.recycleBinEnabled, backupEnabled: backup && backup.enabled, backupStorage: backup && backup.storage, backupPath: (backup && backup.path) || DefaultBackupPath.replace('{name}', this.model.name), backupSchedule: backup ? backup.schedule : DefaultBackupSchedule, historyMaxItems: this.model.historyMaxItems, historyMaxSize: Math.round(this.model.historyMaxSize / 1024 / 1024), formatVersion: this.model.formatVersion, kdfName: this.model.kdfName, keyEncryptionRounds: this.model.keyEncryptionRounds, keyChangeForce: this.model.keyChangeForce > 0 ? this.model.keyChangeForce : null, kdfParameters: this.kdfParametersToUi(this.model.kdfParameters), storageProviders, canBackup, canSaveTo: AppSettingsModel.canSaveTo, canExportXml: AppSettingsModel.canExportXml, canExportHtml: AppSettingsModel.canExportHtml, showYubiKeyBlock, selectedYubiKey, yubiKeys }); if (!this.model.created) { this.$el.find('.settings__file-master-pass-warning').toggle(this.model.passwordChanged); this.$el .find('#settings__file-master-pass-warning-text') .text(Locale.setFilePassChanged); } this.renderKeyFileSelect(); } kdfParametersToUi(kdfParameters) { return kdfParameters ? { ...kdfParameters, memory: Math.round(kdfParameters.memory / 1024) } : null; } renderKeyFileSelect() { const keyFileName = this.model.keyFileName; const oldKeyFileName = this.model.oldKeyFileName; const keyFileChanged = this.model.keyFileChanged; const sel = this.$el.find('#settings__file-key-file'); sel.empty(); if (keyFileName && keyFileChanged) { const text = keyFileName !== 'Generated' ? Locale.setFileUseKeyFile + ' ' + keyFileName : Locale.setFileUseGenKeyFile; $('