Files
notesnook/packages/core/api/vault.js

269 lines
6.8 KiB
JavaScript
Raw Normal View History

import { CHECK_IDS, EV, EVENTS, sendCheckUserStatusEvent } from "../common";
const ERASE_TIME = 1000 * 60 * 30;
var ERASER_TIMEOUT = null;
export default class Vault {
get _password() {
return this._vaultPassword;
}
set _password(value) {
this._vaultPassword = value;
if (value) {
this._startEraser();
}
}
_startEraser() {
clearTimeout(ERASER_TIMEOUT);
ERASER_TIMEOUT = setTimeout(() => {
this._password = null;
}, ERASE_TIME);
}
/**
*
2020-04-16 02:36:09 +05:00
* @param {import('./index').default} db
*/
2020-04-16 02:36:09 +05:00
constructor(db) {
this._db = db;
this._storage = db.storage;
this._key = "svvaads1212#2123";
this._vaultPassword = null;
this.ERRORS = {
noVault: "ERR_NO_VAULT",
vaultLocked: "ERR_VAULT_LOCKED",
wrongPassword: "ERR_WRONG_PASSWORD",
};
EV.subscribe(EVENTS.userLoggedOut, () => {
this._password = null;
});
}
2020-04-16 02:36:09 +05:00
/**
* Creates a new vault
2020-04-16 02:36:09 +05:00
* @param {string} password The password
* @returns {Promise<Boolean>}
2020-04-16 02:36:09 +05:00
*/
async create(password) {
if (!(await sendCheckUserStatusEvent(CHECK_IDS.vaultAdd))) return;
const vaultKey = await this._storage.read("vaultKey");
2020-04-15 14:00:05 +05:00
if (!vaultKey || !vaultKey.cipher || !vaultKey.iv) {
const encryptedData = await this._storage.encrypt(
{ password },
this._key
);
await this._storage.write("vaultKey", encryptedData);
2020-03-08 11:33:55 +05:00
this._password = password;
}
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 {Promise<Boolean>}
2020-04-16 02:36:09 +05:00
*/
async unlock(password) {
const vaultKey = await this._storage.read("vaultKey");
if (!(await this.exists(vaultKey))) throw new Error(this.ERRORS.noVault);
try {
await this._storage.decrypt({ password }, vaultKey);
} catch (e) {
2020-03-10 13:52:20 +05:00
throw new Error(this.ERRORS.wrongPassword);
}
2020-03-08 11:33:55 +05:00
this._password = password;
return true;
}
async changePassword(oldPassword, newPassword) {
if (await this.unlock(oldPassword)) {
const lockedNotes = this._db.notes.locked;
for (var note of lockedNotes) {
await this._unlockNote(note, oldPassword, true);
}
await this._storage.remove("vaultKey");
await this.create(newPassword);
for (var note of lockedNotes) {
await this._lockNote(note, newPassword);
}
}
}
async clear(password) {
if (await this.unlock(password)) {
for (var note of this._db.notes.locked) {
await this._unlockNote(note, password, true);
}
}
}
async delete(deleteAllLockedNotes = false) {
if (deleteAllLockedNotes) {
await this._db.notes.remove(
...this._db.notes.locked.map((note) => note.id)
);
}
await this._storage.remove("vaultKey");
this._password = null;
}
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-12-05 13:19:41 +05:00
if (!(await sendCheckUserStatusEvent(CHECK_IDS.vaultAdd))) return;
await this._check();
await this._lockNote({ id: noteId }, this._password);
}
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) {
const note = this._db.notes.note(noteId).data;
await this._unlockNote(note, password, true);
}
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) {
const note = this._db.notes.note(noteId).data;
const unlockedNote = await this._unlockNote(note, password, false);
this._password = password;
return unlockedNote;
2020-03-08 11:33:55 +05:00
}
2020-04-16 02:36:09 +05:00
/**
* Saves a note in the vault
2020-04-16 02:36:09 +05:00
* @param {{Object}} note The note to save into the vault
*/
2020-03-08 11:33:55 +05:00
async save(note) {
if (!note) return;
// roll over erase timer
this._startEraser();
return await this._lockNote(note, this._password);
2020-03-08 11:33:55 +05:00
}
async exists(vaultKey) {
if (!vaultKey) vaultKey = await this._storage.read("vaultKey");
2020-04-16 02:36:09 +05:00
return vaultKey && vaultKey.cipher && vaultKey.iv;
}
// Private & internal methods
2020-04-16 02:36:09 +05:00
/** @private */
_locked() {
2020-04-16 02:36:09 +05:00
return !this._password || !this._password.length;
}
/** @private */
async _check() {
if (!(await this.exists())) {
2020-04-16 02:36:09 +05:00
throw new Error(this.ERRORS.noVault);
}
if (this._locked()) {
2020-04-16 02:36:09 +05:00
throw new Error(this.ERRORS.vaultLocked);
}
}
/** @private */
async _encryptContent(contentId, content, type, password) {
let encryptedContent = await this._storage.encrypt(
{ password },
JSON.stringify(content)
);
await this._db.content.add({ id: contentId, data: encryptedContent, type });
2020-03-08 11:33:55 +05:00
}
2020-04-16 02:36:09 +05:00
/** @private */
async _decryptContent(contentId, password) {
let encryptedContent = await this._db.content.raw(contentId);
2020-03-19 11:30:05 +05:00
let decryptedContent = await this._storage.decrypt(
{ password },
encryptedContent.data
);
return { type: encryptedContent.type, data: JSON.parse(decryptedContent) };
2020-03-08 11:33:55 +05:00
}
2020-04-16 02:36:09 +05:00
/** @private */
async _lockNote(note, password) {
let { id, content: { type, data } = {}, contentId } = note;
2021-09-18 08:52:08 +05:00
// Case: when note is being newly locked
if (!data || !type || !contentId) {
2021-02-22 09:42:34 +05:00
note = this._db.notes.note(id).data;
if (note.locked) return;
contentId = note.contentId;
2021-09-15 02:16:55 +05:00
let content = await this._db.content.raw(contentId, false);
2021-09-18 08:52:08 +05:00
// NOTE:
// At this point, the note already has all the attachments extracted
// so we should just encrypt it as normal.
data = content.data;
type = content.type;
} else {
const [content] = await this._db.content.extractAttachments([
{ data, type, noteId: id },
]);
data = content.data;
type = content.type;
}
2020-03-19 11:30:05 +05:00
await this._encryptContent(contentId, data, type, password);
2020-03-08 11:33:55 +05:00
return await this._db.notes.add({
id,
locked: true,
2020-04-15 17:16:13 +05:00
headline: "",
2020-12-23 13:19:17 +05:00
title: note.title,
favorite: note.favorite,
2020-03-08 11:33:55 +05:00
});
}
2020-04-16 02:36:09 +05:00
/** @private */
async _unlockNote(note, password, perm = false) {
let content = await this._decryptContent(note.contentId, password);
2020-03-08 11:33:55 +05:00
if (perm) {
await this._db.notes.add({
id: note.id,
locked: false,
headline: note.headline,
contentId: note.contentId,
content,
2020-03-08 11:33:55 +05:00
});
// await this._db.content.add({ id: note.contentId, data: content });
2020-03-19 11:30:05 +05:00
return;
}
2020-03-08 11:33:55 +05:00
return {
...note,
content,
2020-03-08 11:33:55 +05:00
};
}
2020-04-16 02:36:09 +05:00
/** @inner */
async _getKey() {
if (await this.exists()) return await this._storage.read("vaultKey");
2020-04-16 02:36:09 +05:00
}
/** @inner */
async _setKey(vaultKey) {
if (!vaultKey) return;
await this._storage.write("vaultKey", vaultKey);
2020-04-16 02:36:09 +05:00
}
}