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 ? (
-
+
+ Telegram 身份验证: + {statusState?.status?.telegram_oauth === true + ? '已启用' + : '未启用'} +