Browse Source

feat: able to manage system vai access token (close #12)

JustSong 2 years ago
parent
commit
8c305dc1bc
5 changed files with 92 additions and 7 deletions
  1. 36 0
      controller/user.go
  2. 25 6
      middleware/auth.go
  3. 14 0
      model/user.go
  4. 1 0
      router/api-router.go
  5. 16 1
      web/src/components/PersonalSetting.js

+ 36 - 0
controller/user.go

@@ -243,6 +243,42 @@ func GetUser(c *gin.Context) {
 	return
 }
 
+func GenerateAccessToken(c *gin.Context) {
+	id := c.GetInt("id")
+	user, err := model.GetUserById(id, true)
+	if err != nil {
+		c.JSON(http.StatusOK, gin.H{
+			"success": false,
+			"message": err.Error(),
+		})
+		return
+	}
+	user.AccessToken = common.GetUUID()
+
+	if model.DB.Where("token = ?", user.AccessToken).First(user).RowsAffected != 0 {
+		c.JSON(http.StatusOK, gin.H{
+			"success": false,
+			"message": "请重试,系统生成的 UUID 竟然重复了!",
+		})
+		return
+	}
+
+	if err := user.Update(false); err != nil {
+		c.JSON(http.StatusOK, gin.H{
+			"success": false,
+			"message": err.Error(),
+		})
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"success": true,
+		"message": "",
+		"data":    user.AccessToken,
+	})
+	return
+}
+
 func GetSelf(c *gin.Context) {
 	id := c.GetInt("id")
 	user, err := model.GetUserById(id, false)

+ 25 - 6
middleware/auth.go

@@ -16,12 +16,31 @@ func authHelper(c *gin.Context, minRole int) {
 	id := session.Get("id")
 	status := session.Get("status")
 	if username == nil {
-		c.JSON(http.StatusUnauthorized, gin.H{
-			"success": false,
-			"message": "无权进行此操作,未登录",
-		})
-		c.Abort()
-		return
+		// Check access token
+		accessToken := c.Request.Header.Get("Authorization")
+		if accessToken == "" {
+			c.JSON(http.StatusUnauthorized, gin.H{
+				"success": false,
+				"message": "无权进行此操作,未登录且未提供 access token",
+			})
+			c.Abort()
+			return
+		}
+		user := model.ValidateAccessToken(accessToken)
+		if user != nil && user.Username != "" {
+			// Token is valid
+			username = user.Username
+			role = user.Role
+			id = user.Id
+			status = user.Status
+		} else {
+			c.JSON(http.StatusOK, gin.H{
+				"success": false,
+				"message": "无权进行此操作,access token 无效",
+			})
+			c.Abort()
+			return
+		}
 	}
 	if status.(int) == common.UserStatusDisabled {
 		c.JSON(http.StatusOK, gin.H{

+ 14 - 0
model/user.go

@@ -3,6 +3,7 @@ package model
 import (
 	"errors"
 	"one-api/common"
+	"strings"
 )
 
 // User if you add sensitive fields, don't forget to clean them in setupLogin function.
@@ -19,6 +20,7 @@ type User struct {
 	WeChatId         string `json:"wechat_id" gorm:"column:wechat_id;index"`
 	VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
 	Balance          int    `json:"balance" gorm:"type:int;default:0"`
+	AccessToken      string `json:"access_token" gorm:"column:access_token;uniqueIndex"` // this token is for system management
 }
 
 func GetMaxUserId() int {
@@ -188,3 +190,15 @@ func IsAdmin(userId int) bool {
 	}
 	return user.Role >= common.RoleAdminUser
 }
+
+func ValidateAccessToken(token string) (user *User) {
+	if token == "" {
+		return nil
+	}
+	token = strings.Replace(token, "Bearer ", "", 1)
+	user = &User{}
+	if DB.Where("access_token = ?", token).First(user).RowsAffected == 1 {
+		return user
+	}
+	return nil
+}

+ 1 - 0
router/api-router.go

@@ -35,6 +35,7 @@ func SetApiRouter(router *gin.Engine) {
 				selfRoute.GET("/self", controller.GetSelf)
 				selfRoute.PUT("/self", controller.UpdateSelf)
 				selfRoute.DELETE("/self", controller.DeleteSelf)
+				selfRoute.GET("/token", controller.GenerateAccessToken)
 			}
 
 			adminRoute := userRoute.Group("/")

+ 16 - 1
web/src/components/PersonalSetting.js

@@ -1,5 +1,5 @@
 import React, { useEffect, useState } from 'react';
-import { Button, Divider, Form, Header, Image, Modal } from 'semantic-ui-react';
+import { Button, Divider, Form, Header, Image, Message, Modal } from 'semantic-ui-react';
 import { Link } from 'react-router-dom';
 import { API, copy, showError, showInfo, showSuccess } from '../helpers';
 import Turnstile from 'react-turnstile';
@@ -34,6 +34,17 @@ const PersonalSetting = () => {
     setInputs((inputs) => ({ ...inputs, [name]: value }));
   };
 
+  const generateAccessToken = async () => {
+    const res = await API.get('/api/user/token');
+    const { success, message, data } = res.data;
+    if (success) {
+      await copy(data);
+      showSuccess(`令牌已重置并已复制到剪贴板:${data}`);
+    } else {
+      showError(message);
+    }
+  };
+
   const bindWeChat = async () => {
     if (inputs.wechat_verification_code === '') return;
     const res = await API.get(
@@ -92,9 +103,13 @@ const PersonalSetting = () => {
   return (
     <div style={{ lineHeight: '40px' }}>
       <Header as='h3'>通用设置</Header>
+      <Message>
+        注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
+      </Message>
       <Button as={Link} to={`/user/edit/`}>
         更新个人信息
       </Button>
+      <Button onClick={generateAccessToken}>生成系统访问令牌</Button>
       <Divider />
       <Header as='h3'>账号绑定</Header>
       <Button