mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
feat: add 3-layer-encryption for attachments
This commit is contained in:
@@ -78,7 +78,7 @@ class Merger {
|
||||
notebooks = [],
|
||||
content = [],
|
||||
trash = [],
|
||||
vaultKey = [],
|
||||
vaultKey,
|
||||
settings = [],
|
||||
attachments = [],
|
||||
} = serverResponse;
|
||||
|
||||
@@ -159,6 +159,45 @@ class UserManager {
|
||||
return { key, salt: user.salt };
|
||||
}
|
||||
|
||||
async getAttachmentsKey() {
|
||||
let user = await this.getUser();
|
||||
if (!user) return;
|
||||
|
||||
let token = await this.tokenManager.getAccessToken();
|
||||
if (!token) return;
|
||||
|
||||
if (!user.attachmentsKey) {
|
||||
user = await http.get(`${constants.API_HOST}${ENDPOINTS.user}`, token);
|
||||
}
|
||||
|
||||
const userEncryptionKey = await this.getEncryptionKey();
|
||||
if (!userEncryptionKey) return;
|
||||
|
||||
if (!user.attachmentsKey) {
|
||||
const key = await this._storage.generateRandomKey();
|
||||
const encryptedKey = await this._storage.encrypt(
|
||||
userEncryptionKey,
|
||||
JSON.stringify(key)
|
||||
);
|
||||
|
||||
user.attachmentsKey = encryptedKey;
|
||||
await http.patch.json(
|
||||
`${constants.API_HOST}${ENDPOINTS.user}`,
|
||||
user,
|
||||
token
|
||||
);
|
||||
|
||||
await this.setUser(user);
|
||||
return key;
|
||||
}
|
||||
|
||||
const plainData = await this._storage.decrypt(
|
||||
userEncryptionKey,
|
||||
user.attachmentsKey
|
||||
);
|
||||
return JSON.parse(plainData);
|
||||
}
|
||||
|
||||
async sendVerificationEmail() {
|
||||
let token = await this.tokenManager.getAccessToken();
|
||||
if (!token) return;
|
||||
|
||||
@@ -12,36 +12,25 @@ export default class Attachments extends Collection {
|
||||
this.key = null;
|
||||
}
|
||||
|
||||
async _getEncryptionKey() {
|
||||
if (!this.key) this.key = await this._db.user.getEncryptionKey();
|
||||
if (!this.key)
|
||||
throw new Error(
|
||||
"Failed to get user encryption key. Cannot cache attachments."
|
||||
);
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{
|
||||
* iv: string,
|
||||
* salt: string,
|
||||
* length: number,
|
||||
* alg: string,
|
||||
* hash: string,
|
||||
* hashType: string,
|
||||
* filename: string,
|
||||
* type: string
|
||||
* type: string,
|
||||
* salt: string,
|
||||
* key: {}
|
||||
* }} attachment
|
||||
* @param {string} noteId Optional as attachments will be parsed at extraction time
|
||||
* @returns
|
||||
*/
|
||||
add(attachment, noteId) {
|
||||
if (!attachment)
|
||||
return console.error("attachment or noteId cannot be null");
|
||||
|
||||
async add(attachment, noteId) {
|
||||
if (!attachment) return console.error("attachment cannot be undefined");
|
||||
if (attachment.remote) return this._collection.addItem(attachment);
|
||||
|
||||
if (!attachment.hash) throw new Error("Please provide attachment hash.");
|
||||
|
||||
const oldAttachment = this.all.find(
|
||||
@@ -55,21 +44,23 @@ export default class Attachments extends Collection {
|
||||
return this._collection.updateItem(oldAttachment);
|
||||
}
|
||||
|
||||
const { iv, salt, length, alg, hash, hashType, filename, type } =
|
||||
const { iv, length, alg, hash, hashType, filename, salt, type, key } =
|
||||
attachment;
|
||||
|
||||
if (
|
||||
!iv ||
|
||||
!salt ||
|
||||
!length ||
|
||||
!alg ||
|
||||
!hash ||
|
||||
!hashType ||
|
||||
!filename ||
|
||||
!type
|
||||
!type ||
|
||||
!salt ||
|
||||
!key
|
||||
)
|
||||
throw new Error("Could not add attachment: all properties are required.");
|
||||
|
||||
const encryptedKey = await this._encryptKey(key);
|
||||
const attachmentItem = {
|
||||
id: id(),
|
||||
noteIds: noteId ? [noteId] : [],
|
||||
@@ -77,6 +68,7 @@ export default class Attachments extends Collection {
|
||||
salt,
|
||||
length,
|
||||
alg,
|
||||
key: encryptedKey,
|
||||
metadata: {
|
||||
hash,
|
||||
hashType,
|
||||
@@ -91,6 +83,17 @@ export default class Attachments extends Collection {
|
||||
return this._collection.addItem(attachmentItem);
|
||||
}
|
||||
|
||||
async generateKey() {
|
||||
await this._getEncryptionKey();
|
||||
return await this._db.storage.generateRandomKey();
|
||||
}
|
||||
|
||||
async decryptKey(key) {
|
||||
const encryptionKey = await this._getEncryptionKey();
|
||||
const plainData = await this._db.storage.decrypt(encryptionKey, key);
|
||||
return JSON.parse(plainData);
|
||||
}
|
||||
|
||||
async delete(hash, noteId) {
|
||||
const attachment = this.all.find((a) => a.metadata.hash === hash);
|
||||
if (!attachment || !deleteItem(attachment.noteIds, noteId)) return;
|
||||
@@ -253,4 +256,28 @@ export default class Attachments extends Collection {
|
||||
get all() {
|
||||
return this._collection.getItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _encryptKey(key) {
|
||||
const encryptionKey = await this._getEncryptionKey();
|
||||
const encryptedKey = await this._db.storage.encrypt(
|
||||
encryptionKey,
|
||||
JSON.stringify(key)
|
||||
);
|
||||
return encryptedKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _getEncryptionKey() {
|
||||
if (!this.key) this.key = await this._db.user.getAttachmentsKey();
|
||||
if (!this.key)
|
||||
throw new Error(
|
||||
"Failed to get user encryption key. Cannot cache attachments."
|
||||
);
|
||||
return this.key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { randomBytes } from "../utils/random";
|
||||
|
||||
export default class Storage {
|
||||
constructor(context) {
|
||||
this.storage = context;
|
||||
@@ -46,4 +48,10 @@ export default class Storage {
|
||||
getCryptoKey(name) {
|
||||
return this.storage.getCryptoKey(name);
|
||||
}
|
||||
|
||||
async generateRandomKey() {
|
||||
const passwordBytes = randomBytes(124);
|
||||
const password = passwordBytes.toString("base64");
|
||||
return await this.storage.generateCryptoKey(password, false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user