keeweb/app/scripts/models/file-model.js

760 lines
24 KiB
JavaScript
Raw Normal View History

2019-09-15 14:16:32 +02:00
import kdbxweb from 'kdbxweb';
import demoFileData from 'demo.kdbx';
2019-09-17 21:39:06 +02:00
import { Model } from 'framework/model';
2019-09-16 22:57:56 +02:00
import { Events } from 'framework/events';
2019-09-15 14:16:32 +02:00
import { GroupCollection } from 'collections/group-collection';
import { KdbxToHtml } from 'comp/format/kdbx-to-html';
import { GroupModel } from 'models/group-model';
import { AppSettingsModel } from 'models/app-settings-model';
2019-09-15 14:16:32 +02:00
import { IconUrlFormat } from 'util/formatting/icon-url-format';
import { Logger } from 'util/logger';
2019-09-18 20:42:17 +02:00
import { mapObject } from 'util/fn';
import { ChalRespCalculator } from 'comp/app/chal-resp-calculator';
2015-10-17 23:49:24 +02:00
2017-01-31 07:50:28 +01:00
const logger = new Logger('file');
2015-12-12 09:53:50 +01:00
2019-09-17 21:39:06 +02:00
class FileModel extends Model {
constructor(data) {
2019-09-17 23:44:17 +02:00
super({
entryMap: {},
groupMap: {},
...data
});
2019-09-17 21:39:06 +02:00
}
2015-10-17 23:49:24 +02:00
2020-05-30 12:45:56 +02:00
open(password, fileData, keyFileData, callback) {
2015-10-17 23:49:24 +02:00
try {
2020-05-30 12:45:56 +02:00
const challengeResponse = ChalRespCalculator.build(this.chalResp);
2020-05-30 08:10:19 +02:00
const credentials = new kdbxweb.Credentials(password, keyFileData, challengeResponse);
2017-01-31 07:50:28 +01:00
const ts = logger.ts();
2017-01-29 23:28:04 +01:00
kdbxweb.Kdbx.load(fileData, credentials)
2020-06-01 16:53:51 +02:00
.then((db) => {
this.db = db;
2015-12-13 22:27:26 +01:00
this.readModel();
this.setOpenFile({ passwordLength: password ? password.textLength : 0 });
2015-11-06 21:30:36 +01:00
if (keyFileData) {
kdbxweb.ByteUtils.zeroBuffer(keyFileData);
}
this.fixVersion();
2019-08-16 23:05:39 +02:00
logger.info(
'Opened file ' +
2019-09-17 21:39:06 +02:00
this.name +
2019-08-16 23:05:39 +02:00
': ' +
logger.ts(ts) +
', ' +
this.kdfArgsToString(db.header) +
', ' +
Math.round(fileData.byteLength / 1024) +
' kB'
);
2015-12-06 21:32:41 +01:00
callback();
2017-01-29 23:28:04 +01:00
})
2020-06-01 16:53:51 +02:00
.catch((err) => {
2019-08-18 08:05:38 +02:00
if (
err.code === kdbxweb.Consts.ErrorCodes.InvalidKey &&
password &&
!password.byteLength
) {
logger.info(
'Error opening file with empty password, try to open with null password'
);
2020-05-30 14:28:06 +02:00
return this.open(null, fileData, keyFileData, callback);
2017-01-29 23:28:04 +01:00
}
logger.error('Error opening file', err.code, err.message, err);
callback(err);
});
2015-10-17 23:49:24 +02:00
} catch (e) {
2015-12-12 09:53:50 +01:00
logger.error('Error opening file', e, e.code, e.message, e);
2015-12-06 21:32:41 +01:00
callback(e);
2015-10-17 23:49:24 +02:00
}
2019-09-17 21:39:06 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
kdfArgsToString(header) {
2017-01-31 23:18:58 +01:00
if (header.kdfParameters) {
2019-08-16 23:05:39 +02:00
return header.kdfParameters
.keys()
2020-06-01 16:53:51 +02:00
.map((key) => {
2019-08-16 23:05:39 +02:00
const val = header.kdfParameters.get(key);
if (val instanceof ArrayBuffer) {
2019-08-18 10:17:09 +02:00
return undefined;
2019-08-16 23:05:39 +02:00
}
return key + '=' + val;
})
2020-06-01 16:53:51 +02:00
.filter((p) => p)
2019-08-16 23:05:39 +02:00
.join('&');
2017-01-31 23:18:58 +01:00
} else if (header.keyEncryptionRounds) {
return header.keyEncryptionRounds + ' rounds';
} else {
return '?';
}
2019-09-17 21:39:06 +02:00
}
2017-01-31 23:18:58 +01:00
2019-08-18 10:17:09 +02:00
create(name) {
2017-01-31 07:50:28 +01:00
const password = kdbxweb.ProtectedValue.fromString('');
const credentials = new kdbxweb.Credentials(password);
2015-10-17 23:49:24 +02:00
this.db = kdbxweb.Kdbx.create(credentials, name);
2019-09-17 21:39:06 +02:00
this.name = name;
2015-10-17 23:49:24 +02:00
this.readModel();
2019-09-17 21:39:06 +02:00
this.set({ active: true, created: true, name });
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
importWithXml(fileXml, callback) {
2016-03-01 04:29:20 +01:00
try {
2017-01-31 07:50:28 +01:00
const ts = logger.ts();
const password = kdbxweb.ProtectedValue.fromString('');
const credentials = new kdbxweb.Credentials(password);
2017-01-29 23:28:04 +01:00
kdbxweb.Kdbx.loadXml(fileXml, credentials)
2020-06-01 16:53:51 +02:00
.then((db) => {
2016-03-01 04:29:20 +01:00
this.db = db;
this.readModel();
2019-09-17 21:39:06 +02:00
this.set({ active: true, created: true });
logger.info('Imported file ' + this.name + ': ' + logger.ts(ts));
2016-03-01 04:29:20 +01:00
callback();
2017-01-29 23:28:04 +01:00
})
2020-06-01 16:53:51 +02:00
.catch((err) => {
2017-01-29 23:28:04 +01:00
logger.error('Error importing file', err.code, err.message, err);
callback(err);
});
2016-03-01 04:29:20 +01:00
} catch (e) {
logger.error('Error importing file', e, e.code, e.message, e);
callback(e);
}
2019-09-17 21:39:06 +02:00
}
2016-03-01 04:29:20 +01:00
2019-08-18 10:17:09 +02:00
openDemo(callback) {
2017-01-31 07:50:28 +01:00
const password = kdbxweb.ProtectedValue.fromString('demo');
const credentials = new kdbxweb.Credentials(password);
2019-08-18 08:05:38 +02:00
const demoFile = kdbxweb.ByteUtils.arrayToBuffer(
kdbxweb.ByteUtils.base64ToBytes(demoFileData)
);
2020-06-01 16:53:51 +02:00
kdbxweb.Kdbx.load(demoFile, credentials).then((db) => {
2019-08-16 23:05:39 +02:00
this.db = db;
2019-09-17 21:39:06 +02:00
this.name = 'Demo';
2019-08-16 23:05:39 +02:00
this.readModel();
this.setOpenFile({ passwordLength: 4, demo: true });
callback();
});
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
setOpenFile(props) {
2019-09-17 23:44:17 +02:00
this.set({
...props,
2019-09-17 21:39:06 +02:00
active: true,
oldKeyFileName: this.keyFileName,
2015-10-24 21:06:44 +02:00
oldPasswordLength: props.passwordLength,
passwordChanged: false,
keyFileChanged: false
2015-10-24 18:43:30 +02:00
});
2019-09-17 21:39:06 +02:00
this.oldPasswordHash = this.db.credentials.passwordHash;
this.oldKeyFileHash = this.db.credentials.keyFileHash;
this.oldKeyChangeDate = this.db.meta.keyChanged;
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
readModel() {
2017-01-31 07:50:28 +01:00
const groups = new GroupCollection();
2019-08-16 23:05:39 +02:00
this.set(
{
uuid: this.db.getDefaultGroup().uuid.toString(),
2019-08-18 10:17:09 +02:00
groups,
formatVersion: this.db.header.versionMajor,
2019-08-16 23:05:39 +02:00
defaultUser: this.db.meta.defaultUser,
recycleBinEnabled: this.db.meta.recycleBinEnabled,
historyMaxItems: this.db.meta.historyMaxItems,
historyMaxSize: this.db.meta.historyMaxSize,
keyEncryptionRounds: this.db.header.keyEncryptionRounds,
keyChangeForce: this.db.meta.keyChangeForce,
kdfName: this.readKdfName(),
2019-08-16 23:05:39 +02:00
kdfParameters: this.readKdfParams()
},
{ silent: true }
);
2020-06-01 16:53:51 +02:00
this.db.groups.forEach(function (group) {
2017-01-31 07:50:28 +01:00
let groupModel = this.getGroup(this.subId(group.uuid.id));
2015-12-05 14:04:09 +01:00
if (groupModel) {
groupModel.setGroup(group, this);
} else {
groupModel = GroupModel.fromGroup(group, this);
}
2019-09-18 23:37:57 +02:00
groups.push(groupModel);
2015-10-17 23:49:24 +02:00
}, this);
2015-12-05 14:04:09 +01:00
this.buildObjectMap();
2016-08-14 20:53:55 +02:00
this.resolveFieldReferences();
2019-09-17 21:39:06 +02:00
}
2015-12-05 14:04:09 +01:00
readKdfName() {
if (this.db.header.versionMajor === 4 && this.db.header.kdfParameters) {
const kdfParameters = this.db.header.kdfParameters;
let uuid = kdfParameters.get('$UUID');
if (uuid) {
uuid = kdbxweb.ByteUtils.bytesToBase64(uuid);
switch (uuid) {
case kdbxweb.Consts.KdfId.Argon2:
return 'Argon2';
case kdbxweb.Consts.KdfId.Aes:
return 'Aes';
}
}
return 'Unknown';
} else {
return 'Aes';
}
2019-09-17 21:39:06 +02:00
}
2019-08-18 10:17:09 +02:00
readKdfParams() {
2017-01-31 23:18:58 +01:00
const kdfParameters = this.db.header.kdfParameters;
if (!kdfParameters) {
return undefined;
}
let uuid = kdfParameters.get('$UUID');
if (!uuid) {
return undefined;
}
uuid = kdbxweb.ByteUtils.bytesToBase64(uuid);
switch (uuid) {
case kdbxweb.Consts.KdfId.Argon2:
return {
parallelism: kdfParameters.get('P').valueOf(),
iterations: kdfParameters.get('I').valueOf(),
memory: kdfParameters.get('M').valueOf()
};
case kdbxweb.Consts.KdfId.Aes:
return {
rounds: kdfParameters.get('R').valueOf()
};
default:
return undefined;
2017-01-31 23:18:58 +01:00
}
2019-09-17 21:39:06 +02:00
}
2017-01-31 23:18:58 +01:00
2019-08-18 10:17:09 +02:00
subId(id) {
return this.id + ':' + id;
2019-09-17 21:39:06 +02:00
}
2019-08-18 10:17:09 +02:00
buildObjectMap() {
2017-01-31 07:50:28 +01:00
const entryMap = {};
const groupMap = {};
2019-08-16 23:05:39 +02:00
this.forEachGroup(
2020-06-01 16:53:51 +02:00
(group) => {
2019-08-16 23:05:39 +02:00
groupMap[group.id] = group;
2020-06-01 16:53:51 +02:00
group.forEachOwnEntry(null, (entry) => {
2019-08-16 23:05:39 +02:00
entryMap[entry.id] = entry;
});
},
{ includeDisabled: true }
);
2015-12-05 14:04:09 +01:00
this.entryMap = entryMap;
this.groupMap = groupMap;
2019-09-17 21:39:06 +02:00
}
2015-12-05 14:04:09 +01:00
2019-08-18 10:17:09 +02:00
resolveFieldReferences() {
2017-01-31 07:50:28 +01:00
const entryMap = this.entryMap;
2020-06-01 16:53:51 +02:00
Object.keys(entryMap).forEach((e) => {
2016-08-14 20:53:55 +02:00
entryMap[e].resolveFieldReferences();
});
2019-09-17 21:39:06 +02:00
}
2016-08-14 20:53:55 +02:00
fixVersion() {
if (
this.db.meta.generator === 'KdbxWeb' &&
this.db.header.versionMajor === 4 &&
this.db.header.versionMinor === 1
) {
this.db.header.versionMinor = 0;
logger.info('Fixed file version: 4.1 => 4.0');
}
}
2019-08-18 10:17:09 +02:00
reload() {
2015-12-05 14:04:09 +01:00
this.buildObjectMap();
2015-12-13 22:27:26 +01:00
this.readModel();
2019-09-20 23:12:02 +02:00
this.emit('reload', this);
2019-09-17 21:39:06 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
mergeOrUpdate(fileData, remoteKey, callback) {
2017-01-29 23:28:04 +01:00
let credentials;
let credentialsPromise = Promise.resolve();
if (remoteKey) {
credentials = new kdbxweb.Credentials(kdbxweb.ProtectedValue.fromString(''));
2017-01-29 23:28:04 +01:00
credentialsPromise = credentials.ready.then(() => {
2017-01-31 07:50:28 +01:00
const promises = [];
2017-01-29 23:28:04 +01:00
if (remoteKey.password) {
promises.push(credentials.setPassword(remoteKey.password));
} else {
2017-01-29 23:28:04 +01:00
credentials.passwordHash = this.db.credentials.passwordHash;
}
2017-01-29 23:28:04 +01:00
if (remoteKey.keyFileName) {
if (remoteKey.keyFileData) {
promises.push(credentials.setKeyFile(remoteKey.keyFileData));
} else {
credentials.keyFileHash = this.db.credentials.keyFileHash;
}
}
2017-05-17 20:32:46 +02:00
return Promise.all(promises);
2017-01-29 23:28:04 +01:00
});
} else {
credentials = this.db.credentials;
}
2017-01-29 23:28:04 +01:00
credentialsPromise.then(() => {
kdbxweb.Kdbx.load(fileData, credentials)
2020-06-01 16:53:51 +02:00
.then((remoteDb) => {
2019-09-17 21:39:06 +02:00
if (this.modified) {
2017-01-29 23:28:04 +01:00
try {
if (remoteKey && remoteDb.meta.keyChanged > this.db.meta.keyChanged) {
this.db.credentials = remoteDb.credentials;
2019-09-17 21:39:06 +02:00
this.keyFileName = remoteKey.keyFileName || '';
2017-01-29 23:28:04 +01:00
if (remoteKey.password) {
2019-09-17 21:39:06 +02:00
this.passwordLength = remoteKey.password.textLength;
2017-01-29 23:28:04 +01:00
}
}
2017-01-29 23:28:04 +01:00
this.db.merge(remoteDb);
} catch (e) {
logger.error('File merge error', e);
return callback(e);
}
2017-01-29 23:28:04 +01:00
} else {
this.db = remoteDb;
2015-12-10 22:31:47 +01:00
}
2019-09-17 21:39:06 +02:00
this.dirty = true;
2017-01-29 23:28:04 +01:00
this.reload();
callback();
})
2020-06-01 16:53:51 +02:00
.catch((err) => {
2017-01-29 23:28:04 +01:00
logger.error('Error opening file to merge', err.code, err.message, err);
callback(err);
});
2016-07-17 13:30:38 +02:00
});
2019-09-17 21:39:06 +02:00
}
2015-12-10 20:44:02 +01:00
2019-08-18 10:17:09 +02:00
getLocalEditState() {
2015-12-10 22:31:47 +01:00
return this.db.getLocalEditState();
2019-09-17 21:39:06 +02:00
}
2015-12-10 22:31:47 +01:00
2019-08-18 10:17:09 +02:00
setLocalEditState(editState) {
2015-12-10 22:31:47 +01:00
this.db.setLocalEditState(editState);
2019-09-17 21:39:06 +02:00
}
2015-12-10 22:31:47 +01:00
2019-08-18 10:17:09 +02:00
close() {
2015-11-17 21:57:32 +01:00
this.set({
keyFileName: '',
passwordLength: 0,
modified: false,
dirty: false,
2019-09-17 21:39:06 +02:00
active: false,
2015-11-17 21:57:32 +01:00
created: false,
groups: null,
passwordChanged: false,
keyFileChanged: false,
syncing: false
});
if (this.chalResp && !AppSettingsModel.yubiKeyRememberChalResp) {
2020-05-30 12:45:56 +02:00
ChalRespCalculator.clearCache(this.chalResp);
}
2019-09-17 21:39:06 +02:00
}
2015-11-17 21:57:32 +01:00
2019-08-18 10:17:09 +02:00
getEntry(id) {
2015-12-05 14:04:09 +01:00
return this.entryMap[id];
2019-09-17 21:39:06 +02:00
}
2015-12-05 14:04:09 +01:00
2019-08-18 10:17:09 +02:00
getGroup(id) {
2015-12-05 14:04:09 +01:00
return this.groupMap[id];
2019-09-17 21:39:06 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
forEachEntry(filter, callback) {
2017-01-31 07:50:28 +01:00
let top = this;
2015-10-17 23:49:24 +02:00
if (filter.trash) {
2019-08-18 08:05:38 +02:00
top = this.getGroup(
this.db.meta.recycleBinUuid ? this.subId(this.db.meta.recycleBinUuid.id) : null
);
2015-10-17 23:49:24 +02:00
} else if (filter.group) {
top = this.getGroup(filter.group);
}
if (top) {
if (top.forEachOwnEntry) {
top.forEachOwnEntry(filter, callback);
}
if (!filter.group || filter.subGroups) {
2020-06-01 16:53:51 +02:00
top.forEachGroup((group) => {
group.forEachOwnEntry(filter, callback);
2017-01-29 19:03:38 +01:00
}, filter);
}
2015-10-17 23:49:24 +02:00
}
2019-09-17 21:39:06 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
forEachGroup(callback, filter) {
2020-06-01 16:53:51 +02:00
this.groups.forEach((group) => {
2015-10-17 23:49:24 +02:00
if (callback(group) !== false) {
2017-01-29 19:03:38 +01:00
group.forEachGroup(callback, filter);
2015-10-17 23:49:24 +02:00
}
});
2019-09-17 21:39:06 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
getTrashGroup() {
2019-08-18 08:05:38 +02:00
return this.db.meta.recycleBinEnabled
? this.getGroup(this.subId(this.db.meta.recycleBinUuid.id))
: null;
2019-09-17 21:39:06 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
getEntryTemplatesGroup() {
2019-08-18 08:05:38 +02:00
return this.db.meta.entryTemplatesGroup
? this.getGroup(this.subId(this.db.meta.entryTemplatesGroup.id))
: null;
2019-09-17 21:39:06 +02:00
}
2017-05-03 20:44:16 +02:00
2019-08-18 10:17:09 +02:00
createEntryTemplatesGroup() {
2019-09-18 23:37:57 +02:00
const rootGroup = this.groups[0];
2017-05-03 20:44:16 +02:00
const templatesGroup = GroupModel.newGroup(rootGroup, this);
templatesGroup.setName('Templates');
this.db.meta.entryTemplatesGroup = templatesGroup.group.uuid;
this.reload();
return templatesGroup;
2019-09-17 21:39:06 +02:00
}
2017-05-03 20:44:16 +02:00
2019-08-18 10:17:09 +02:00
setModified() {
2019-09-17 21:39:06 +02:00
if (!this.demo) {
this.set({ modified: true, dirty: true });
2019-09-16 22:57:56 +02:00
Events.emit('file-modified');
2015-10-17 23:49:24 +02:00
}
2019-09-17 21:39:06 +02:00
}
2015-10-18 16:02:00 +02:00
2019-08-18 10:17:09 +02:00
getData(cb) {
2015-11-22 09:45:40 +01:00
this.db.cleanup({
historyRules: true,
2015-12-17 21:22:36 +01:00
customIcons: true,
binaries: true
2015-11-22 09:45:40 +01:00
});
2015-12-17 21:22:36 +01:00
this.db.cleanup({ binaries: true });
2019-08-16 23:05:39 +02:00
this.db
.save()
2020-06-01 16:53:51 +02:00
.then((data) => {
2017-01-29 23:28:04 +01:00
cb(data);
})
2020-06-01 16:53:51 +02:00
.catch((err) => {
2019-09-17 21:39:06 +02:00
logger.error('Error saving file', this.name, err);
2017-01-29 23:28:04 +01:00
cb(undefined, err);
});
2019-09-17 21:39:06 +02:00
}
2015-10-18 16:02:00 +02:00
2019-08-18 10:17:09 +02:00
getXml(cb) {
2020-06-01 16:53:51 +02:00
this.db.saveXml(true).then((xml) => {
2019-08-16 23:05:39 +02:00
cb(xml);
});
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-09-08 20:28:02 +02:00
getHtml(cb) {
cb(
KdbxToHtml.convert(this.db, {
2019-09-17 21:39:06 +02:00
name: this.name
2019-09-08 20:28:02 +02:00
})
);
2019-09-17 21:39:06 +02:00
}
2019-09-08 20:28:02 +02:00
2019-08-18 10:17:09 +02:00
getKeyFileHash() {
2017-01-31 07:50:28 +01:00
const hash = this.db.credentials.keyFileHash;
2016-02-14 12:20:21 +01:00
return hash ? kdbxweb.ByteUtils.bytesToBase64(hash.getBinary()) : null;
2019-09-17 21:39:06 +02:00
}
2016-02-14 12:20:21 +01:00
2019-08-18 10:17:09 +02:00
forEachEntryTemplate(callback) {
2017-05-02 21:22:08 +02:00
if (!this.db.meta.entryTemplatesGroup) {
return;
}
const group = this.getGroup(this.subId(this.db.meta.entryTemplatesGroup.id));
if (!group) {
return;
}
group.forEachOwnEntry({}, callback);
2019-09-17 21:39:06 +02:00
}
2017-05-02 21:22:08 +02:00
2019-08-18 10:17:09 +02:00
setSyncProgress() {
2015-12-10 22:31:47 +01:00
this.set({ syncing: true });
2019-09-17 21:39:06 +02:00
}
2015-12-10 22:31:47 +01:00
2020-05-30 23:28:33 +02:00
setSyncComplete(path, storage, error) {
if (!error) {
this.db.removeLocalEditState();
}
2019-09-17 21:39:06 +02:00
const modified = this.modified && !!error;
2015-12-10 22:31:47 +01:00
this.set({
created: false,
2019-09-17 21:39:06 +02:00
path: path || this.path,
storage: storage || this.storage,
2019-08-18 10:17:09 +02:00
modified,
2020-05-30 23:28:33 +02:00
dirty: error ? this.dirty : false,
2015-12-10 22:31:47 +01:00
syncing: false,
syncError: error
});
2019-09-17 21:39:06 +02:00
const shouldResetFingerprint = this.passwordChanged && this.fingerprint;
if (shouldResetFingerprint && !error) {
2019-09-17 21:39:06 +02:00
this.fingerprint = null;
}
2019-09-17 21:39:06 +02:00
if (!this.open) {
2016-03-19 12:17:52 +01:00
return;
}
2019-09-17 21:39:06 +02:00
this.setOpenFile({ passwordLength: this.passwordLength });
2020-06-01 16:53:51 +02:00
this.forEachEntry({ includeDisabled: true }, (entry) => entry.setSaved());
2019-09-17 21:39:06 +02:00
}
2015-10-24 21:06:44 +02:00
2019-08-18 10:17:09 +02:00
setPassword(password) {
2015-10-24 18:43:30 +02:00
this.db.credentials.setPassword(password);
this.db.meta.keyChanged = new Date();
2016-01-16 15:19:33 +01:00
this.set({ passwordLength: password.textLength, passwordChanged: true });
2015-10-24 18:43:30 +02:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
resetPassword() {
2019-09-17 21:39:06 +02:00
this.db.credentials.passwordHash = this.oldPasswordHash;
if (this.db.credentials.keyFileHash === this.oldKeyFileHash) {
this.db.meta.keyChanged = this.oldKeyChangeDate;
2015-10-24 18:43:30 +02:00
}
2019-09-17 21:39:06 +02:00
this.set({ passwordLength: this.oldPasswordLength, passwordChanged: false });
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
setKeyFile(keyFile, keyFileName) {
2015-10-24 18:43:30 +02:00
this.db.credentials.setKeyFile(keyFile);
this.db.meta.keyChanged = new Date();
2019-08-18 10:17:09 +02:00
this.set({ keyFileName, keyFileChanged: true });
2015-10-24 18:43:30 +02:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
generateAndSetKeyFile() {
2017-01-31 07:50:28 +01:00
const keyFile = kdbxweb.Credentials.createRandomKeyFile();
const keyFileName = 'Generated';
2015-10-24 18:43:30 +02:00
this.setKeyFile(keyFile, keyFileName);
return keyFile;
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
resetKeyFile() {
2019-09-17 21:39:06 +02:00
this.db.credentials.keyFileHash = this.oldKeyFileHash;
if (this.db.credentials.passwordHash === this.oldPasswordHash) {
this.db.meta.keyChanged = this.oldKeyChangeDate;
2015-10-24 18:43:30 +02:00
}
2019-09-17 21:39:06 +02:00
this.set({ keyFileName: this.oldKeyFileName, keyFileChanged: false });
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
removeKeyFile() {
2015-10-24 18:43:30 +02:00
this.db.credentials.keyFileHash = null;
2019-09-17 21:39:06 +02:00
const changed = !!this.oldKeyFileHash;
if (!changed && this.db.credentials.passwordHash === this.oldPasswordHash) {
this.db.meta.keyChanged = this.oldKeyChangeDate;
2015-10-24 18:43:30 +02:00
}
this.set({ keyFileName: '', keyFilePath: '', keyFileChanged: changed });
2019-09-16 22:57:56 +02:00
Events.emit('unset-keyfile', this.id);
2016-02-14 12:20:21 +01:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
isKeyChangePending(force) {
2016-07-03 18:46:43 +02:00
if (!this.db.meta.keyChanged) {
return false;
}
2017-01-31 07:50:28 +01:00
const expiryDays = force ? this.db.meta.keyChangeForce : this.db.meta.keyChangeRec;
2016-07-03 18:46:43 +02:00
if (!expiryDays || expiryDays < 0 || isNaN(expiryDays)) {
return false;
}
2017-01-31 07:50:28 +01:00
const daysDiff = (Date.now() - this.db.meta.keyChanged) / 1000 / 3600 / 24;
2016-07-03 18:46:43 +02:00
return daysDiff > expiryDays;
2019-09-17 21:39:06 +02:00
}
2016-07-03 18:46:43 +02:00
2020-05-31 14:13:08 +02:00
setChallengeResponse(chalResp) {
if (this.chalResp && !AppSettingsModel.yubiKeyRememberChalResp) {
ChalRespCalculator.clearCache(this.chalResp);
}
this.db.credentials.setChallengeResponse(ChalRespCalculator.build(chalResp));
this.db.meta.keyChanged = new Date();
this.chalResp = chalResp;
this.setModified();
}
2019-08-18 10:17:09 +02:00
setKeyChange(force, days) {
2016-07-03 18:46:43 +02:00
if (isNaN(days) || !days || days < 0) {
days = -1;
}
2017-01-31 07:50:28 +01:00
const prop = force ? 'keyChangeForce' : 'keyChangeRec';
2016-07-03 18:46:43 +02:00
this.db.meta[prop] = days;
2019-09-17 21:39:06 +02:00
this[prop] = days;
2016-07-03 18:46:43 +02:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2016-07-03 18:46:43 +02:00
2019-08-18 10:17:09 +02:00
setName(name) {
2015-10-24 18:43:30 +02:00
this.db.meta.name = name;
this.db.meta.nameChanged = new Date();
2019-09-17 21:39:06 +02:00
this.name = name;
2019-09-18 23:37:57 +02:00
this.groups[0].setName(name);
2015-10-24 18:43:30 +02:00
this.setModified();
2015-12-13 15:59:41 +01:00
this.reload();
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
setDefaultUser(defaultUser) {
2015-10-24 18:43:30 +02:00
this.db.meta.defaultUser = defaultUser;
this.db.meta.defaultUserChanged = new Date();
2019-09-17 21:39:06 +02:00
this.defaultUser = defaultUser;
2015-10-24 18:43:30 +02:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
setRecycleBinEnabled(enabled) {
2015-10-24 18:43:30 +02:00
enabled = !!enabled;
this.db.meta.recycleBinEnabled = enabled;
if (enabled) {
this.db.createRecycleBin();
}
2019-09-28 19:26:09 +02:00
this.recycleBinEnabled = enabled;
2015-10-24 18:43:30 +02:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
setHistoryMaxItems(count) {
2015-10-24 18:43:30 +02:00
this.db.meta.historyMaxItems = count;
2019-09-17 21:39:06 +02:00
this.historyMaxItems = count;
2015-10-24 18:43:30 +02:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
setHistoryMaxSize(size) {
2015-10-24 18:43:30 +02:00
this.db.meta.historyMaxSize = size;
2019-09-17 21:39:06 +02:00
this.historyMaxSize = size;
2015-10-24 18:43:30 +02:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2015-10-24 18:43:30 +02:00
2019-08-18 10:17:09 +02:00
setKeyEncryptionRounds(rounds) {
2015-10-24 18:43:30 +02:00
this.db.header.keyEncryptionRounds = rounds;
2019-09-17 21:39:06 +02:00
this.keyEncryptionRounds = rounds;
2015-10-24 18:43:30 +02:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2015-11-08 22:25:00 +01:00
2019-08-18 10:17:09 +02:00
setKdfParameter(field, value) {
2017-01-31 23:18:58 +01:00
const ValueType = kdbxweb.VarDictionary.ValueType;
switch (field) {
case 'memory':
this.db.header.kdfParameters.set('M', ValueType.UInt64, kdbxweb.Int64.from(value));
break;
case 'iterations':
this.db.header.kdfParameters.set('I', ValueType.UInt64, kdbxweb.Int64.from(value));
break;
case 'parallelism':
this.db.header.kdfParameters.set('P', ValueType.UInt32, value);
break;
case 'rounds':
this.db.header.kdfParameters.set('R', ValueType.UInt32, value);
break;
2017-01-31 23:18:58 +01:00
default:
return;
}
2019-09-17 21:39:06 +02:00
this.kdfParameters = this.readKdfParams();
2017-01-31 23:18:58 +01:00
this.setModified();
2019-09-17 21:39:06 +02:00
}
2017-01-31 23:18:58 +01:00
2019-08-18 10:17:09 +02:00
emptyTrash() {
2017-01-31 07:50:28 +01:00
const trashGroup = this.getTrashGroup();
2015-11-08 22:25:00 +01:00
if (trashGroup) {
2017-01-31 07:50:28 +01:00
let modified = false;
2019-08-16 23:05:39 +02:00
trashGroup
.getOwnSubGroups()
.slice()
2020-06-01 16:53:51 +02:00
.forEach(function (group) {
2019-08-16 23:05:39 +02:00
this.db.move(group, null);
modified = true;
}, this);
2020-06-01 16:53:51 +02:00
trashGroup.group.entries.slice().forEach(function (entry) {
2015-11-08 22:25:00 +01:00
this.db.move(entry, null);
modified = true;
2015-11-08 22:25:00 +01:00
}, this);
2019-09-18 23:37:57 +02:00
trashGroup.items.length = 0;
trashGroup.entries.length = 0;
if (modified) {
this.setModified();
}
2015-11-08 22:25:00 +01:00
}
2019-09-17 21:39:06 +02:00
}
2015-11-22 09:21:12 +01:00
2019-08-18 10:17:09 +02:00
getCustomIcons() {
2020-06-01 16:53:51 +02:00
return mapObject(this.db.meta.customIcons, (customIcon) =>
2019-09-15 08:11:11 +02:00
IconUrlFormat.toDataUrl(customIcon)
);
2019-09-17 21:39:06 +02:00
}
2015-11-22 09:21:12 +01:00
2019-08-18 10:17:09 +02:00
addCustomIcon(iconData) {
2017-01-31 07:50:28 +01:00
const uuid = kdbxweb.KdbxUuid.random();
2019-08-18 08:05:38 +02:00
this.db.meta.customIcons[uuid] = kdbxweb.ByteUtils.arrayToBuffer(
kdbxweb.ByteUtils.base64ToBytes(iconData)
);
return uuid.toString();
2019-09-17 21:39:06 +02:00
}
2016-04-17 22:02:39 +02:00
2019-08-18 10:17:09 +02:00
renameTag(from, to) {
2020-06-01 16:53:51 +02:00
this.forEachEntry({}, (entry) => entry.renameTag(from, to));
2019-09-17 21:39:06 +02:00
}
setFormatVersion(version) {
this.db.setVersion(version);
this.setModified();
this.readModel();
2019-09-17 21:39:06 +02:00
}
setKdf(kdfName) {
const kdfParameters = this.db.header.kdfParameters;
if (!kdfParameters) {
throw new Error('Cannot set KDF on this version');
}
switch (kdfName) {
case 'Aes':
this.db.setKdf(kdbxweb.Consts.KdfId.Aes);
break;
case 'Argon2':
this.db.setKdf(kdbxweb.Consts.KdfId.Argon2);
break;
default:
throw new Error('Bad KDF name');
}
this.setModified();
this.readModel();
2015-10-17 23:49:24 +02:00
}
2019-09-17 21:39:06 +02:00
static createKeyFileWithHash(hash) {
return kdbxweb.Credentials.createKeyFileWithHash(hash);
}
}
FileModel.defineModelProperties({
id: '',
uuid: '',
name: '',
db: null,
entryMap: null,
groupMap: null,
keyFileName: '',
keyFilePath: null,
2020-05-30 08:10:19 +02:00
chalResp: null,
2019-09-17 21:39:06 +02:00
passwordLength: 0,
path: '',
opts: null,
storage: null,
modified: false,
dirty: false,
active: false,
created: false,
demo: false,
groups: null,
oldPasswordLength: 0,
oldKeyFileName: '',
passwordChanged: false,
keyFileChanged: false,
keyChangeForce: -1,
syncing: false,
syncError: null,
syncDate: null,
backup: null,
formatVersion: null,
defaultUser: null,
recycleBinEnabled: null,
historyMaxItems: null,
historyMaxSize: null,
keyEncryptionRounds: null,
kdfName: null,
kdfParameters: null,
fingerprint: null,
oldPasswordHash: null,
oldKeyFileHash: null,
oldKeyChangeDate: null
});
2016-02-14 12:20:21 +01:00
2019-09-15 14:16:32 +02:00
export { FileModel };