Compare commits

...

7 Commits

Author SHA1 Message Date
Louis
ce9c8fe1cf
Update web-app/src/lib/completion.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-08 20:59:36 +07:00
Louis
38bdfd51ba
Update web-app/src/lib/completion.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-08 10:10:42 +07:00
Louis
cf62729cd0
Merge branch 'dev' into feat/encrypt-api-key 2025-10-08 10:09:10 +07:00
Louis
583a9bb7af
Update web-app/src/lib/completion.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-08 10:08:28 +07:00
Louis
c63604ac55
fix: remove fallback key 2025-10-02 16:02:37 +07:00
Louis
22a7547e6c
fix: deps 2025-09-29 10:35:35 +07:00
Louis
5363022634
feat: encrypt API Key
# Conflicts:
#	web-app/package.json
2025-09-29 10:04:20 +07:00
3 changed files with 40 additions and 6 deletions

View File

@ -51,6 +51,7 @@
"@types/uuid": "10.0.0",
"@uiw/react-textarea-code-editor": "3.1.1",
"class-variance-authority": "0.7.1",
"crypto-js": "^4.2.0",
"culori": "4.0.1",
"emoji-picker-react": "4.12.2",
"framer-motion": "12.23.12",
@ -97,6 +98,7 @@
"@testing-library/jest-dom": "6.8.0",
"@testing-library/react": "16.3.0",
"@testing-library/user-event": "14.6.1",
"@types/crypto-js": "^4.2.2",
"@types/culori": "2.1.1",
"@types/istanbul-lib-report": "3.0.3",
"@types/istanbul-reports": "3.0.4",

View File

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import CryptoJS from 'crypto-js'
import {
ContentType,
ChatCompletionRole,
@ -168,9 +169,31 @@ 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)
if (!decryptedKey) {
throw new Error('Failed to decrypt API key: result is empty')
}
return decryptedKey
} catch (error) {
console.warn('Failed to decrypt API key:', error)
throw new Error('Failed to decrypt API key')
}
}
if (!secretKey) {
throw new Error('Encryption key unavailable: cannot decrypt API key.')
}
if (!provider.api_key) {
throw new Error('API key is missing for the selected provider.');
}
const apiKey = decryptApiKey(provider.api_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

View File

@ -1,9 +1,11 @@
/* 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 { cn, getProviderTitle, getModelDisplayName } from '@/lib/utils'
import { getServiceHub } from '@/hooks/useServiceHub'
import { cn, getProviderTitle } from '@/lib/utils'
import {
createFileRoute,
Link,
@ -497,7 +499,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
@ -520,7 +522,14 @@ function ProviderDetail() {
settingKey === 'api-key' &&
typeof newValue === 'string'
) {
updateObj.api_key = newValue
const secretKey = await getServiceHub()
.core()
.getAppToken()
if (secretKey)
updateObj.api_key = CryptoJS.AES.encrypt(
newValue,
secretKey
).toString()
} else if (
settingKey === 'base-url' &&
typeof newValue === 'string'
@ -536,7 +545,7 @@ function ProviderDetail() {
)
if (deviceSettingIndex !== -1) {
(
;(
newSettings[deviceSettingIndex]
.controller_props as {
value: string