diff --git a/web-app/package.json b/web-app/package.json index da7849f87..37753a1b2 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -82,6 +82,7 @@ "remark-math": "6.0.0", "sonner": "2.0.5", "tailwindcss": "4.1.4", + "crypto-js": "^4.2.0", "token.js": "npm:token.js-fork@0.7.27", "tw-animate-css": "1.2.8", "ulidx": "2.4.1", diff --git a/web-app/src/lib/completion.ts b/web-app/src/lib/completion.ts index 8348188f7..f523881aa 100644 --- a/web-app/src/lib/completion.ts +++ b/web-app/src/lib/completion.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import CryptoJS from 'crypto-js' import { ContentType, ChatCompletionRole, @@ -168,9 +169,25 @@ export const sendCompletion = async ( if (!Object.keys(models).some((key) => key === providerName)) providerName = 'openai-compatible' + // Decrypt API key if it exists and is encrypted + const secretKey = await getServiceHub().core().getAppToken() + const decryptApiKey = (encryptedKey: string, key: string): string => { + try { + const bytes = CryptoJS.AES.decrypt(encryptedKey, key) + const decryptedKey = bytes.toString(CryptoJS.enc.Utf8) + return decryptedKey || encryptedKey // Return original if decryption fails + } catch (error) { + console.warn('Failed to decrypt API key, using original value:', error) + return encryptedKey + } + } + + const apiKey = provider.api_key + ? decryptApiKey(provider.api_key, secretKey || 'fallback-key') + : (secretKey ?? '') + const tokenJS = new TokenJS({ - apiKey: - provider.api_key ?? (await getServiceHub().core().getAppToken()) ?? '', + apiKey, // TODO: Retrieve from extension settings baseURL: provider.base_url, // Use Tauri's fetch to avoid CORS issues only for openai-compatible provider diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx index 06fed3713..1785c3551 100644 --- a/web-app/src/routes/settings/providers/$providerName.tsx +++ b/web-app/src/routes/settings/providers/$providerName.tsx @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import CryptoJS from 'crypto-js' import { Card, CardItem } from '@/containers/Card' import HeaderPage from '@/containers/HeaderPage' import SettingsMenu from '@/containers/SettingsMenu' import { useModelProvider } from '@/hooks/useModelProvider' +import { getServiceHub } from '@/hooks/useServiceHub' import { cn, getProviderTitle } from '@/lib/utils' import { createFileRoute, @@ -508,7 +510,7 @@ function ProviderDetail() { 'third-step-setup-remote-provider', setting.key === 'device' && 'hidden' )} - onChange={(newValue) => { + onChange={async (newValue) => { if (provider) { const newSettings = [...provider.settings] // Handle different value types by forcing the type @@ -531,7 +533,13 @@ function ProviderDetail() { settingKey === 'api-key' && typeof newValue === 'string' ) { - updateObj.api_key = newValue + const secretKey = await getServiceHub() + .core() + .getAppToken() + updateObj.api_key = CryptoJS.AES.encrypt( + newValue, + secretKey || 'fallback-key' + ).toString() } else if ( settingKey === 'base-url' && typeof newValue === 'string'