2021-01-23 10:50:07 +05:00
|
|
|
import { CHECK_IDS, EV, EVENTS, sendCheckUserStatusEvent } from "../common";
|
2020-04-12 11:04:30 +05:00
|
|
|
|
2020-10-03 12:04:04 +05:00
|
|
|
const ERASE_TIME = 1000 * 60 * 30;
|
2021-07-07 10:15:28 +05:00
|
|
|
var ERASER_TIMEOUT = null;
|
2020-03-07 12:29:55 +05:00
|
|
|
export default class Vault {
|
2021-07-07 10:15:28 +05:00
|
|
|
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-03-07 12:29:55 +05:00
|
|
|
/**
|
|
|
|
|
*
|
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;
|
2021-07-07 10:15:28 +05:00
|
|
|
this._key = "svvaads1212#2123";
|
|
|
|
|
this._vaultPassword = null;
|
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
|
|
|
};
|
2021-01-23 10:50:07 +05:00
|
|
|
EV.subscribe(EVENTS.userLoggedOut, () => {
|
2021-06-07 10:24:39 +05:00
|
|
|
this._password = null;
|
2020-09-19 11:33:31 +05:00
|
|
|
});
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/**
|
2021-07-07 10:15:28 +05:00
|
|
|
* Creates a new vault
|
2020-04-16 02:36:09 +05:00
|
|
|
* @param {string} password The password
|
2020-11-21 16:09:07 +05:00
|
|
|
* @returns {Promise<Boolean>}
|
2020-04-16 02:36:09 +05:00
|
|
|
*/
|
2020-03-07 12:29:55 +05:00
|
|
|
async create(password) {
|
2021-02-15 21:09:04 +05:00
|
|
|
if (!(await sendCheckUserStatusEvent(CHECK_IDS.vaultAdd))) return;
|
|
|
|
|
|
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-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
|
2020-11-21 16:09:07 +05:00
|
|
|
* @throws ERR_NO_VAULT | ERR_WRONG_PASSWORD
|
2020-11-18 02:24:23 +05:00
|
|
|
* @returns {Promise<Boolean>}
|
2020-04-16 02:36:09 +05:00
|
|
|
*/
|
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-11-21 15:44:56 +05:00
|
|
|
if (!(await this.exists(vaultKey))) throw new Error(this.ERRORS.noVault);
|
2020-03-07 12:29:55 +05:00
|
|
|
try {
|
2021-07-07 10:15:28 +05:00
|
|
|
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
|
|
|
this._password = password;
|
2020-03-07 12:29:55 +05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-21 16:09:07 +05:00
|
|
|
async changePassword(oldPassword, newPassword) {
|
|
|
|
|
if (await this.unlock(oldPassword)) {
|
2021-07-07 10:15:28 +05:00
|
|
|
const lockedNotes = this._db.notes.locked;
|
2020-11-21 16:09:07 +05:00
|
|
|
for (var note of lockedNotes) {
|
2021-07-07 10:15:28 +05:00
|
|
|
await this._unlockNote(note, oldPassword, true);
|
2020-11-21 16:09:07 +05:00
|
|
|
}
|
|
|
|
|
await this._context.remove("vaultKey");
|
|
|
|
|
await this.create(newPassword);
|
|
|
|
|
for (var note of lockedNotes) {
|
2021-07-07 10:15:28 +05:00
|
|
|
await this._lockNote(note, newPassword);
|
2020-11-21 16:09:07 +05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-07 10:15:28 +05:00
|
|
|
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._context.remove("vaultKey");
|
|
|
|
|
this._password = null;
|
2020-10-03 12:04:04 +05:00
|
|
|
}
|
|
|
|
|
|
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;
|
2020-11-11 15:43:09 +05:00
|
|
|
|
2020-03-07 12:29:55 +05:00
|
|
|
await this._check();
|
2021-07-07 10:15:28 +05:00
|
|
|
await this._lockNote({ id: noteId }, this._password);
|
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) {
|
2021-07-07 10:15:28 +05:00
|
|
|
const note = this._db.notes.note(noteId).data;
|
|
|
|
|
await this._unlockNote(note, password, 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) {
|
2021-07-07 10:15:28 +05:00
|
|
|
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
|
|
|
/**
|
2021-07-07 10:15:28 +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;
|
2021-07-07 10:15:28 +05:00
|
|
|
// roll over erase timer
|
|
|
|
|
this._startEraser();
|
|
|
|
|
return await this._lockNote(note, this._password);
|
2020-03-08 11:33:55 +05:00
|
|
|
}
|
|
|
|
|
|
2020-11-21 15:44:56 +05:00
|
|
|
async exists(vaultKey) {
|
2020-04-16 02:36:09 +05:00
|
|
|
if (!vaultKey) vaultKey = await this._context.read("vaultKey");
|
|
|
|
|
return vaultKey && vaultKey.cipher && vaultKey.iv;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-21 15:44:56 +05:00
|
|
|
// Private & internal methods
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/** @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() {
|
2020-11-21 15:44:56 +05:00
|
|
|
if (!(await this.exists())) {
|
2020-04-16 02:36:09 +05:00
|
|
|
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 */
|
2021-07-07 10:15:28 +05:00
|
|
|
async _encryptContent(contentId, content, type, password) {
|
2020-11-04 10:17:37 +05:00
|
|
|
let encryptedContent = await this._context.encrypt(
|
2021-07-07 10:15:28 +05:00
|
|
|
{ password },
|
2020-11-04 10:17:37 +05:00
|
|
|
JSON.stringify(content)
|
|
|
|
|
);
|
|
|
|
|
|
2020-11-18 02:44:18 +05:00
|
|
|
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 */
|
2021-07-07 10:15:28 +05:00
|
|
|
async _decryptContent(contentId, password) {
|
2020-11-04 10:17:37 +05:00
|
|
|
let encryptedContent = await this._db.content.raw(contentId);
|
2020-03-19 11:30:05 +05:00
|
|
|
|
2020-11-04 10:17:37 +05:00
|
|
|
let decryptedContent = await this._context.decrypt(
|
2021-07-07 10:15:28 +05:00
|
|
|
{ password },
|
2020-11-04 10:17:37 +05:00
|
|
|
encryptedContent.data
|
|
|
|
|
);
|
2020-04-15 12:30:36 +05:00
|
|
|
|
2020-11-04 10:17:37 +05:00
|
|
|
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 */
|
2021-07-07 10:15:28 +05:00
|
|
|
async _lockNote(note, password) {
|
2020-11-18 02:44:18 +05:00
|
|
|
let { id, content: { type, data } = {}, contentId } = note;
|
2020-11-18 02:24:23 +05:00
|
|
|
|
2020-11-18 02:44:18 +05:00
|
|
|
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;
|
2020-11-18 02:44:18 +05:00
|
|
|
let content = await this._db.content.raw(contentId);
|
|
|
|
|
data = content.data;
|
|
|
|
|
type = content.type;
|
2020-11-18 02:24:23 +05:00
|
|
|
}
|
2020-03-19 11:30:05 +05:00
|
|
|
|
2021-07-07 10:15:28 +05:00
|
|
|
await this._encryptContent(contentId, data, type, password);
|
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-12-23 13:19:17 +05:00
|
|
|
title: note.title,
|
2020-12-23 13:29:23 +05:00
|
|
|
favorite: note.favorite,
|
2020-03-08 11:33:55 +05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 02:36:09 +05:00
|
|
|
/** @private */
|
2021-07-07 10:15:28 +05:00
|
|
|
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,
|
2020-04-13 11:13:32 +05:00
|
|
|
locked: false,
|
2020-11-04 10:17:37 +05:00
|
|
|
headline: note.headline,
|
|
|
|
|
contentId: note.contentId,
|
|
|
|
|
content,
|
2020-03-08 11:33:55 +05:00
|
|
|
});
|
2020-11-04 10:17:37 +05:00
|
|
|
// await this._db.content.add({ id: note.contentId, data: content });
|
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-11-04 10:17:37 +05:00
|
|
|
content,
|
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() {
|
2020-11-21 15:44:56 +05:00
|
|
|
if (await this.exists()) return await this._context.read("vaultKey");
|
2020-04-16 02:36:09 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @inner */
|
|
|
|
|
async _setKey(vaultKey) {
|
|
|
|
|
if (!vaultKey) return;
|
|
|
|
|
await this._context.write("vaultKey", vaultKey);
|
|
|
|
|
}
|
2020-03-07 12:29:55 +05:00
|
|
|
}
|