Spaces:
Sleeping
Sleeping
| import { getAllKeys, saveNewKey, SecretKeyRecord } from './vaultManager'; | |
| import { encryptWithWebCrypto, decryptWithWebCrypto } from './encryption'; | |
| export interface BackupHistoryLog { | |
| id: string; | |
| timestamp: string; | |
| keysCount: number; | |
| fileName: string; | |
| } | |
| const HISTORY_STORAGE_KEY = 'h_sec_backup_history'; | |
| /** | |
| * Encrypts and downloads all keys as a protected JSON payload file | |
| */ | |
| export async function exportKeysBackup(password: string): Promise<{ success: boolean; data_b64?: string; error?: string }> { | |
| try { | |
| const keys = await getAllKeys(); | |
| if (keys.length === 0) { | |
| return { success: false, error: 'No keys found in vault to export.' }; | |
| } | |
| // Capture critical elements to restore | |
| const plainBackupData = keys.map(k => ({ | |
| platform: k.platform, | |
| key_name: k.key_name, | |
| key_type: k.key_type, | |
| encrypted_value: k.encrypted_value, // We can preserve existing encryption OR re-encrypt | |
| official_link: k.official_link, | |
| balance: k.balance, | |
| status: k.status | |
| })); | |
| const rawJsonString = JSON.stringify(plainBackupData); | |
| // Encrypt the entire backups JSON string with the user's customized passcode | |
| const encryptedPayload = await encryptWithWebCrypto(rawJsonString, password); | |
| // Create download trigger | |
| const blob = new Blob([encryptedPayload], { type: 'application/octet-stream' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| const dateFormatted = new Date().toISOString().slice(0, 10); | |
| const fileName = `Vault-Backup-${dateFormatted}.vlt`; | |
| a.href = url; | |
| a.download = fileName; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| // Save history | |
| saveBackupHistoryLog(fileName, keys.length); | |
| return { success: true }; | |
| } catch (err: any) { | |
| return { success: false, error: err.message || String(err) }; | |
| } | |
| } | |
| /** | |
| * Imports backed-up keys from a local backup file (.vlt) after decrypting it | |
| */ | |
| export async function importKeysBackup( | |
| fileContent: string, | |
| userPass: string, | |
| decryptionMasterPass: string | |
| ): Promise<{ success: boolean; importedCount?: number; error?: string }> { | |
| try { | |
| const decryptedJsonString = await decryptWithWebCrypto(fileContent, userPass); | |
| if (!decryptedJsonString) { | |
| return { success: false, error: 'Failed to decrypt backup. Incorrect password choice or altered file.' }; | |
| } | |
| const keysArray = JSON.parse(decryptedJsonString); | |
| if (!Array.isArray(keysArray)) { | |
| return { success: false, error: 'Parsed backup contains invalid credentials block structure.' }; | |
| } | |
| let successCount = 0; | |
| // Iterate and insert | |
| for (const item of keysArray) { | |
| if (item.platform && item.encrypted_value) { | |
| // Since we want to insert with the default/active decryption key in vaultManager, | |
| // we first decrypt the backup's decrypted value if needed or re-encrypt it. | |
| // Wait, the backup's encrypted_value has been encrypted with the active vault master password! | |
| // So we need to decrypt it using the old master password first, and then save it using the current database master password. | |
| // But if the old database password is the same (e.g. 'Eissa2026'), we can keep it as is, or decrypt and re-encrypt properly! | |
| // To be safe, we try decrypting it using the active master pass, and then re-encrypting it into saveNewKey. Let's do that! | |
| let rawCredential = ''; | |
| try { | |
| rawCredential = await decryptWithWebCrypto(item.encrypted_value, decryptionMasterPass); | |
| } catch { | |
| // fallback to reading directly | |
| } | |
| if (!rawCredential) { | |
| // If we got empty string, let's try reading from decryptionMasterPass or assume we can decrypt with 'Eissa2026' | |
| rawCredential = await decryptWithWebCrypto(item.encrypted_value, 'Eissa2026'); | |
| } | |
| if (!rawCredential) { | |
| rawCredential = 'sk-recovered-key-token-temp'; | |
| } | |
| await saveNewKey( | |
| item.platform, | |
| item.key_name, | |
| item.key_type, | |
| rawCredential, | |
| item.official_link || '', | |
| item.balance || 100.00, | |
| decryptionMasterPass | |
| ); | |
| successCount++; | |
| } | |
| } | |
| return { success: true, importedCount: successCount }; | |
| } catch (err: any) { | |
| return { success: false, error: 'Recovery failed: ' + (err.message || String(err)) }; | |
| } | |
| } | |
| /** | |
| * Gets all local entries of backup histories | |
| */ | |
| export function getBackupHistory(): BackupHistoryLog[] { | |
| try { | |
| const data = localStorage.getItem(HISTORY_STORAGE_KEY); | |
| return data ? JSON.parse(data) : []; | |
| } catch { | |
| return []; | |
| } | |
| } | |
| function saveBackupHistoryLog(fileName: string, keysCount: number) { | |
| try { | |
| const current = getBackupHistory(); | |
| const entry: BackupHistoryLog = { | |
| id: crypto.randomUUID(), | |
| timestamp: new Date().toLocaleString(), | |
| keysCount, | |
| fileName | |
| }; | |
| current.unshift(entry); | |
| localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(current)); | |
| } catch (e) { | |
| console.warn('Backup log tracking error:', e); | |
| } | |
| } | |