diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 88121c1..ac7e495 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ChatApp", - "version": "0.4.0", + "version": "0.4.1", "identifier": "com.meinname.chatapp", "build": { "beforeDevCommand": "pnpm vite:dev", diff --git a/apps/desktop/src/lib/secureFileStore.ts b/apps/desktop/src/lib/secureFileStore.ts index 49fd6d8..e132d3a 100644 --- a/apps/desktop/src/lib/secureFileStore.ts +++ b/apps/desktop/src/lib/secureFileStore.ts @@ -22,7 +22,17 @@ import sodium from 'libsodium-wrappers'; // renamed onto `` so an interrupted write never corrupts the existing // vault. -const FILE_NAME = 'chatapp-vault.bin'; +// Per-user vault filename so multiple accounts on the same machine each get +// their own file (and Argon2 derives a different key per user, so cross-user +// decrypt is also blocked even if filenames collided). +async function vaultFileName(userId: string): Promise { + const enc = new TextEncoder(); + const buf = await crypto.subtle.digest('SHA-256', enc.encode('chatapp-vault-name:' + userId)); + const hex = Array.from(new Uint8Array(buf)) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); + return 'chatapp-vault-' + hex.slice(0, 16) + '.bin'; +} const MAGIC = new TextEncoder().encode('CHATVLT1'); // 8 bytes const SALT_LEN = 16; const NONCE_LEN = 24; @@ -79,7 +89,8 @@ async function deriveKey(userId: string, salt: Uint8Array, s: typeof sodium): Pr async function loadOrCreateVault(userId: string): Promise { const s = await ensureSodium(); const dir = await appLocalDataDir(); - const path = joinPath(dir, FILE_NAME); + const fileName = await vaultFileName(userId); + const path = joinPath(dir, fileName); const tmpPath = path + '.tmp'; try {