2020-11-02 09:50:27 +05:00
|
|
|
import { EV } from "../common";
|
2020-03-19 11:30:05 +05:00
|
|
|
import getId from "../utils/id";
|
2020-04-12 11:04:30 +05:00
|
|
|
|
2020-10-03 12:04:04 +05:00
|
|
|
const ERASE_TIME = 1000 * 60 * 30;
|
2020-03-07 12:29:55 +05:00
|
|
|
export default class Vault {
|
|
|
|
|
/**
|
|
|
|
|
*
|
2020-04-16 02:36:09 +05:00
|
|
|
* @param {import('./index').default} db
|
2020-03-07 12:29:55 +05:00
|
|
|
*/
|
2020-04-16 02:36:09 +05:00
|
|
|
constructor(db) {
|
|
|
|
|
this._db = db;
|
|
|
|
|
this._context = db.context;
|
2020-03-08 11:33:55 +05:00
|
|
|
this._key = "Notesnook";
|
|
|
|
|
this._password = "";
|
2020-03-10 13:54:34 +05:00
|
|
|
this.ERRORS = {
|
|
|
|
|
noVault: "ERR_NO_VAULT",
|
|
|
|
|
vaultLocked: "ERR_VAULT_LOCKED",
|
2020-04-13 11:13:32 +05:00
|
|
|
wrongPassword: "ERR_WRONG_PASSWORD",
|
2020-03-10 13:54:34 +05:00
|
|
|
};
|
2020-11-02 09:50:27 +05:00
|
|
|
EV.subscribe("user:loggedOut", () => {
|
2020-09-19 11:33:31 +05:00
|
|
|
this._password = "";
|
|
|
|
|
});
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/**
|
|
|
|
|
* Creates a new vault (replacing if any older exists)
|
|
|
|
|
* @param {string} password The password
|
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
*/
|
2020-03-07 12:29:55 +05:00
|
|
|
async create(password) {
|
2020-04-15 14:00:05 +05:00
|
|
|
const vaultKey = await this._context.read("vaultKey");
|
|
|
|
|
if (!vaultKey || !vaultKey.cipher || !vaultKey.iv) {
|
2020-04-13 11:13:32 +05:00
|
|
|
const encryptedData = await this._context.encrypt(
|
|
|
|
|
{ password },
|
|
|
|
|
this._key
|
|
|
|
|
);
|
2020-04-15 14:00:05 +05:00
|
|
|
await this._context.write("vaultKey", encryptedData);
|
2020-03-08 11:33:55 +05:00
|
|
|
this._password = password;
|
2020-10-03 12:04:04 +05:00
|
|
|
this._startEraser();
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/**
|
|
|
|
|
* Unlocks the vault with the given password
|
|
|
|
|
* @param {string} password The password
|
|
|
|
|
* @throws ERR_NO_VAULT | ERR_WRONG_PASSWORD
|
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
*/
|
2020-03-07 12:29:55 +05:00
|
|
|
async unlock(password) {
|
2020-04-15 14:00:05 +05:00
|
|
|
const vaultKey = await this._context.read("vaultKey");
|
2020-04-16 02:36:09 +05:00
|
|
|
if (!(await this._exists(vaultKey))) throw new Error(this.ERRORS.noVault);
|
2020-03-07 12:29:55 +05:00
|
|
|
var data;
|
|
|
|
|
try {
|
2020-04-15 14:00:05 +05:00
|
|
|
data = await this._context.decrypt({ password }, vaultKey);
|
2020-03-07 12:29:55 +05:00
|
|
|
} catch (e) {
|
2020-03-10 13:52:20 +05:00
|
|
|
throw new Error(this.ERRORS.wrongPassword);
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
2020-03-08 11:33:55 +05:00
|
|
|
if (data !== this._key) {
|
2020-03-10 13:52:20 +05:00
|
|
|
throw new Error(this.ERRORS.wrongPassword);
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
2020-03-08 11:33:55 +05:00
|
|
|
this._password = password;
|
2020-10-03 12:04:04 +05:00
|
|
|
this._startEraser();
|
2020-03-07 12:29:55 +05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-03 12:04:04 +05:00
|
|
|
_startEraser() {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this._password = "";
|
|
|
|
|
}, ERASE_TIME);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/**
|
|
|
|
|
* Locks (add to vault) a note
|
|
|
|
|
* @param {string} noteId The id of the note to lock
|
|
|
|
|
*/
|
|
|
|
|
async add(noteId) {
|
2020-03-07 12:29:55 +05:00
|
|
|
await this._check();
|
2020-04-16 02:36:09 +05:00
|
|
|
const note = this._db.notes.note(noteId).data;
|
|
|
|
|
await this._lockNote(noteId, note);
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/**
|
|
|
|
|
* Permanently unlocks (remove from vault) a note
|
|
|
|
|
* @param {string} noteId The note id
|
|
|
|
|
* @param {string} password The password to unlock note with
|
|
|
|
|
*/
|
|
|
|
|
async remove(noteId, password) {
|
2020-03-07 12:29:55 +05:00
|
|
|
if (await this.unlock(password)) {
|
2020-04-16 02:36:09 +05:00
|
|
|
const note = this._db.notes.note(noteId).data;
|
2020-03-08 11:33:55 +05:00
|
|
|
await this._unlockNote(note, true);
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/**
|
|
|
|
|
* Temporarily unlock (open) a note
|
|
|
|
|
* @param {string} noteId The note id
|
|
|
|
|
* @param {string} password The password to open note with
|
|
|
|
|
*/
|
|
|
|
|
async open(noteId, password) {
|
2020-03-07 12:29:55 +05:00
|
|
|
if (await this.unlock(password)) {
|
2020-04-16 02:36:09 +05:00
|
|
|
const note = this._db.notes.note(noteId).data;
|
2020-03-08 11:33:55 +05:00
|
|
|
return this._unlockNote(note, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/**
|
|
|
|
|
* Saves a note into the vault
|
|
|
|
|
* @param {{Object}} note The note to save into the vault
|
|
|
|
|
*/
|
2020-03-08 11:33:55 +05:00
|
|
|
async save(note) {
|
|
|
|
|
if (!note) return;
|
2020-09-17 19:58:33 +05:00
|
|
|
//await this._check();
|
2020-03-19 11:30:05 +05:00
|
|
|
let id = note.id || getId();
|
2020-03-08 11:33:55 +05:00
|
|
|
return await this._lockNote(id, note);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
// Private & internal methods
|
|
|
|
|
|
|
|
|
|
/** @private */
|
|
|
|
|
async _exists(vaultKey) {
|
|
|
|
|
if (!vaultKey) vaultKey = await this._context.read("vaultKey");
|
|
|
|
|
return vaultKey && vaultKey.cipher && vaultKey.iv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @private */
|
2020-09-17 19:58:33 +05:00
|
|
|
_locked() {
|
2020-04-16 02:36:09 +05:00
|
|
|
return !this._password || !this._password.length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @private */
|
|
|
|
|
async _check() {
|
|
|
|
|
if (!(await this._exists())) {
|
|
|
|
|
throw new Error(this.ERRORS.noVault);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-17 19:58:33 +05:00
|
|
|
if (this._locked()) {
|
2020-04-16 02:36:09 +05:00
|
|
|
throw new Error(this.ERRORS.vaultLocked);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @private */
|
2020-03-19 11:30:05 +05:00
|
|
|
async _encryptContent(content, ids) {
|
2020-09-17 19:58:33 +05:00
|
|
|
let { text, delta } = content;
|
2020-03-19 11:30:05 +05:00
|
|
|
let { deltaId, textId } = ids;
|
2020-03-08 11:33:55 +05:00
|
|
|
|
2020-03-19 11:30:05 +05:00
|
|
|
if (!delta.ops) delta = await this._db.delta.get(deltaId);
|
|
|
|
|
if (text === textId) text = await this._db.text.get(textId);
|
2020-03-08 11:33:55 +05:00
|
|
|
|
2020-09-17 19:58:33 +05:00
|
|
|
[text, delta] = await Promise.all([
|
|
|
|
|
this._context.encrypt({ password: this._password }, text),
|
|
|
|
|
this._context.encrypt(
|
|
|
|
|
{ password: this._password },
|
|
|
|
|
JSON.stringify(delta)
|
|
|
|
|
),
|
|
|
|
|
]);
|
|
|
|
|
await Promise.all([
|
|
|
|
|
this._db.text.add({ id: textId, data: text }),
|
|
|
|
|
this._db.delta.add({ id: deltaId, data: delta }),
|
|
|
|
|
]);
|
2020-03-08 11:33:55 +05:00
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/** @private */
|
2020-03-19 11:30:05 +05:00
|
|
|
async _decryptContent(content) {
|
2020-09-17 19:58:33 +05:00
|
|
|
let { text, delta } = content;
|
2020-03-19 11:30:05 +05:00
|
|
|
|
2020-09-17 19:58:33 +05:00
|
|
|
[text, delta] = await Promise.all([
|
|
|
|
|
this._db.text.get(text),
|
|
|
|
|
this._db.text.get(delta),
|
|
|
|
|
]);
|
2020-03-19 11:30:05 +05:00
|
|
|
|
2020-09-17 19:58:33 +05:00
|
|
|
[text, delta] = await Promise.all([
|
|
|
|
|
this._context.decrypt({ password: this._password }, text),
|
|
|
|
|
this._context.decrypt({ password: this._password }, delta),
|
|
|
|
|
]);
|
2020-03-19 11:30:05 +05:00
|
|
|
|
2020-09-17 19:58:33 +05:00
|
|
|
delta = JSON.parse(delta);
|
2020-04-15 12:30:36 +05:00
|
|
|
|
2020-03-19 11:30:05 +05:00
|
|
|
return {
|
|
|
|
|
delta,
|
2020-04-13 11:13:32 +05:00
|
|
|
text,
|
2020-03-19 11:30:05 +05:00
|
|
|
};
|
2020-03-08 11:33:55 +05:00
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/** @private */
|
2020-03-08 11:33:55 +05:00
|
|
|
async _lockNote(id, note) {
|
2020-04-15 17:16:13 +05:00
|
|
|
let oldNote = this._db.notes.note(id);
|
2020-03-19 11:30:05 +05:00
|
|
|
|
|
|
|
|
let deltaId = 0;
|
|
|
|
|
let textId = 0;
|
|
|
|
|
|
2020-04-15 17:16:13 +05:00
|
|
|
if (oldNote && oldNote.data.content) {
|
|
|
|
|
deltaId = oldNote.data.content.delta;
|
|
|
|
|
textId = oldNote.data.content.text;
|
2020-03-19 11:30:05 +05:00
|
|
|
}
|
2020-03-08 11:33:55 +05:00
|
|
|
|
2020-03-19 11:30:05 +05:00
|
|
|
await this._encryptContent(note.content, { textId, deltaId });
|
2020-03-08 11:33:55 +05:00
|
|
|
|
|
|
|
|
return await this._db.notes.add({
|
|
|
|
|
id,
|
2020-04-13 11:13:32 +05:00
|
|
|
locked: true,
|
2020-04-15 17:16:13 +05:00
|
|
|
headline: "",
|
2020-03-08 11:33:55 +05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/** @private */
|
2020-03-08 11:33:55 +05:00
|
|
|
async _unlockNote(note, perm = false) {
|
|
|
|
|
if (!note.locked) return;
|
|
|
|
|
|
2020-03-19 11:30:05 +05:00
|
|
|
let { delta, text } = await this._decryptContent(note.content);
|
2020-03-08 11:33:55 +05:00
|
|
|
|
|
|
|
|
if (perm) {
|
|
|
|
|
await this._db.notes.add({
|
|
|
|
|
id: note.id,
|
2020-04-13 11:13:32 +05:00
|
|
|
locked: false,
|
2020-04-16 12:31:32 +05:00
|
|
|
content: {
|
|
|
|
|
delta,
|
|
|
|
|
text,
|
|
|
|
|
},
|
2020-03-08 11:33:55 +05:00
|
|
|
});
|
2020-03-19 11:30:05 +05:00
|
|
|
return;
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
2020-03-08 11:33:55 +05:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...note,
|
2020-04-15 12:40:45 +05:00
|
|
|
content: { delta, text },
|
2020-03-08 11:33:55 +05:00
|
|
|
};
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
2020-04-16 02:36:09 +05:00
|
|
|
|
|
|
|
|
/** @inner */
|
|
|
|
|
async _getKey() {
|
|
|
|
|
if (await this._exists()) return await this._context.read("vaultKey");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @inner */
|
|
|
|
|
async _setKey(vaultKey) {
|
|
|
|
|
if (!vaultKey) return;
|
|
|
|
|
await this._context.write("vaultKey", vaultKey);
|
|
|
|
|
}
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|