add encryption

This commit is contained in:
nicwands
2026-03-02 11:59:52 -05:00
parent 1c753a6f8f
commit 2609d73bbd
6 changed files with 208 additions and 154 deletions

View File

@@ -1,14 +1,17 @@
import { Index } from 'flexsearch'
import crypto from 'crypto'
import sodium from 'libsodium-wrappers'
export default class NotesAPI {
constructor(adapter) {
constructor(adapter, encryptionKey = null) {
if (!adapter) {
throw new Error('NotesAPI requires a storage adapter')
}
this.adapter = adapter
this.notesCache = new Map()
this.encryptionKey = encryptionKey || process.env.NOTES_ENCRYPTION_KEY
this._sodiumReady = false
this.index = new Index({
tokenize: 'tolerant',
@@ -16,14 +19,73 @@ export default class NotesAPI {
})
}
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()
const encryptedNotes = 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)
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)
}
}
}
@@ -65,10 +127,15 @@ export default class NotesAPI {
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
}
@@ -87,9 +154,14 @@ export default class NotesAPI {
note.content = content
note.updatedAt = 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
}
@@ -115,9 +187,14 @@ export default class NotesAPI {
note.updatedAt = 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
}

View File

@@ -82,16 +82,19 @@ app.whenReady().then(async () => {
const config = await new PluginConfig(filesystemPlugin).load()
// Create instance of active adapter
// const plugin = registry.get(config.activeAdapter)
const plugin = registry.get(supabasePlugin.id)
// const adapter = plugin.createAdapter(config.adapterConfig)
const adapter = plugin.createAdapter({
supabaseKey: process.env.SUPABASE_KEY,
supabaseUrl: process.env.SUPABASE_URL,
})
const plugin = registry.get(config.activeAdapter)
// const plugin = registry.get(supabasePlugin.id)
const adapter = plugin.createAdapter(config.adapterConfig)
// const adapter = plugin.createAdapter({
// supabaseKey: process.env.SUPABASE_KEY,
// supabaseUrl: process.env.SUPABASE_URL,
// })
// Init Notes API
const notesAPI = new NotesAPI(adapter)
const notesAPI = new NotesAPI(
adapter,
'729a0d21d783654c68f1a0123e2a0e986350de536b5324f1f35876ea12ffeaf5',
)
await notesAPI.init()
// Handle Notes API