From 95b86b6570c5946e8b3edd9eb9394344d2793605 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 18 Jul 2025 09:06:21 -0700 Subject: [PATCH] Refactor safe retrieval to get multiple records. --- src/lib/workers/safe.ts | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/lib/workers/safe.ts b/src/lib/workers/safe.ts index 59e7a4a..073f9d9 100644 --- a/src/lib/workers/safe.ts +++ b/src/lib/workers/safe.ts @@ -76,8 +76,8 @@ export class Safe extends WorkerInterface { throw new Error('Invalid password') } + const records: SafeRecord[] = [] try { - const records: SafeRecord[] = [] const salt = await Entropy.create() const encryptionKey = await this.#createAesKey('encrypt', password, salt.buffer) for (const label of Object.keys(data)) { @@ -102,10 +102,12 @@ export class Safe extends WorkerInterface { /** * Retrieves data from the Safe and decrypts it with a password byte array. */ - static async get (name: string | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise { - if (typeof name !== 'string' || name === '') { - throw new Error('Invalid database field name') + static async get (name: string | string[] | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise { + const names = Array.isArray(name) ? name : [name] + if (names.some(v => typeof v !== 'string')) { + throw new Error('Invalid fields') } + const fields: string[] = names if (typeof store !== 'string' || store === '') { throw new Error('Invalid database store name') } @@ -113,16 +115,20 @@ export class Safe extends WorkerInterface { throw new Error('Invalid password') } + const results: Data = {} try { - const record: SafeRecord = await this.#get(name, store) - if (record == null) { - throw new Error('Failed to find record') + const records: SafeRecord[] = await this.#get(fields, store) + if (records == null || records.length === 0) { + throw new Error('Failed to find records') } - const salt = await Entropy.import(record.salt) - const decryptionKey = 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 }, decryptionKey, record.encrypted) - return { [record.label]: decrypted } + const decryptionKeys: { [salt: string]: CryptoKey } = {} + for (const record of records) { + decryptionKeys[record.salt] ??= await this.#createAesKey('decrypt', password, (await Entropy.import(record.salt)).buffer) + const iv = await Entropy.import(record.iv) + const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv.buffer }, decryptionKeys[record.salt], record.encrypted) + results[record.label] = decrypted + } + return results } catch (err) { console.log(err) return null @@ -161,15 +167,16 @@ export class Safe extends WorkerInterface { }) } - static async #get (name: string, store: string): Promise { + static async #get (fields: string[], store: string): Promise { const transaction = this.#storage.transaction(store, 'readonly') const db = transaction.objectStore(store) return new Promise((resolve, reject) => { - const request = db.get(name) - request.onsuccess = (event) => { - resolve((event.target as IDBRequest).result) + const requests = fields.map(field => db.get(field)) + transaction.oncomplete = (event) => { + const results = requests.map(r => r.result) + resolve(results) } - request.onerror = (event) => { + transaction.onerror = (event) => { console.error('Database error') reject((event.target as IDBRequest).error) } -- 2.47.3