add encryption
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user