]> git.codecow.com Git - libnemo.git/commitdiff
Create dedicated export method for Safe.
authorChris Duncan <chris@zoso.dev>
Fri, 25 Jul 2025 12:31:28 +0000 (05:31 -0700)
committerChris Duncan <chris@zoso.dev>
Fri, 25 Jul 2025 12:31:28 +0000 (05:31 -0700)
src/lib/workers/safe.ts

index 7b8690c4b79d69402c7ac24bdef8f3a0f61ea094..591ad8c85a976cbeace931bc27be305e4869822f 100644 (file)
@@ -48,7 +48,7 @@ export class Safe extends WorkerInterface {
                                        return await this.fetch(name, store, password)
                                }
                                case 'export': {
-                                       return await this.fetch(name, store, password, true)
+                                       return await this.export(store, password)
                                }
                                case 'destroy': {
                                        return { result: await this.destroy(data, store) }
@@ -113,7 +113,7 @@ export class Safe extends WorkerInterface {
        /**
        * Retrieves data from the Safe and decrypts it with a password byte array.
        */
-       static async fetch (name: string | string[] | unknown, store: string | unknown, password: ArrayBuffer | unknown, all: boolean = false): Promise<NamedData<ArrayBuffer>> {
+       static async fetch (name: string | string[] | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<NamedData<ArrayBuffer>> {
                const names = Array.isArray(name) ? name : [name]
                if (names.some(v => typeof v !== 'string')) {
                        throw new Error('Invalid fields')
@@ -128,9 +128,7 @@ export class Safe extends WorkerInterface {
 
                const results: NamedData<ArrayBuffer> = {}
                try {
-                       const records: SafeRecord[] = all
-                               ? await this.#getAll(store)
-                               : await this.#get(fields, store)
+                       const records: SafeRecord[] = await this.#get(fields, store)
                        if (records == null) {
                                throw new Error('')
                        }
@@ -151,6 +149,47 @@ export class Safe extends WorkerInterface {
                }
        }
 
+       /**
+       * Retrieves all data from a specified Safe table. If a password is not
+       * provided, the records are returned as encrypted data.
+       */
+       static async export (store: string | unknown, password?: ArrayBuffer | unknown): Promise<NamedData<ArrayBuffer>> {
+               if (typeof store !== 'string' || store === '') {
+                       throw new Error('Invalid database store name')
+               }
+               if (password != null && !(password instanceof ArrayBuffer)) {
+                       throw new Error('Invalid password')
+               }
+
+               const results: NamedData<ArrayBuffer> = {}
+               try {
+                       const records: SafeRecord[] = await this.#getAll(store)
+                       if (records == null) {
+                               throw new Error('')
+                       }
+                       if (password instanceof ArrayBuffer) {
+                               const decryptionKeys: { [salt: string]: CryptoKey } = {}
+                               for (const record of records) {
+                                       const salt = await Entropy.import(record.salt)
+                                       decryptionKeys[salt.hex] ??= await this.#createAesKey('decrypt', password, salt.buffer)
+                                       const iv = await Entropy.import(record.iv)
+                                       const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv.buffer }, decryptionKeys[salt.hex], record.encrypted)
+                                       results[record.label] = decrypted
+                               }
+                       } else {
+                               for (const record of records) {
+                                       results[record.label] = record.encrypted
+                               }
+                       }
+                       return results
+               } catch (err) {
+                       console.error(err)
+                       throw new Error(`Failed to export ${store} records`, { cause: err })
+               } finally {
+                       if (password instanceof ArrayBuffer) bytes.erase(password)
+               }
+       }
+
        static async #createAesKey (purpose: 'encrypt' | 'decrypt', password: ArrayBuffer, salt: ArrayBuffer): Promise<CryptoKey> {
                const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
                const derivationAlgorithm: Pbkdf2Params = {