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) }
/**
* 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')
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('')
}
}
}
+ /**
+ * 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 = {