From 84144306a899055071f32d5ad73f5da0c0bd50f5 Mon Sep 17 00:00:00 2001
From: sljeff
Date: Sat, 2 Mar 2024 17:15:52 +0800
Subject: [PATCH] feat: telegram login and bind
---
controller/telegram.go | 116 ++++++++++++++++++++++++++
docker-compose.yml | 2 +-
model/user.go | 15 ++++
router/api-router.go | 2 +
web/package.json | 5 +-
web/src/components/LoginForm.js | 31 ++++---
web/src/components/PersonalSetting.js | 13 ++-
web/src/components/SystemSetting.js | 6 +-
web/src/pages/Home/index.js | 6 ++
9 files changed, 174 insertions(+), 22 deletions(-)
create mode 100644 controller/telegram.go
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
+ ? '已启用'
+ : '未启用'}
+