diff --git a/controller/telegram.go b/controller/telegram.go new file mode 100644 index 0000000..b5bc0c0 --- /dev/null +++ b/controller/telegram.go @@ -0,0 +1,116 @@ +package controller + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "io" + "one-api/common" + "one-api/model" + "sort" + + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" +) + +func TelegramBind(c *gin.Context) { + if !common.TelegramOAuthEnabled { + c.JSON(200, gin.H{ + "message": "管理员未开启通过 Telegram 登录以及注册", + "success": false, + }) + return + } + params := c.Request.URL.Query() + if !checkTelegramAuthorization(params, common.TelegramBotToken) { + c.JSON(200, gin.H{ + "message": "无效的请求", + "success": false, + }) + return + } + telegramId := params["id"][0] + if model.IsTelegramIdAlreadyTaken(telegramId) { + c.JSON(200, gin.H{ + "message": "该 Telegram 账户已被绑定", + "success": false, + }) + return + } + + session := sessions.Default(c) + id := session.Get("id") + user := model.User{Id: id.(int)} + if err := user.FillUserById(); err != nil { + c.JSON(200, gin.H{ + "message": err.Error(), + "success": false, + }) + return + } + user.TelegramId = telegramId + if err := user.Update(false); err != nil { + c.JSON(200, gin.H{ + "message": err.Error(), + "success": false, + }) + return + } + + c.Redirect(302, "/setting") +} + +func TelegramLogin(c *gin.Context) { + if !common.TelegramOAuthEnabled { + c.JSON(200, gin.H{ + "message": "管理员未开启通过 Telegram 登录以及注册", + "success": false, + }) + return + } + params := c.Request.URL.Query() + if !checkTelegramAuthorization(params, common.TelegramBotToken) { + c.JSON(200, gin.H{ + "message": "无效的请求", + "success": false, + }) + return + } + + telegramId := params["id"][0] + user := model.User{TelegramId: telegramId} + if err := user.FillUserByTelegramId(); err != nil { + c.JSON(200, gin.H{ + "message": err.Error(), + "success": false, + }) + return + } + setupLogin(&user, c) +} + +func checkTelegramAuthorization(params map[string][]string, token string) bool { + strs := []string{} + var hash = "" + for k, v := range params { + if k == "hash" { + hash = v[0] + continue + } + strs = append(strs, k+"="+v[0]) + } + sort.Strings(strs) + var imploded = "" + for _, s := range strs { + if imploded != "" { + imploded += "\n" + } + imploded += s + } + sha256hash := sha256.New() + io.WriteString(sha256hash, token) + hmachash := hmac.New(sha256.New, sha256hash.Sum(nil)) + io.WriteString(hmachash, imploded) + ss := hex.EncodeToString(hmachash.Sum(nil)) + return hash == ss +} diff --git a/docker-compose.yml b/docker-compose.yml index 403f372..1a1ff21 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.4' services: new-api: - image: calciumion/new-api:latest + build: . container_name: new-api restart: always command: --log-dir /app/logs diff --git a/model/user.go b/model/user.go index 7b70bfe..00294b2 100644 --- a/model/user.go +++ b/model/user.go @@ -288,6 +288,17 @@ func (user *User) FillUserByUsername() error { return nil } +func (user *User) FillUserByTelegramId() error { + if user.TelegramId == "" { + return errors.New("Telegram id 为空!") + } + err := DB.Where(User{TelegramId: user.TelegramId}).First(user).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("该 Telegram 账户未绑定") + } + return nil +} + func IsEmailAlreadyTaken(email string) bool { return DB.Where("email = ?", email).Find(&User{}).RowsAffected == 1 } @@ -304,6 +315,10 @@ func IsUsernameAlreadyTaken(username string) bool { return DB.Where("username = ?", username).Find(&User{}).RowsAffected == 1 } +func IsTelegramIdAlreadyTaken(telegramId string) bool { + return DB.Where("telegram_id = ?", telegramId).Find(&User{}).RowsAffected == 1 +} + func ResetUserPasswordByEmail(email string, password string) error { if email == "" || password == "" { return errors.New("邮箱地址或密码为空!") diff --git a/router/api-router.go b/router/api-router.go index 04d3490..f72b9b3 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -26,6 +26,8 @@ func SetApiRouter(router *gin.Engine) { 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) + apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin) + apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.TelegramBind) userRoute := apiRouter.Group("/user") { diff --git a/web/package.json b/web/package.json index 009092a..d6d7ad5 100644 --- a/web/package.json +++ b/web/package.json @@ -3,10 +3,10 @@ "version": "0.1.0", "private": true, "dependencies": { - "@douyinfe/semi-ui": "^2.46.1", "@douyinfe/semi-icons": "^2.46.1", - "@visactor/vchart": "~1.8.8", + "@douyinfe/semi-ui": "^2.46.1", "@visactor/react-vchart": "~1.8.8", + "@visactor/vchart": "~1.8.8", "@visactor/vchart-semi-theme": "~1.8.8", "axios": "^0.27.2", "history": "^5.3.0", @@ -17,6 +17,7 @@ "react-fireworks": "^1.0.4", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "react-telegram-login": "^1.1.2", "react-toastify": "^9.0.8", "react-turnstile": "^1.0.5", "semantic-ui-css": "^2.5.0", diff --git a/web/src/components/LoginForm.js b/web/src/components/LoginForm.js index ec49ece..eb94784 100644 --- a/web/src/components/LoginForm.js +++ b/web/src/components/LoginForm.js @@ -7,6 +7,7 @@ import Turnstile from "react-turnstile"; import { Layout, Card, Image, Form, Button, Divider, Modal } from "@douyinfe/semi-ui"; import Title from "@douyinfe/semi-ui/lib/es/typography/title"; import Text from "@douyinfe/semi-ui/lib/es/typography/text"; +import TelegramLoginButton from 'react-telegram-login'; import { IconGithubLogo } from '@douyinfe/semi-icons'; @@ -101,10 +102,24 @@ const LoginForm = () => { } // 添加Telegram登录处理函数 - const onTelegramLoginClicked = async () => { - // 这里调用后端API进行Telegram登录 - // 例如: const res = await API.get(`/api/oauth/telegram`); - // 根据响应处理登录逻辑 + const onTelegramLoginClicked = async (response) => { + const fields = ["id", "first_name", "last_name", "username", "photo_url", "auth_date", "hash", "lang"]; + const params = {}; + fields.forEach((field) => { + if (response[field]) { + params[field] = response[field]; + } + }); + const res = await API.get(`/api/oauth/telegram/login`, { params }); + const { success, message, data } = res.data; + if (success) { + userDispatch({ type: 'login', payload: data }); + localStorage.setItem('user', JSON.stringify(data)); + showSuccess('登录成功!'); + navigate('/'); + } else { + showError(message); + } }; return ( @@ -176,13 +191,7 @@ const LoginForm = () => { {/*)}*/} {status.telegram_oauth ? ( - + ) : ( <> )} diff --git a/web/src/components/PersonalSetting.js b/web/src/components/PersonalSetting.js index 3c67a3f..1216b99 100644 --- a/web/src/components/PersonalSetting.js +++ b/web/src/components/PersonalSetting.js @@ -21,6 +21,7 @@ import {getQuotaPerUnit, renderQuota, renderQuotaWithPrompt, stringToColor} from import EditToken from "../pages/Token/EditToken"; import EditUser from "../pages/User/EditUser"; import passwordResetConfirm from "./PasswordResetConfirm"; +import TelegramLoginButton from 'react-telegram-login'; const PersonalSetting = () => { const [userState, userDispatch] = useContext(UserContext); @@ -453,13 +454,11 @@ const PersonalSetting = () => { >
- + {status.telegram_oauth ? + userState.user.telegram_id !== '' ? + : + : + }
diff --git a/web/src/components/SystemSetting.js b/web/src/components/SystemSetting.js index 436cc67..4d2bbc1 100644 --- a/web/src/components/SystemSetting.js +++ b/web/src/components/SystemSetting.js @@ -133,7 +133,9 @@ const SystemSetting = () => { name === 'TurnstileSiteKey' || name === 'TurnstileSecretKey' || name === 'EmailDomainWhitelist' || - name === 'TopupGroupRatio' + name === 'TopupGroupRatio' || + name === 'TelegramBotToken' || + name === 'TelegramBotName' ) { setInputs((inputs) => ({ ...inputs, [name]: value })); } else { @@ -605,12 +607,14 @@ const SystemSetting = () => { diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index 6009486..5ee9f0a 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -110,6 +110,12 @@ const Home = () => { ? '已启用' : '未启用'}

+

+ Telegram 身份验证: + {statusState?.status?.telegram_oauth === true + ? '已启用' + : '未启用'} +