add encryption
This commit is contained in:
@@ -7,6 +7,7 @@ import fs from "fs/promises";
|
||||
import path, { join } from "path";
|
||||
import { Index } from "flexsearch";
|
||||
import crypto from "crypto";
|
||||
import sodium from "libsodium-wrappers";
|
||||
import __cjs_mod__ from "node:module";
|
||||
const __filename = import.meta.filename;
|
||||
const __dirname = import.meta.dirname;
|
||||
@@ -88,23 +89,73 @@ class PluginConfig {
|
||||
}
|
||||
}
|
||||
class NotesAPI {
|
||||
constructor(adapter) {
|
||||
constructor(adapter, encryptionKey = null) {
|
||||
if (!adapter) {
|
||||
throw new Error("NotesAPI requires a storage adapter");
|
||||
}
|
||||
this.adapter = adapter;
|
||||
this.notesCache = /* @__PURE__ */ new Map();
|
||||
this.encryptionKey = encryptionKey || process.env.NOTES_ENCRYPTION_KEY;
|
||||
this._sodiumReady = false;
|
||||
this.index = new Index({
|
||||
tokenize: "tolerant",
|
||||
resolution: 9
|
||||
});
|
||||
}
|
||||
async _initSodium() {
|
||||
if (!this._sodiumReady) {
|
||||
await sodium.ready;
|
||||
this._sodiumReady = true;
|
||||
}
|
||||
}
|
||||
_encrypt(note) {
|
||||
if (!this.encryptionKey) {
|
||||
throw new Error("Encryption key not set");
|
||||
}
|
||||
const key = Buffer.from(this.encryptionKey, "hex");
|
||||
if (key.length !== 32) {
|
||||
throw new Error("Encryption key must be 64 hex characters (32 bytes)");
|
||||
}
|
||||
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
|
||||
const message = JSON.stringify(note);
|
||||
const ciphertext = sodium.crypto_secretbox_easy(
|
||||
Buffer.from(message),
|
||||
nonce,
|
||||
key
|
||||
);
|
||||
const combined = Buffer.concat([nonce, ciphertext]);
|
||||
return combined.toString("base64");
|
||||
}
|
||||
_decrypt(encryptedData) {
|
||||
if (!this.encryptionKey) {
|
||||
throw new Error("Encryption key not set");
|
||||
}
|
||||
const key = Buffer.from(this.encryptionKey, "hex");
|
||||
if (key.length !== 32) {
|
||||
throw new Error("Encryption key must be 64 hex characters (32 bytes)");
|
||||
}
|
||||
const combined = Buffer.from(encryptedData, "base64");
|
||||
const nonce = combined.slice(0, sodium.crypto_secretbox_NONCEBYTES);
|
||||
const ciphertext = combined.slice(sodium.crypto_secretbox_NONCEBYTES);
|
||||
const decrypted = sodium.crypto_secretbox_open_easy(
|
||||
ciphertext,
|
||||
nonce,
|
||||
key
|
||||
);
|
||||
return JSON.parse(decrypted.toString());
|
||||
}
|
||||
async init() {
|
||||
await this._initSodium();
|
||||
await this.adapter.init();
|
||||
const notes = await this.adapter.getAll();
|
||||
for (const note of notes) {
|
||||
this.notesCache.set(note.id, note);
|
||||
this.index.add(note.id, note.title + "\n" + note.content);
|
||||
const encryptedNotes = await this.adapter.getAll();
|
||||
for (const encryptedNote of encryptedNotes) {
|
||||
try {
|
||||
const note = this._decrypt(encryptedNote.data || encryptedNote);
|
||||
this.notesCache.set(note.id, note);
|
||||
this.index.add(note.id, note.title + "\n" + note.content);
|
||||
} catch (error) {
|
||||
console.error("Failed to decrypt note:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* -----------------------
|
||||
@@ -136,9 +187,13 @@ class NotesAPI {
|
||||
updatedAt: now,
|
||||
content
|
||||
};
|
||||
const encryptedNote = {
|
||||
id: note.id,
|
||||
data: this._encrypt(note)
|
||||
};
|
||||
this.notesCache.set(id, note);
|
||||
this.index.add(id, note.title + "\n" + content);
|
||||
await this.adapter.create(note);
|
||||
await this.adapter.create(encryptedNote);
|
||||
return note;
|
||||
}
|
||||
async deleteNote(id) {
|
||||
@@ -151,8 +206,12 @@ class NotesAPI {
|
||||
if (!note) throw new Error("Note not found");
|
||||
note.content = content;
|
||||
note.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
||||
const encryptedNote = {
|
||||
id: note.id,
|
||||
data: this._encrypt(note)
|
||||
};
|
||||
this.index.update(id, note.title + "\n" + content);
|
||||
await this.adapter.update(note);
|
||||
await this.adapter.update(encryptedNote);
|
||||
return note;
|
||||
}
|
||||
async updateNoteMetadata(id, updates = {}) {
|
||||
@@ -171,8 +230,12 @@ class NotesAPI {
|
||||
note.category = updates.category;
|
||||
}
|
||||
note.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
||||
const encryptedNote = {
|
||||
id: note.id,
|
||||
data: this._encrypt(note)
|
||||
};
|
||||
this.index.update(id, note.title + "\n" + note.content);
|
||||
await this.adapter.update(note);
|
||||
await this.adapter.update(encryptedNote);
|
||||
return note;
|
||||
}
|
||||
search(query) {
|
||||
@@ -235,13 +298,13 @@ app.whenReady().then(async () => {
|
||||
const registry = new PluginRegistry();
|
||||
registry.register(filesystemPlugin);
|
||||
registry.register(supabasePlugin);
|
||||
await new PluginConfig(filesystemPlugin).load();
|
||||
const plugin = registry.get(supabasePlugin.id);
|
||||
const adapter = plugin.createAdapter({
|
||||
supabaseKey: process.env.SUPABASE_KEY,
|
||||
supabaseUrl: process.env.SUPABASE_URL
|
||||
});
|
||||
const notesAPI = new NotesAPI(adapter);
|
||||
const config = await new PluginConfig(filesystemPlugin).load();
|
||||
const plugin = registry.get(config.activeAdapter);
|
||||
const adapter = plugin.createAdapter(config.adapterConfig);
|
||||
const notesAPI = new NotesAPI(
|
||||
adapter,
|
||||
"729a0d21d783654c68f1a0123e2a0e986350de536b5324f1f35876ea12ffeaf5"
|
||||
);
|
||||
await notesAPI.init();
|
||||
ipcMain.handle("notesAPI:call", (_, method, args) => {
|
||||
if (!notesAPI[method]) {
|
||||
|
||||
Reference in New Issue
Block a user