]> git.codecow.com Git - libnemo.git/commitdiff
Add logging to Safe now that prod build removes logging statements. Leave retrieved...
authorChris Duncan <chris@zoso.dev>
Thu, 17 Jul 2025 13:13:41 +0000 (06:13 -0700)
committerChris Duncan <chris@zoso.dev>
Thu, 17 Jul 2025 13:13:41 +0000 (06:13 -0700)
src/lib/workers/safe.ts

index 970b0dfdc35ba2b16eafa3d29a312e44e5803ec8..6543a1948fbb0b52352890395a71918183a123b2 100644 (file)
@@ -22,21 +22,21 @@ export class Safe extends WorkerInterface {
        }
 
        static async work (headers: Headers, data: Data): Promise<any> {
-               const { method, name, password, store } = headers
+               const { method, name, store, password } = headers
                this.#storage = await this.#open(this.DB_NAME)
                let result
                try {
                        switch (method) {
                                case 'set': {
-                                       result = await this.set(store, password, data)
+                                       result = await this.set(data, store, password)
                                        break
                                }
                                case 'get': {
-                                       result = await this.get(store, password, name)
+                                       result = await this.get(name, store, password)
                                        break
                                }
                                case 'destroy': {
-                                       result = await this.destroy(store, name)
+                                       result = await this.destroy(name, store)
                                        break
                                }
                                default: {
@@ -44,6 +44,7 @@ export class Safe extends WorkerInterface {
                                }
                        }
                } catch (err) {
+                       console.log(err)
                        result = false
                }
                return result
@@ -52,10 +53,11 @@ export class Safe extends WorkerInterface {
        /**
        * Removes data from the Safe without decrypting.
        */
-       static async destroy (store: string, name: string): Promise<boolean> {
+       static async destroy (name: string, store: string): Promise<boolean> {
                try {
-                       return await this.#delete(store, name)
-               } catch {
+                       return await this.#delete(name, store)
+               } catch (err) {
+                       console.log(err)
                        throw new Error(this.ERR_MSG)
                }
        }
@@ -63,28 +65,20 @@ export class Safe extends WorkerInterface {
        /**
        * Encrypts data with a password byte array and stores it in the Safe.
        */
-       static async set (store: string, password: ArrayBuffer, data: Data): Promise<boolean> {
-               try {
-                       const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
-                       if (this.#isInvalid(store, derivationKey, data)) {
-                               throw new Error('Failed to import key')
-                       }
+       static async set (data: Data | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<boolean> {
+               this.#isDataValid(data)
+               if (typeof store !== 'string' || store === '') {
+                       throw new Error('Invalid database store name')
+               }
+               if (!(password instanceof ArrayBuffer)) {
+                       throw new Error('Invalid password')
+               }
 
+               try {
                        const records: SafeRecord[] = []
                        for (const label of Object.keys(data)) {
                                const salt = await Entropy.create()
-                               const derivationAlgorithm: Pbkdf2Params = {
-                                       name: 'PBKDF2',
-                                       hash: 'SHA-512',
-                                       salt: salt.bytes,
-                                       iterations: 210000
-                               }
-                               const derivedKeyType: AesKeyGenParams = {
-                                       name: 'AES-GCM',
-                                       length: 256
-                               }
-                               const encryptionKey = await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, ['encrypt'])
-
+                               const encryptionKey = await Safe.#createAesKey('encrypt', password, salt.buffer)
                                const iv = await Entropy.create()
                                const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv.buffer }, encryptionKey, data[label])
                                const record: SafeRecord = {
@@ -95,7 +89,6 @@ export class Safe extends WorkerInterface {
                                }
                                records.push(record)
                        }
-
                        return await this.#put(records, store)
                } catch (err) {
                        throw new Error(this.ERR_MSG)
@@ -107,62 +100,51 @@ export class Safe extends WorkerInterface {
        /**
        * Retrieves data from the Safe and decrypts it with a password byte array.
        */
-       static async get (store: string, password: ArrayBuffer, name: string): Promise<Data | null> {
-               try {
-                       const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
-                       if (this.#isInvalid(store, derivationKey)) {
-                               throw new Error('Failed to import key')
-                       }
+       static async get (name: string | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<Data | null> {
+               if (typeof name !== 'string' || name === '') {
+                       throw new Error('Invalid database field name')
+               }
+               if (typeof store !== 'string' || store === '') {
+                       throw new Error('Invalid database store name')
+               }
+               if (!(password instanceof ArrayBuffer)) {
+                       throw new Error('Invalid password')
+               }
 
+               try {
                        const record: SafeRecord = await this.#get(name, store)
                        if (record == null) {
                                throw new Error('Failed to find record')
                        }
-                       const { label, encrypted } = record
-
                        const salt = await Entropy.import(record.salt)
-                       const derivationAlgorithm: Pbkdf2Params = {
-                               name: 'PBKDF2',
-                               hash: 'SHA-512',
-                               salt: salt.bytes,
-                               iterations: 210000
-                       }
-                       const derivedKeyType: AesKeyGenParams = {
-                               name: 'AES-GCM',
-                               length: 256
-                       }
-                       const decryptionKey = await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, ['decrypt'])
-
+                       const decryptionKey = await Safe.#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 }, decryptionKey, encrypted)
-
-                       await this.destroy(store, name)
-                       return { [label]: decrypted }
+                       const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv.buffer }, decryptionKey, record.encrypted)
+                       return { [record.label]: decrypted }
                } catch (err) {
+                       console.log(err)
                        return null
                } finally {
                        bytes.erase(password)
                }
        }
 
-       static #isInvalid (name: string, passkey: CryptoKey, data?: any): boolean {
-               if (typeof name !== 'string' || name === '') {
-                       return true
-               }
-               if (!(passkey instanceof CryptoKey)) {
-                       return true
+       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 = {
+                       name: 'PBKDF2',
+                       hash: 'SHA-512',
+                       iterations: 210000,
+                       salt
                }
-               if (typeof data === 'object') {
-                       try {
-                               JSON.stringify(data, (k, v) => typeof v === 'bigint' ? v.toString() : v)
-                       } catch {
-                               return true
-                       }
+               const derivedKeyType: AesKeyGenParams = {
+                       name: 'AES-GCM',
+                       length: 256
                }
-               return false
+               return await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose])
        }
 
-       static async #delete (store: string, name: string): Promise<boolean> {
+       static async #delete (name: string, store: string): Promise<boolean> {
                const transaction = this.#storage.transaction(store, 'readwrite')
                const db = transaction.objectStore(store)
                return new Promise((resolve, reject) => {
@@ -192,6 +174,16 @@ export class Safe extends WorkerInterface {
                })
        }
 
+       static #isDataValid (data: unknown): asserts data is Data {
+               if (typeof data !== 'object') {
+                       throw new Error('Invalid data')
+               }
+               const dataObject = data as { [key: string]: unknown }
+               if (Object.keys(dataObject).some(k => !(dataObject[k] instanceof ArrayBuffer))) {
+                       throw new Error('Invalid data')
+               }
+       }
+
        static async #open (database: string): Promise<IDBDatabase> {
                return new Promise((resolve, reject) => {
                        const request = indexedDB.open(database, 1)