mirror of https://github.com/keeweb/keeweb
file settings implemented
parent
30ceab0bcd
commit
058b82f722
3
TODO.md
3
TODO.md
|
@ -1,11 +1,10 @@
|
|||
# MVP
|
||||
|
||||
- [ ] add/edit groups
|
||||
- [ ] dropbox
|
||||
- [ ] file settings
|
||||
|
||||
# FUTURE
|
||||
|
||||
- [ ] dropbox
|
||||
- [ ] remember last file
|
||||
- [ ] trash: groups/empty/untrash
|
||||
- [ ] advanced search
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue