diff --git a/packages/app/src/components/settings/ApiKeyManager.vue b/packages/app/src/components/settings/ApiKeyManager.vue index 9cf458c2..17783960 100644 --- a/packages/app/src/components/settings/ApiKeyManager.vue +++ b/packages/app/src/components/settings/ApiKeyManager.vue @@ -136,12 +136,17 @@ async function loadProviders() { async function saveKey() { if (!selectedProvider.value || !newKey.value.trim()) return - await storeApiKey(selectedProvider.value, newKey.value.trim()) - statusMessage.value = `${PROVIDER_NAMES[selectedProvider.value] ?? selectedProvider.value} key saved` - statusError.value = false - newKey.value = '' - selectedProvider.value = '' - await loadProviders() + try { + await storeApiKey(selectedProvider.value, newKey.value.trim()) + statusMessage.value = `${PROVIDER_NAMES[selectedProvider.value] ?? selectedProvider.value} key saved` + statusError.value = false + newKey.value = '' + selectedProvider.value = '' + await loadProviders() + } catch (err) { + statusMessage.value = err instanceof Error ? err.message : 'Failed to store key — encryption required' + statusError.value = true + } } async function removeKey(provider: string) { diff --git a/packages/app/src/utils/key-vault.ts b/packages/app/src/utils/key-vault.ts index a54f70ff..7fa37a58 100644 --- a/packages/app/src/utils/key-vault.ts +++ b/packages/app/src/utils/key-vault.ts @@ -1,7 +1,7 @@ /** * Encrypted API key vault using IndexedDB + AES-256-GCM. * Keys are encrypted with the session key derived from passphrase. - * Falls back to plaintext storage when crypto is disabled (dev mode). + * Refuses to store keys when encryption is unavailable. */ import { isCryptoEnabled, @@ -38,13 +38,13 @@ export async function storeApiKey(provider: string, key: string): Promise if (typeof indexedDB === 'undefined') return const db = await openDB() const sessionKey = getSessionKey() - const useEncryption = isCryptoEnabled() && !!sessionKey - const value = useEncryption - ? await encryptToString(key, sessionKey!) - : key + if (!isCryptoEnabled() || !sessionKey) { + throw new Error('Encryption required: cannot store API keys without an active session key. Please set a passphrase first.') + } - const record: VaultRecord = { provider, value, encrypted: useEncryption } + const value = await encryptToString(key, sessionKey) + const record: VaultRecord = { provider, value, encrypted: true } return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, 'readwrite')