file settings implemented

pull/8/head
Antelle 2015-10-24 19:43:30 +03:00
parent 30ceab0bcd
commit 058b82f722
7 changed files with 280 additions and 32 deletions

View File

@ -1,11 +1,10 @@
# MVP
- [ ] add/edit groups
- [ ] dropbox
- [ ] file settings
# FUTURE
- [ ] dropbox
- [ ] remember last file
- [ ] trash: groups/empty/untrash
- [ ] advanced search

View File

@ -18,7 +18,11 @@ var FileModel = Backbone.Model.extend({
error: false,
created: false,
demo: false,
groups: null
groups: null,
oldPasswordLength: 0,
oldKeyFileName: '',
passwordChanged: false,
keyFileChanged: false
},
db: null,
@ -49,7 +53,10 @@ var FileModel = Backbone.Model.extend({
return;
}
this.readModel(this.get('name'));
this.set({ open: true, opening: false, error: false, passwordLength: len });
this.setOpenFile({ passwordLength: len });
this._oldPasswordHash = this.db.credentials.passwordHash;
this._oldKeyFileHash = this.db.credentials.keyFileHash;
this._oldKeyChangeDate = this.db.meta.keyChanged;
},
create: function(name) {
@ -66,7 +73,18 @@ var FileModel = Backbone.Model.extend({
var demoFile = kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(demoFileData));
this.db = kdbxweb.Kdbx.load(demoFile, credentials);
this.readModel();
this.set({ open: true, created: false, opening: false, error: false, name: 'Demo', passwordLength: 4, demo: true });
this.setOpenFile({ passwordLength: 4, demo: true, name: 'Demo' });
},
setOpenFile: function(props) {
_.extend(props, {
open: true,
opening: false,
error: false,
oldKeyFileName: this.get('keyFileName'),
oldPasswordLength: props.passwordLength
});
this.set(props);
},
readModel: function(topGroupTitle) {
@ -142,6 +160,94 @@ var FileModel = Backbone.Model.extend({
getXml: function() {
return this.db.saveXml();
},
setPassword: function(password) {
this.db.credentials.setPassword(password);
this.db.meta.keyChanged = new Date();
this.set({ passwordLength: password.byteLength, passwordChanged: true });
this.setModified();
},
resetPassword: function() {
this.db.credentials.passwordHash = this._oldPasswordHash;
if (this.db.credentials.keyFileHash === this._oldKeyFileHash) {
this.db.meta.keyChanged = this._oldKeyChangeDate;
}
this.set({ passwordLength: this.get('oldPasswordLength'), passwordChanged: false });
},
setKeyFile: function(keyFile, keyFileName) {
this.db.credentials.setKeyFile(keyFile);
this.db.meta.keyChanged = new Date();
this.set({ keyFileName: keyFileName, keyFileChanged: true });
this.setModified();
},
generateAndSetKeyFile: function() {
var keyFile = kdbxweb.Credentials.createRandomKeyFile();
var keyFileName = 'Generated';
this.setKeyFile(keyFile, keyFileName);
return keyFile;
},
resetKeyFile: function() {
this.db.credentials.keyFileHash = this._oldKeyFileHash;
if (this.db.credentials.passwordHash === this._oldPasswordHash) {
this.db.meta.keyChanged = this._oldKeyChangeDate;
}
this.set({ keyFileName: this.get('oldKeyFileName'), keyFileChanged: false });
},
removeKeyFile: function() {
this.db.credentials.keyFileHash = null;
var changed = !!this._oldKeyFileHash;
if (!changed && this.db.credentials.passwordHash === this._oldPasswordHash) {
this.db.meta.keyChanged = this._oldKeyChangeDate;
}
this.set({ keyFileName: '', keyFileChanged: changed });
},
setName: function(name) {
this.db.meta.name = name;
this.db.meta.nameChanged = new Date();
this.set('name', name);
this.setModified();
},
setDefaultUser: function(defaultUser) {
this.db.meta.defaultUser = defaultUser;
this.db.meta.defaultUserChanged = new Date();
this.set('defaultUser', defaultUser);
this.setModified();
},
setRecycleBinEnabled: function(enabled) {
enabled = !!enabled;
this.db.meta.recycleBinEnabled = enabled;
if (enabled) {
this.db.createRecycleBin();
}
this.set('setRecycleBinEnabled', enabled);
this.setModified();
},
setHistoryMaxItems: function(count) {
this.db.meta.historyMaxItems = count;
this.set('historyMaxItems', count);
this.setModified();
},
setHistoryMaxSize: function(size) {
this.db.meta.historyMaxSize = size;
this.set('historyMaxSize', size);
this.setModified();
},
setKeyEncryptionRounds: function(rounds) {
this.db.header.keyEncryptionRounds = rounds;
this.set('keyEncryptionRounds', rounds);
this.setModified();
}
});

View File

@ -102,12 +102,16 @@ var OpenFileView = Backbone.View.extend({
} else {
this.model.set('keyFileName', file.name);
}
complete(true);
if (complete) {
complete(true);
}
}).bind(this);
reader.onerror = (function() {
Alerts.error({ header: 'Failed to read file' });
this.showReadyToOpen();
complete(false);
if (complete) {
complete(false);
}
}).bind(this);
reader.readAsArrayBuffer(file);
},

View File

@ -6,6 +6,7 @@ var Backbone = require('backbone'),
Alerts = require('../../util/alerts'),
RuntimeInfo = require('../../util/runtime-info'),
Links = require('../../const/links'),
kdbxweb = require('kdbxweb'),
FileSaver = require('filesaver');
var SettingsAboutView = Backbone.View.extend({
@ -15,9 +16,17 @@ var SettingsAboutView = Backbone.View.extend({
'click .settings__file-button-save-file': 'saveToFile',
'click .settings__file-button-export-xml': 'exportAsXml',
'click .settings__file-button-save-dropbox': 'saveToDropbox',
'change #settings__file-key-file': 'keyfileChange',
'change #settings__file-key-file': 'keyFileChange',
'mousedown #settings__file-file-select-link': 'triggerSelectFile',
'change #settings__file-file-select': 'fileSelected',
'focus #settings__file-master-pass': 'focusMasterPass',
'blur #settings__file-master-pass': 'blurMasterPass'
'blur #settings__file-master-pass': 'blurMasterPass',
'blur #settings__file-name': 'blurName',
'blur #settings__file-def-user': 'blurDefUser',
'change #settings__file-trash': 'changeTrash',
'blur #settings__file-hist-len': 'blurHistoryLength',
'blur #settings__file-hist-size': 'blurHistorySize',
'blur #settings__file-key-rounds': 'blurKeyRounds'
},
initialize: function() {
@ -38,51 +47,119 @@ var SettingsAboutView = Backbone.View.extend({
historyMaxSize: Math.round(this.model.get('historyMaxSize') / 1024 / 1024),
keyEncryptionRounds: this.model.get('keyEncryptionRounds')
});
if (!this.model.get('created')) {
this.$el.find('.settings__file-master-pass-warning').toggle(this.model.get('passwordChanged'));
}
this.renderKeyFileSelect();
},
renderKeyFileSelect: function() {
var keyFileName = this.model.get('keyFileName'),
oldKeyFileName = this.model.get('oldKeyFileName'),
keyFileChanged = this.model.get('keyFileChanged');
var sel = this.$el.find('#settings__file-key-file');
sel.html('');
if (keyFileName && keyFileChanged) {
var text = keyFileName !== 'Generated' ? 'Use key file ' + keyFileName : 'Use generated key file';
$('<option/>').val('ex').text(text).appendTo(sel);
}
if (oldKeyFileName) {
$('<option/>').val('old').text('Use ' + (keyFileChanged ? 'old ' : '') + 'key file ' + oldKeyFileName).appendTo(sel);
}
$('<option/>').val('gen').text('Generate new key file').appendTo(sel);
$('<option/>').val('none').text('Don\'t use key file').appendTo(sel);
if (keyFileName && keyFileChanged) {
sel.val('ex');
} else if (!keyFileName) {
sel.val('none');
} else if (oldKeyFileName && keyFileName === oldKeyFileName && !keyFileChanged) {
sel.val('old');
}
},
validate: function() {
if (!this.model.get('passwordLength')) {
Alerts.error({
header: 'Empty password',
body: 'Please, enter the password. You will use it the next time you open this file.',
complete: (function() {
this.$el.find('#settings__file-master-pass').focus();
}).bind(this)
});
return false;
}
return true;
},
saveToFile: function() {
if (!this.validate()) {
return;
}
var data = this.model.getData();
var blob = new Blob([data], {type: 'application/octet-stream'});
FileSaver.saveAs(blob, this.model.get('name') + '.kdbx');
},
exportAsXml: function() {
if (!this.validate()) {
return;
}
var data = this.model.getXml();
var blob = new Blob([data], {type: 'text/xml'});
FileSaver.saveAs(blob, this.model.get('name') + '.xml');
},
saveToDropbox: function() {
if (!this.validate()) {
return;
}
Alerts.notImplemented();
},
keyfileChange: function(e) {
keyFileChange: function(e) {
switch (e.target.value) {
case 'ex':
this.useExistingKeyFile();
break;
case 'sel':
this.selectKeyFile();
case 'old':
this.selectOldKeyFile();
break;
case 'gen':
this.generateKeyFile();
break;
default:
case 'none':
this.clearKeyFile();
break;
}
},
useExistingKeyFile: function() {
},
selectKeyFile: function() {
selectOldKeyFile: function() {
this.model.resetKeyFile();
this.renderKeyFileSelect();
},
generateKeyFile: function() {
var keyFile = this.model.generateAndSetKeyFile();
var blob = new Blob([keyFile], {type: 'application/octet-stream'});
FileSaver.saveAs(blob, this.model.get('name') + '.key');
this.renderKeyFileSelect();
},
clearKeyFile: function() {
this.model.removeKeyFile();
this.renderKeyFileSelect();
},
triggerSelectFile: function() {
this.$el.find('#settings__file-file-select').click();
},
fileSelected: function(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = (function(e) {
var res = e.target.result;
this.model.setKeyFile(res, file.name);
this.renderKeyFileSelect();
}).bind(this);
reader.readAsArrayBuffer(file);
},
focusMasterPass: function(e) {
@ -95,11 +172,62 @@ var SettingsAboutView = Backbone.View.extend({
blurMasterPass: function(e) {
if (!e.target.value) {
this.passwordChanged = false;
this.model.resetPassword();
e.target.value = PasswordDisplay.present(this.model.get('passwordLength'));
this.$el.find('.settings__file-master-pass-warning').hide();
} else {
this.passwordChanged = true;
this.model.setPassword(kdbxweb.ProtectedValue.fromString(e.target.value));
if (!this.model.get('created')) {
this.$el.find('.settings__file-master-pass-warning').show();
}
}
e.target.setAttribute('type', 'password');
},
blurName: function(e) {
var value = $.trim(e.target.value);
if (!value) {
e.target.value = this.model.get('name');
return;
}
this.model.setName(value);
},
blurDefUser: function(e) {
var value = $.trim(e.target.value);
this.model.setDefaultUser(value);
},
changeTrash: function(e) {
this.model.setRecycleBinEnabled(e.target.checked);
},
blurHistoryLength: function(e) {
var value = +e.target.value;
if (isNaN(value)) {
e.target.value = this.model.get('historyMaxItems');
return;
}
this.model.setHistoryMaxItems(value);
},
blurHistorySize: function(e) {
var value = +e.target.value;
if (isNaN(value)) {
e.target.value = this.model.get('historyMaxSize') / 1024 / 1024;
return;
}
this.model.setHistoryMaxSize(value * 1024 * 1024);
},
blurKeyRounds: function(e) {
var value = +e.target.value;
if (isNaN(value)) {
e.target.value = this.model.get('keyEncryptionRounds');
return;
}
this.model.setKeyEncryptionRounds(value);
}
});

View File

@ -43,7 +43,7 @@
}
}
&__select, &__input, &__pre {
&__select, &__input, &__pre, &__file-master-pass-label {
width: 60%;
@include tablet {
width: calc(100% - 20px);
@ -58,4 +58,14 @@
@include user-select(text);
white-space: pre-wrap;
}
&__select-no-margin {
margin-bottom: $base-padding-v;
}
&__file-master-pass-warning {
font-weight: normal;
float: right;
display: none;
}
}

View File

@ -17,15 +17,16 @@
</div>
<h2>Settings</h2>
<label for="settings__file-master-pass">Master password:</label>
<label for="settings__file-master-pass" class="settings__file-master-pass-label">Master password:
<span class="settings__file-master-pass-warning">
<i class="fa fa-warning"></i> password was changed; leave the field blank to use old password
</span>
</label>
<input type="password" class="settings__input" id="settings__file-master-pass" value="<%= password %>" />
<label for="settings__file-key-file">Key file:</label>
<select class="settings__select" id="settings__file-key-file">
<option value="">Don't use key file</option>
<option value="ex">Use existing key file</option>
<option value="gen">Generate new key file</option>
<option value="sel">Select a key file</option>
</select>
<select class="settings__select settings__select-no-margin" id="settings__file-key-file"></select>
<a id="settings__file-file-select-link">Select a key file</a>
<input type="file" accept=".key" id="settings__file-file-select" class="hide" />
<h2>Names</h2>
<label for="settings__file-name">Name:</label>
@ -39,11 +40,11 @@
<label for="settings__file-trash">Enable trash</label>
</div>
<label for="settings__file-hist-len">History length, keep last records per entry:</label>
<input type="text" pattern="\d*" required class="settings__input" id="settings__file-hist-len" value="<%= historyMaxItems %>" />
<input type="text" pattern="\d+" required class="settings__input" id="settings__file-hist-len" value="<%= historyMaxItems %>" />
<label for="settings__file-hist-size">History size, total MB per file:</label>
<input type="text" pattern="\d*" required class="settings__input" id="settings__file-hist-size" value="<%= historyMaxSize %>" />
<input type="text" pattern="\d+" required class="settings__input" id="settings__file-hist-size" value="<%= historyMaxSize %>" />
<h2>Advanced</h2>
<label for="settings__file-key-rounds">Key encryption rounds:</label>
<input type="text" pattern="\d*" required class="settings__input" id="settings__file-key-rounds" value="<%= keyEncryptionRounds %>" />
<input type="text" pattern="\d+" required class="settings__input" id="settings__file-key-rounds" value="<%= keyEncryptionRounds %>" />
</div>

View File

@ -29,7 +29,7 @@
"dropbox": "antelle/dropbox-js#0.10.3",
"font-awesome": "~4.4.0",
"install": "~1.0.4",
"kdbxweb": "~0.1.8",
"kdbxweb": "~0.1.10",
"normalize.css": "~3.0.3",
"pikaday": "~1.3.3",
"zepto": "~1.1.6",