From 280ec3377bc86a5e9686089300f86836348d9a6d Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Fri, 7 Feb 2025 22:27:28 +0800 Subject: [PATCH] feat: add aihubmix oauth --- src/main/ipc.ts | 8 +++++++ src/main/services/WindowService.ts | 2 +- src/main/utils/aes.ts | 13 +++++------ src/preload/index.d.ts | 4 ++++ src/preload/index.ts | 5 +++++ src/renderer/src/i18n/locales/en-us.json | 4 +++- src/renderer/src/i18n/locales/ja-jp.json | 4 +++- src/renderer/src/i18n/locales/ru-ru.json | 4 +++- src/renderer/src/i18n/locales/zh-cn.json | 4 +++- src/renderer/src/i18n/locales/zh-tw.json | 4 +++- src/renderer/src/services/ProviderService.ts | 2 +- src/renderer/src/utils/oauth.ts | 23 +++++++++++++++----- 12 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/main/ipc.ts b/src/main/ipc.ts index a859117b..04e7287f 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -18,6 +18,8 @@ import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutSe import { TrayService } from './services/TrayService' import { windowService } from './services/WindowService' import { getResourcePath } from './utils' +import { decrypt } from './utils/aes' +import { encrypt } from './utils/aes' import { compress, decompress } from './utils/zip' const fileManager = new FileStorage() @@ -199,4 +201,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle('miniwindow:hide', () => windowService.hideMiniWindow()) ipcMain.handle('miniwindow:close', () => windowService.closeMiniWindow()) ipcMain.handle('miniwindow:toggle', () => windowService.toggleMiniWindow()) + + // aes + ipcMain.handle('aes:encrypt', (_, text: string, secretKey: string, iv: string) => encrypt(text, secretKey, iv)) + ipcMain.handle('aes:decrypt', (_, encryptedData: string, iv: string, secretKey: string) => + decrypt(encryptedData, iv, secretKey) + ) } diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index dc9b42c0..ce3217bb 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -163,7 +163,7 @@ export class WindowService { mainWindow.webContents.setWindowOpenHandler((details) => { const { url } = details - const oauthProviderUrls = ['https://account.siliconflow.cn/oauth'] + const oauthProviderUrls = ['https://account.siliconflow.cn/oauth', 'https://aihubmix.com/oauth'] if (oauthProviderUrls.some((link) => url.startsWith(link))) { return { diff --git a/src/main/utils/aes.ts b/src/main/utils/aes.ts index 78fb07f0..908a6463 100644 --- a/src/main/utils/aes.ts +++ b/src/main/utils/aes.ts @@ -1,22 +1,19 @@ import * as crypto from 'crypto' -// 定义密钥和初始化向量(IV) -const secretKey = 'kDQvWz5slot3syfucoo53X6KKsEUJoeFikpiUWRJTLIo3zcUPpFvEa009kK13KCr' -const iv = Buffer.from('Cherry Studio', 'hex') - // 加密函数 -export function encrypt(text: string): { iv: string; encryptedData: string } { - const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(secretKey), iv) +export function encrypt(text: string, secretKey: string, iv: string): { iv: string; encryptedData: string } { + const _iv = Buffer.from(iv, 'hex') + const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(secretKey), _iv) let encrypted = cipher.update(text, 'utf8', 'hex') encrypted += cipher.final('hex') return { - iv: iv.toString('hex'), + iv: _iv.toString('hex'), encryptedData: encrypted } } // 解密函数 -export function decrypt(encryptedData: string, iv: string): string { +export function decrypt(encryptedData: string, iv: string, secretKey: string): string { const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(secretKey), Buffer.from(iv, 'hex')) let decrypted = decipher.update(encryptedData, 'hex', 'utf8') decrypted += decipher.final('utf8') diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index cbc98f03..f7ce536e 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -106,6 +106,10 @@ declare global { close: () => Promise toggle: () => Promise } + aes: { + encrypt: (text: string, secretKey: string, iv: string) => Promise<{ iv: string; encryptedData: string }> + decrypt: (encryptedData: string, iv: string, secretKey: string) => Promise + } } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index 2cdb5c69..60548ca6 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -99,6 +99,11 @@ const api = { hide: () => ipcRenderer.invoke('miniwindow:hide'), close: () => ipcRenderer.invoke('miniwindow:close'), toggle: () => ipcRenderer.invoke('miniwindow:toggle') + }, + aes: { + encrypt: (text: string, secretKey: string, iv: string) => ipcRenderer.invoke('aes:encrypt', text, secretKey, iv), + decrypt: (encryptedData: string, iv: string, secretKey: string) => + ipcRenderer.invoke('aes:decrypt', encryptedData, iv, secretKey) } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index c6fd8817..bbe77bbb 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -501,6 +501,7 @@ "docs_check": "Check", "docs_more_details": "for more details", "get_api_key": "Get API Key", + "charge": "Charge", "no_models": "Please add models first before checking the API connection", "not_checked": "Not Checked", "remove_duplicate_keys": "Remove Duplicate Keys", @@ -703,7 +704,8 @@ "oauth_button": "Auth with {{provider}}", "get_key": "Get", "get_key_success": "API key automatically obtained successfully", - "login": "Login" + "login": "Login", + "error": "API key automatically obtained failed, please get it manually" } } } diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index cfdf243a..abeb2781 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -492,6 +492,7 @@ "docs_check": "チェック", "docs_more_details": "詳細を確認", "get_api_key": "APIキーを取得", + "charge": "充電", "no_models": "API接続をチェックする前に、モデルを追加してください", "not_checked": "未チェック", "remove_duplicate_keys": "重複キーを削除", @@ -683,7 +684,8 @@ "oauth_button": "{{provider}}で認証", "get_key": "取得", "get_key_success": "APIキーの自動取得に成功しました", - "login": "認証" + "login": "認証", + "error": "APIキーの自動取得に失敗しました。手動で取得してください" } } } diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 3e45653d..d533a648 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -493,6 +493,7 @@ "docs_check": "Проверить", "docs_more_details": "для получения дополнительной информации", "get_api_key": "Получить ключ API", + "charge": "Пополнить", "no_models": "Пожалуйста, добавьте модели перед проверкой соединения с API", "not_checked": "Не проверено", "remove_duplicate_keys": "Удалить дубликаты ключей", @@ -695,7 +696,8 @@ "oauth_button": "Авторизоваться с {{provider}}", "get_key": "Получить", "get_key_success": "Автоматический получение ключа API успешно", - "login": "Войти" + "login": "Войти", + "error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную" } } } diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 1ca0a49b..a9c72405 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -499,6 +499,7 @@ "docs_check": "查看", "docs_more_details": "获取更多详情", "get_api_key": "点击这里获取密钥", + "charge": "充值", "no_models": "请先添加模型再检查 API 连接", "not_checked": "未检查", "remove_duplicate_keys": "移除重复密钥", @@ -690,7 +691,8 @@ "oauth_button": "使用{{provider}}登录", "get_key": "获取", "get_key_success": "自动获取密钥成功", - "login": "登录" + "login": "登录", + "error": "自动获取密钥失败,请手动获取" } } } diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 87d61eec..23ab18b4 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -498,6 +498,7 @@ "docs_check": "檢查", "docs_more_details": "查看更多細節", "get_api_key": "點擊這裡獲取密鑰", + "charge": "充值", "no_models": "請先添加模型再檢查 API 連接", "not_checked": "未檢查", "remove_duplicate_keys": "移除重複密鑰", @@ -689,7 +690,8 @@ "oauth_button": "使用{{provider}}登入", "get_key": "獲取", "get_key_success": "自動獲取密鑰成功", - "login": "登入" + "login": "登入", + "error": "自動獲取密鑰失敗,請手動獲取" } } } diff --git a/src/renderer/src/services/ProviderService.ts b/src/renderer/src/services/ProviderService.ts index 049d7e1a..33c0f457 100644 --- a/src/renderer/src/services/ProviderService.ts +++ b/src/renderer/src/services/ProviderService.ts @@ -16,6 +16,6 @@ export function getProviderName(id: string) { } export function isProviderSupportAuth(provider: Provider) { - const supportProviders = ['silicon'] + const supportProviders = ['silicon', 'aihubmix'] return supportProviders.includes(provider.id) } diff --git a/src/renderer/src/utils/oauth.ts b/src/renderer/src/utils/oauth.ts index 0cfc4db8..521c1c86 100644 --- a/src/renderer/src/utils/oauth.ts +++ b/src/renderer/src/utils/oauth.ts @@ -1,5 +1,6 @@ import { SILICON_CLIENT_ID } from '@renderer/config/constant' import { getLanguageCode } from '@renderer/i18n' +import i18n from '@renderer/i18n' export const oauthWithSiliconFlow = async (setKey) => { const authUrl = `https://account.siliconflow.cn/oauth?client_id=${SILICON_CLIENT_ID}` @@ -22,7 +23,7 @@ export const oauthWithSiliconFlow = async (setKey) => { } export const oauthWithAihubmix = async (setKey) => { - const authUrl = `https://aihubmix.com/login?cherry_studio_oauth=true&lang=${getLanguageCode()}&aff=SJyh` + const authUrl = ` https://aihubmix.com/oauth?client_id=cherry_studio_oauth&lang=${getLanguageCode()}&aff=SJyh` const popup = window.open( authUrl, @@ -30,15 +31,25 @@ export const oauthWithAihubmix = async (setKey) => { 'width=720,height=720,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,alwaysOnTop=yes,alwaysRaised=yes' ) - const messageHandler = (event) => { + const messageHandler = async (event) => { const data = event.data if (data && data.key === 'cherry_studio_oauth_callback') { - const apiKeys = data?.data?.apiKeys - if (apiKeys && apiKeys.length > 0) { - setKey(apiKeys[0].value) + const { iv, encryptedData } = data.data + + try { + const secret = import.meta.env.RENDERER_VITE_AIHUBMIX_SECRET || '' + const decryptedData: any = await window.api.aes.decrypt(encryptedData, iv, secret) + const { api_keys } = JSON.parse(decryptedData) + if (api_keys && api_keys.length > 0) { + setKey(api_keys[0].value) + popup?.close() + window.removeEventListener('message', messageHandler) + } + } catch (error) { + console.error('[oauthWithAihubmix] error', error) popup?.close() - window.removeEventListener('message', messageHandler) + window.message.error(i18n.t('oauth.error')) } } }