From 01863d3e44d817e20eafdeb2d3a690737d7655c4 Mon Sep 17 00:00:00 2001 From: JustSong Date: Wed, 13 Sep 2023 21:50:45 +0800 Subject: [PATCH 1/5] fix: fix quota not return when error occurred (close #518) --- controller/relay-text.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/controller/relay-text.go b/controller/relay-text.go index b190a999..d0fed54a 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -347,6 +347,13 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { isStream = isStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") if resp.StatusCode != http.StatusOK { + go func() { + // return pre-consumed quota + err := model.PostConsumeTokenQuota(tokenId, -preConsumedQuota) + if err != nil { + common.SysError("error return pre-consumed quota: " + err.Error()) + } + }() return relayErrorHandler(resp) } } From 420c37514092b4f406b11f3ab21b4b222f64e9c4 Mon Sep 17 00:00:00 2001 From: JustSong Date: Wed, 13 Sep 2023 22:05:10 +0800 Subject: [PATCH 2/5] perf: only return quota when it's not zero --- controller/relay-text.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index d0fed54a..2cd5598a 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -347,13 +347,15 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { isStream = isStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") if resp.StatusCode != http.StatusOK { - go func() { - // return pre-consumed quota - err := model.PostConsumeTokenQuota(tokenId, -preConsumedQuota) - if err != nil { - common.SysError("error return pre-consumed quota: " + err.Error()) - } - }() + if preConsumedQuota != 0 { + go func() { + // return pre-consumed quota + err := model.PostConsumeTokenQuota(tokenId, -preConsumedQuota) + if err != nil { + common.SysError("error return pre-consumed quota: " + err.Error()) + } + }() + } return relayErrorHandler(resp) } } From 1b4cc788909844f748985e178a05a0723077eb56 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Wed, 13 Sep 2023 23:18:53 +0800 Subject: [PATCH 3/5] docs: add QChatGPT (#522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * doc(README.md): 添加支持One API的项目QChatGPT * Update README.md --------- Co-authored-by: JustSong <39998050+songquanpeng@users.noreply.github.com> --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b89c6be8..367392a4 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,13 @@ docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://ope 注意修改端口号、`OPENAI_API_BASE_URL` 和 `OPENAI_API_KEY`。 +#### QChatGPT - QQ机器人 +项目主页:https://github.com/RockChinQ/QChatGPT + +根据文档完成部署后,在`config.py`设置配置项`openai_config`的`reverse_proxy`为 One API 后端地址,设置`api_key`为 One API 生成的key,并在配置项`completion_api_params`的`model`参数设置为 One API 支持的模型名称。 + +可安装 [Switcher 插件](https://github.com/RockChinQ/Switcher)在运行时切换所使用的模型。 + ### 部署到第三方平台
部署到 Sealos @@ -364,4 +371,4 @@ https://openai.justsong.cn 同样适用于基于本项目的二开项目。 -依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。 \ No newline at end of file +依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。 From b57a0eca16a43658ee6efda01cc23ab4a72ffad3 Mon Sep 17 00:00:00 2001 From: JustSong Date: Wed, 13 Sep 2023 23:22:53 +0800 Subject: [PATCH 4/5] docs: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 367392a4..138a15e0 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,10 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 + [x] [360 智脑](https://ai.360.cn) 2. 支持配置镜像以及众多第三方代理服务: + [x] [OpenAI-SB](https://openai-sb.com) + + [x] [CloseAI](https://console.closeai-asia.com/r/2412) + [x] [API2D](https://api2d.com/r/197971) + [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf) + [x] [AI Proxy](https://aiproxy.io/?i=OneAPI) (邀请码:`OneAPI`) - + [x] [CloseAI](https://console.closeai-asia.com/r/2412) + [x] 自定义渠道:例如各种未收录的第三方代理服务 3. 支持通过**负载均衡**的方式访问多个渠道。 4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。 From 39ae8075e433138ec6cae1677121583f4421b29b Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 15 Sep 2023 00:24:20 +0800 Subject: [PATCH 5/5] fix: fix oauth2 state not checking --- controller/github.go | 27 +++++++++++++++++++++++++++ router/api-router.go | 1 + web/src/components/GitHubOAuth.js | 9 +++++---- web/src/components/LoginForm.js | 9 ++------- web/src/components/PersonalSetting.js | 9 ++------- web/src/components/utils.js | 20 ++++++++++++++++++++ 6 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 web/src/components/utils.js diff --git a/controller/github.go b/controller/github.go index e1c64130..ee995379 100644 --- a/controller/github.go +++ b/controller/github.go @@ -79,6 +79,14 @@ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) { func GitHubOAuth(c *gin.Context) { session := sessions.Default(c) + state := c.Query("state") + if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) { + c.JSON(http.StatusForbidden, gin.H{ + "success": false, + "message": "state is empty or not same", + }) + return + } username := session.Get("username") if username != nil { GitHubBind(c) @@ -205,3 +213,22 @@ func GitHubBind(c *gin.Context) { }) return } + +func GenerateOAuthCode(c *gin.Context) { + session := sessions.Default(c) + state := common.GetRandomString(12) + session.Set("oauth_state", state) + err := session.Save() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + "data": state, + }) +} diff --git a/router/api-router.go b/router/api-router.go index cc330d7e..7ad48871 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -21,6 +21,7 @@ func SetApiRouter(router *gin.Engine) { apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth) + apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode) apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth) apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind) apiRouter.GET("/oauth/email/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.EmailBind) diff --git a/web/src/components/GitHubOAuth.js b/web/src/components/GitHubOAuth.js index 147d4d30..c43ed2a1 100644 --- a/web/src/components/GitHubOAuth.js +++ b/web/src/components/GitHubOAuth.js @@ -13,8 +13,8 @@ const GitHubOAuth = () => { let navigate = useNavigate(); - const sendCode = async (code, count) => { - const res = await API.get(`/api/oauth/github?code=${code}`); + const sendCode = async (code, state, count) => { + const res = await API.get(`/api/oauth/github?code=${code}&state=${state}`); const { success, message, data } = res.data; if (success) { if (message === 'bind') { @@ -36,13 +36,14 @@ const GitHubOAuth = () => { count++; setPrompt(`出现错误,第 ${count} 次重试中...`); await new Promise((resolve) => setTimeout(resolve, count * 2000)); - await sendCode(code, count); + await sendCode(code, state, count); } }; useEffect(() => { let code = searchParams.get('code'); - sendCode(code, 0).then(); + let state = searchParams.get('state'); + sendCode(code, state, 0).then(); }, []); return ( diff --git a/web/src/components/LoginForm.js b/web/src/components/LoginForm.js index 110dad46..b5c4e6f9 100644 --- a/web/src/components/LoginForm.js +++ b/web/src/components/LoginForm.js @@ -3,6 +3,7 @@ import { Button, Divider, Form, Grid, Header, Image, Message, Modal, Segment } f import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { UserContext } from '../context/User'; import { API, getLogo, showError, showSuccess } from '../helpers'; +import { getOAuthState, onGitHubOAuthClicked } from './utils'; const LoginForm = () => { const [inputs, setInputs] = useState({ @@ -31,12 +32,6 @@ const LoginForm = () => { const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false); - const onGitHubOAuthClicked = () => { - window.open( - `https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email` - ); - }; - const onWeChatLoginClicked = () => { setShowWeChatLoginModal(true); }; @@ -131,7 +126,7 @@ const LoginForm = () => { circular color='black' icon='github' - onClick={onGitHubOAuthClicked} + onClick={()=>onGitHubOAuthClicked(status.github_client_id)} /> ) : ( <> diff --git a/web/src/components/PersonalSetting.js b/web/src/components/PersonalSetting.js index c7a303f9..6baf1f35 100644 --- a/web/src/components/PersonalSetting.js +++ b/web/src/components/PersonalSetting.js @@ -4,6 +4,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers'; import Turnstile from 'react-turnstile'; import { UserContext } from '../context/User'; +import { onGitHubOAuthClicked } from './utils'; const PersonalSetting = () => { const [userState, userDispatch] = useContext(UserContext); @@ -130,12 +131,6 @@ const PersonalSetting = () => { } }; - const openGitHubOAuth = () => { - window.open( - `https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email` - ); - }; - const sendVerificationCode = async () => { setDisableButton(true); if (inputs.email === '') return; @@ -249,7 +244,7 @@ const PersonalSetting = () => { { status.github_oauth && ( - + ) }