From c84c0fb424e317392b79300439f26fd705658118 Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 5 Mar 2026 06:58:55 +0000 Subject: [PATCH] =?UTF-8?q?fix(app):=20passphrase=20dialog=20=E2=80=94=20r?= =?UTF-8?q?emove=20CSP=20meta,=20check=20crypto.subtle=20availability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove CSP meta tag from index.html (breaks Vite HMR, should be set via HTTP headers in production nginx instead) - isCryptoEnabled() now checks crypto.subtle is available (undefined over HTTP on non-localhost origins) - Add try/catch + error feedback to passphrase submit flow - PassphraseDialog accepts error prop, focuses input on visible Co-Authored-By: Claude Opus 4.6 --- packages/app/index.html | 2 +- packages/app/src/App.vue | 33 ++++++++++++------- .../src/components/ui/PassphraseDialog.vue | 13 ++++---- packages/app/src/utils/crypto.ts | 10 ++++-- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/app/index.html b/packages/app/index.html index 08a7710f..c852ae6d 100644 --- a/packages/app/index.html +++ b/packages/app/index.html @@ -10,7 +10,7 @@ - + AIUI diff --git a/packages/app/src/App.vue b/packages/app/src/App.vue index 868ace4d..154ec0f4 100644 --- a/packages/app/src/App.vue +++ b/packages/app/src/App.vue @@ -6,6 +6,7 @@ @@ -50,21 +51,29 @@ const isCreatingPassphrase = ref(false) const SALT_KEY = 'aiui-crypto-salt' +const passphraseError = ref('') + async function handlePassphraseSubmit(passphrase: string) { - let saltHex = localStorage.getItem(SALT_KEY) - let salt: Uint8Array + passphraseError.value = '' + try { + let saltHex = localStorage.getItem(SALT_KEY) + let salt: Uint8Array - if (saltHex) { - salt = new Uint8Array(saltHex.match(/.{2}/g)!.map(b => parseInt(b, 16))) - } else { - salt = await generateSalt() - saltHex = Array.from(salt).map(b => b.toString(16).padStart(2, '0')).join('') - localStorage.setItem(SALT_KEY, saltHex) + if (saltHex) { + salt = new Uint8Array(saltHex.match(/.{2}/g)!.map(b => parseInt(b, 16))) + } else { + salt = await generateSalt() + saltHex = Array.from(salt).map(b => b.toString(16).padStart(2, '0')).join('') + localStorage.setItem(SALT_KEY, saltHex) + } + + const key = await deriveKey(passphrase, salt) + setSessionKey(key, salt) + showPassphrase.value = false + } catch (err) { + console.error('[AIUI] Passphrase error:', err) + passphraseError.value = 'Encryption failed — check your passphrase or try skipping' } - - const key = await deriveKey(passphrase, salt) - setSessionKey(key, salt) - showPassphrase.value = false } onMounted(() => { diff --git a/packages/app/src/components/ui/PassphraseDialog.vue b/packages/app/src/components/ui/PassphraseDialog.vue index ae3e003c..700eb2da 100644 --- a/packages/app/src/components/ui/PassphraseDialog.vue +++ b/packages/app/src/components/ui/PassphraseDialog.vue @@ -54,8 +54,8 @@ @keydown.enter="handleSubmit" /> -

- {{ errorMessage }} +

+ {{ errorMessage || props.error }}

@@ -86,11 +86,12 @@ diff --git a/packages/app/src/utils/crypto.ts b/packages/app/src/utils/crypto.ts index c6f39ae1..1582597c 100644 --- a/packages/app/src/utils/crypto.ts +++ b/packages/app/src/utils/crypto.ts @@ -105,13 +105,17 @@ function base64ToBuffer(base64: string): Uint8Array { /** * Check if encryption should be enabled. - * Disabled in dev mode with VITE_DISABLE_CRYPTO=true. + * Disabled in dev mode with VITE_DISABLE_CRYPTO=true, or when + * Web Crypto API is unavailable (HTTP on non-localhost origins). */ export function isCryptoEnabled(): boolean { try { - return import.meta.env.VITE_DISABLE_CRYPTO !== 'true' - } catch { + if (import.meta.env.VITE_DISABLE_CRYPTO === 'true') return false + // crypto.subtle is only available in secure contexts (HTTPS or localhost) + if (typeof globalThis.crypto?.subtle === 'undefined') return false return true + } catch { + return false } }