mirror of
				https://github.com/linux-do/new-api.git
				synced 2025-11-04 21:33:41 +08:00 
			
		
		
		
	1. add LINUX DO oauth
2. fix oauth reg aff issue Signed-off-by: wozulong <>
This commit is contained in:
		@@ -7,7 +7,7 @@ all: build-frontend start-backend
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
build-frontend:
 | 
					build-frontend:
 | 
				
			||||||
	@echo "Building frontend..."
 | 
						@echo "Building frontend..."
 | 
				
			||||||
	@cd $(FRONTEND_DIR) && npm install && DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build npm run build
 | 
						@cd $(FRONTEND_DIR) && npm install && DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
start-backend:
 | 
					start-backend:
 | 
				
			||||||
	@echo "Starting backend dev server..."
 | 
						@echo "Starting backend dev server..."
 | 
				
			||||||
@@ -50,6 +50,7 @@ var PasswordLoginEnabled = true
 | 
				
			|||||||
var PasswordRegisterEnabled = true
 | 
					var PasswordRegisterEnabled = true
 | 
				
			||||||
var EmailVerificationEnabled = false
 | 
					var EmailVerificationEnabled = false
 | 
				
			||||||
var GitHubOAuthEnabled = false
 | 
					var GitHubOAuthEnabled = false
 | 
				
			||||||
 | 
					var LinuxDoOAuthEnabled = false
 | 
				
			||||||
var WeChatAuthEnabled = false
 | 
					var WeChatAuthEnabled = false
 | 
				
			||||||
var TelegramOAuthEnabled = false
 | 
					var TelegramOAuthEnabled = false
 | 
				
			||||||
var TurnstileCheckEnabled = false
 | 
					var TurnstileCheckEnabled = false
 | 
				
			||||||
@@ -82,6 +83,9 @@ var SMTPToken = ""
 | 
				
			|||||||
var GitHubClientId = ""
 | 
					var GitHubClientId = ""
 | 
				
			||||||
var GitHubClientSecret = ""
 | 
					var GitHubClientSecret = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var LinuxDoClientId = ""
 | 
				
			||||||
 | 
					var LinuxDoClientSecret = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var WeChatServerAddress = ""
 | 
					var WeChatServerAddress = ""
 | 
				
			||||||
var WeChatServerToken = ""
 | 
					var WeChatServerToken = ""
 | 
				
			||||||
var WeChatAccountQRCodeImageURL = ""
 | 
					var WeChatAccountQRCodeImageURL = ""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -123,6 +123,8 @@ func GitHubOAuth(c *gin.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		if common.RegisterEnabled {
 | 
							if common.RegisterEnabled {
 | 
				
			||||||
 | 
								user.InviterId, _ = model.GetUserIdByAffCode(c.Query("aff"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			user.Username = "github_" + strconv.Itoa(model.GetMaxUserId()+1)
 | 
								user.Username = "github_" + strconv.Itoa(model.GetMaxUserId()+1)
 | 
				
			||||||
			if githubUser.Name != "" {
 | 
								if githubUser.Name != "" {
 | 
				
			||||||
				user.DisplayName = githubUser.Name
 | 
									user.DisplayName = githubUser.Name
 | 
				
			||||||
@@ -133,7 +135,7 @@ func GitHubOAuth(c *gin.Context) {
 | 
				
			|||||||
			user.Role = common.RoleCommonUser
 | 
								user.Role = common.RoleCommonUser
 | 
				
			||||||
			user.Status = common.UserStatusEnabled
 | 
								user.Status = common.UserStatusEnabled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := user.Insert(0); err != nil {
 | 
								if err := user.Insert(user.InviterId); err != nil {
 | 
				
			||||||
				c.JSON(http.StatusOK, gin.H{
 | 
									c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
					"success": false,
 | 
										"success": false,
 | 
				
			||||||
					"message": err.Error(),
 | 
										"message": err.Error(),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										223
									
								
								controller/linuxdo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								controller/linuxdo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,223 @@
 | 
				
			|||||||
 | 
					package controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"encoding/base64"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"github.com/gin-contrib/sessions"
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"one-api/common"
 | 
				
			||||||
 | 
						"one-api/model"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LinuxDoOAuthResponse struct {
 | 
				
			||||||
 | 
						AccessToken string `json:"access_token"`
 | 
				
			||||||
 | 
						Scope       string `json:"scope"`
 | 
				
			||||||
 | 
						TokenType   string `json:"token_type"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LinuxDoUser struct {
 | 
				
			||||||
 | 
						ID         int    `json:"id"`
 | 
				
			||||||
 | 
						Username   string `json:"username"`
 | 
				
			||||||
 | 
						Name       string `json:"name"`
 | 
				
			||||||
 | 
						Active     bool   `json:"active"`
 | 
				
			||||||
 | 
						TrustLevel int    `json:"trust_level"`
 | 
				
			||||||
 | 
						Silenced   bool   `json:"silenced"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getLinuxDoUserInfoByCode(code string) (*LinuxDoUser, error) {
 | 
				
			||||||
 | 
						if code == "" {
 | 
				
			||||||
 | 
							return nil, errors.New("无效的参数")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						auth := base64.StdEncoding.EncodeToString([]byte(common.LinuxDoClientId + ":" + common.LinuxDoClientSecret))
 | 
				
			||||||
 | 
						form := url.Values{
 | 
				
			||||||
 | 
							"grant_type": {"authorization_code"},
 | 
				
			||||||
 | 
							"code":       {code},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req, err := http.NewRequest("POST", "https://connect.linux.do/oauth2/token", bytes.NewBufferString(form.Encode()))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 | 
				
			||||||
 | 
						req.Header.Set("Authorization", "Basic "+auth)
 | 
				
			||||||
 | 
						req.Header.Set("Accept", "application/json")
 | 
				
			||||||
 | 
						client := http.Client{
 | 
				
			||||||
 | 
							Timeout: 5 * time.Second,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res, err := client.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							common.SysLog(err.Error())
 | 
				
			||||||
 | 
							return nil, errors.New("无法连接至 LINUX DO 服务器,请稍后重试!")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer res.Body.Close()
 | 
				
			||||||
 | 
						var oAuthResponse LinuxDoOAuthResponse
 | 
				
			||||||
 | 
						err = json.NewDecoder(res.Body).Decode(&oAuthResponse)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req, err = http.NewRequest("GET", "https://connect.linux.do/api/user", nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", oAuthResponse.AccessToken))
 | 
				
			||||||
 | 
						res2, err := client.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							common.SysLog(err.Error())
 | 
				
			||||||
 | 
							return nil, errors.New("无法连接至 LINUX DO 服务器,请稍后重试!")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer res2.Body.Close()
 | 
				
			||||||
 | 
						var linuxdoUser LinuxDoUser
 | 
				
			||||||
 | 
						err = json.NewDecoder(res2.Body).Decode(&linuxdoUser)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if linuxdoUser.ID == 0 {
 | 
				
			||||||
 | 
							return nil, errors.New("返回值非法,用户字段为空,请稍后重试!")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &linuxdoUser, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func LinuxDoOAuth(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 {
 | 
				
			||||||
 | 
							LinuxDoBind(c)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !common.LinuxDoOAuthEnabled {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": "管理员未开启通过 LINUX DO 登录以及注册",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						code := c.Query("code")
 | 
				
			||||||
 | 
						linuxdoUser, err := getLinuxDoUserInfoByCode(code)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user := model.User{
 | 
				
			||||||
 | 
							LinuxDoId: strconv.Itoa(linuxdoUser.ID),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if model.IsLinuxDoIdAlreadyTaken(user.LinuxDoId) {
 | 
				
			||||||
 | 
							err := user.FillUserByLinuxDoId()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
									"success": false,
 | 
				
			||||||
 | 
									"message": err.Error(),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if common.RegisterEnabled {
 | 
				
			||||||
 | 
								affCode := c.Query("aff")
 | 
				
			||||||
 | 
								user.InviterId, _ = model.GetUserIdByAffCode(affCode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								user.Username = "linuxdo_" + strconv.Itoa(model.GetMaxUserId()+1)
 | 
				
			||||||
 | 
								if linuxdoUser.Name != "" {
 | 
				
			||||||
 | 
									user.DisplayName = linuxdoUser.Name
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									user.DisplayName = linuxdoUser.Username
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								user.Role = common.RoleCommonUser
 | 
				
			||||||
 | 
								user.Status = common.UserStatusEnabled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := user.Insert(user.InviterId); err != nil {
 | 
				
			||||||
 | 
									c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
										"success": false,
 | 
				
			||||||
 | 
										"message": err.Error(),
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
									"success": false,
 | 
				
			||||||
 | 
									"message": "管理员关闭了新用户注册",
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if user.Status != common.UserStatusEnabled {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"message": "用户已被封禁",
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						setupLogin(&user, c)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func LinuxDoBind(c *gin.Context) {
 | 
				
			||||||
 | 
						if !common.LinuxDoOAuthEnabled {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": "管理员未开启通过 LINUX DO 登录以及注册",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						code := c.Query("code")
 | 
				
			||||||
 | 
						linuxdoUser, err := getLinuxDoUserInfoByCode(code)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user := model.User{
 | 
				
			||||||
 | 
							LinuxDoId: strconv.Itoa(linuxdoUser.ID),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if model.IsLinuxDoIdAlreadyTaken(user.LinuxDoId) {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": "该 LINUX DO 账户已被绑定",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						session := sessions.Default(c)
 | 
				
			||||||
 | 
						id := session.Get("id")
 | 
				
			||||||
 | 
						// id := c.GetInt("id")  // critical bug!
 | 
				
			||||||
 | 
						user.Id = id.(int)
 | 
				
			||||||
 | 
						err = user.FillUserById()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user.LinuxDoId = strconv.Itoa(linuxdoUser.ID)
 | 
				
			||||||
 | 
						err = user.Update(false)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
							"success": true,
 | 
				
			||||||
 | 
							"message": "bind",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -36,6 +36,8 @@ func GetStatus(c *gin.Context) {
 | 
				
			|||||||
			"email_verification":       common.EmailVerificationEnabled,
 | 
								"email_verification":       common.EmailVerificationEnabled,
 | 
				
			||||||
			"github_oauth":             common.GitHubOAuthEnabled,
 | 
								"github_oauth":             common.GitHubOAuthEnabled,
 | 
				
			||||||
			"github_client_id":         common.GitHubClientId,
 | 
								"github_client_id":         common.GitHubClientId,
 | 
				
			||||||
 | 
								"linuxdo_oauth":            common.LinuxDoOAuthEnabled,
 | 
				
			||||||
 | 
								"linuxdo_client_id":        common.LinuxDoClientId,
 | 
				
			||||||
			"telegram_oauth":           common.TelegramOAuthEnabled,
 | 
								"telegram_oauth":           common.TelegramOAuthEnabled,
 | 
				
			||||||
			"telegram_bot_name":        common.TelegramBotName,
 | 
								"telegram_bot_name":        common.TelegramBotName,
 | 
				
			||||||
			"system_name":              common.SystemName,
 | 
								"system_name":              common.SystemName,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,6 +50,14 @@ func UpdateOption(c *gin.Context) {
 | 
				
			|||||||
			})
 | 
								})
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						case "LinuxDoOAuthEnabled":
 | 
				
			||||||
 | 
							if option.Value == "true" && common.LinuxDoClientId == "" {
 | 
				
			||||||
 | 
								c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
									"success": false,
 | 
				
			||||||
 | 
									"message": "无法启用 LINUX DO OAuth,请先填入 LINUX DO Client Id 以及 LINUX DO Client Secret!",
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	case "EmailDomainRestrictionEnabled":
 | 
						case "EmailDomainRestrictionEnabled":
 | 
				
			||||||
		if option.Value == "true" && len(common.EmailDomainWhitelist) == 0 {
 | 
							if option.Value == "true" && len(common.EmailDomainWhitelist) == 0 {
 | 
				
			||||||
			c.JSON(http.StatusOK, gin.H{
 | 
								c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ func InitOptionMap() {
 | 
				
			|||||||
	common.OptionMap["PasswordRegisterEnabled"] = strconv.FormatBool(common.PasswordRegisterEnabled)
 | 
						common.OptionMap["PasswordRegisterEnabled"] = strconv.FormatBool(common.PasswordRegisterEnabled)
 | 
				
			||||||
	common.OptionMap["EmailVerificationEnabled"] = strconv.FormatBool(common.EmailVerificationEnabled)
 | 
						common.OptionMap["EmailVerificationEnabled"] = strconv.FormatBool(common.EmailVerificationEnabled)
 | 
				
			||||||
	common.OptionMap["GitHubOAuthEnabled"] = strconv.FormatBool(common.GitHubOAuthEnabled)
 | 
						common.OptionMap["GitHubOAuthEnabled"] = strconv.FormatBool(common.GitHubOAuthEnabled)
 | 
				
			||||||
 | 
						common.OptionMap["LinuxDoOAuthEnabled"] = strconv.FormatBool(common.LinuxDoOAuthEnabled)
 | 
				
			||||||
	common.OptionMap["TelegramOAuthEnabled"] = strconv.FormatBool(common.TelegramOAuthEnabled)
 | 
						common.OptionMap["TelegramOAuthEnabled"] = strconv.FormatBool(common.TelegramOAuthEnabled)
 | 
				
			||||||
	common.OptionMap["WeChatAuthEnabled"] = strconv.FormatBool(common.WeChatAuthEnabled)
 | 
						common.OptionMap["WeChatAuthEnabled"] = strconv.FormatBool(common.WeChatAuthEnabled)
 | 
				
			||||||
	common.OptionMap["TurnstileCheckEnabled"] = strconv.FormatBool(common.TurnstileCheckEnabled)
 | 
						common.OptionMap["TurnstileCheckEnabled"] = strconv.FormatBool(common.TurnstileCheckEnabled)
 | 
				
			||||||
@@ -65,6 +66,8 @@ func InitOptionMap() {
 | 
				
			|||||||
	common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString()
 | 
						common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString()
 | 
				
			||||||
	common.OptionMap["GitHubClientId"] = ""
 | 
						common.OptionMap["GitHubClientId"] = ""
 | 
				
			||||||
	common.OptionMap["GitHubClientSecret"] = ""
 | 
						common.OptionMap["GitHubClientSecret"] = ""
 | 
				
			||||||
 | 
						common.OptionMap["LinuxDoClientId"] = ""
 | 
				
			||||||
 | 
						common.OptionMap["LinuxDoClientSecret"] = ""
 | 
				
			||||||
	common.OptionMap["TelegramBotToken"] = ""
 | 
						common.OptionMap["TelegramBotToken"] = ""
 | 
				
			||||||
	common.OptionMap["TelegramBotName"] = ""
 | 
						common.OptionMap["TelegramBotName"] = ""
 | 
				
			||||||
	common.OptionMap["WeChatServerAddress"] = ""
 | 
						common.OptionMap["WeChatServerAddress"] = ""
 | 
				
			||||||
@@ -155,6 +158,8 @@ func updateOptionMap(key string, value string) (err error) {
 | 
				
			|||||||
			common.EmailVerificationEnabled = boolValue
 | 
								common.EmailVerificationEnabled = boolValue
 | 
				
			||||||
		case "GitHubOAuthEnabled":
 | 
							case "GitHubOAuthEnabled":
 | 
				
			||||||
			common.GitHubOAuthEnabled = boolValue
 | 
								common.GitHubOAuthEnabled = boolValue
 | 
				
			||||||
 | 
							case "LinuxDoOAuthEnabled":
 | 
				
			||||||
 | 
								common.LinuxDoOAuthEnabled = boolValue
 | 
				
			||||||
		case "WeChatAuthEnabled":
 | 
							case "WeChatAuthEnabled":
 | 
				
			||||||
			common.WeChatAuthEnabled = boolValue
 | 
								common.WeChatAuthEnabled = boolValue
 | 
				
			||||||
		case "TelegramOAuthEnabled":
 | 
							case "TelegramOAuthEnabled":
 | 
				
			||||||
@@ -217,6 +222,10 @@ func updateOptionMap(key string, value string) (err error) {
 | 
				
			|||||||
		common.GitHubClientId = value
 | 
							common.GitHubClientId = value
 | 
				
			||||||
	case "GitHubClientSecret":
 | 
						case "GitHubClientSecret":
 | 
				
			||||||
		common.GitHubClientSecret = value
 | 
							common.GitHubClientSecret = value
 | 
				
			||||||
 | 
						case "LinuxDoClientId":
 | 
				
			||||||
 | 
							common.LinuxDoClientId = value
 | 
				
			||||||
 | 
						case "LinuxDoClientSecret":
 | 
				
			||||||
 | 
							common.LinuxDoClientSecret = value
 | 
				
			||||||
	case "Footer":
 | 
						case "Footer":
 | 
				
			||||||
		common.Footer = value
 | 
							common.Footer = value
 | 
				
			||||||
	case "SystemName":
 | 
						case "SystemName":
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ type User struct {
 | 
				
			|||||||
	Status           int            `json:"status" gorm:"type:int;default:1"` // enabled, disabled
 | 
						Status           int            `json:"status" gorm:"type:int;default:1"` // enabled, disabled
 | 
				
			||||||
	Email            string         `json:"email" gorm:"index" validate:"max=50"`
 | 
						Email            string         `json:"email" gorm:"index" validate:"max=50"`
 | 
				
			||||||
	GitHubId         string         `json:"github_id" gorm:"column:github_id;index"`
 | 
						GitHubId         string         `json:"github_id" gorm:"column:github_id;index"`
 | 
				
			||||||
 | 
						LinuxDoId        string         `json:"linuxdo_id" gorm:"column:linuxdo_id;index"`
 | 
				
			||||||
	WeChatId         string         `json:"wechat_id" gorm:"column:wechat_id;index"`
 | 
						WeChatId         string         `json:"wechat_id" gorm:"column:wechat_id;index"`
 | 
				
			||||||
	TelegramId       string         `json:"telegram_id" gorm:"column:telegram_id;index"`
 | 
						TelegramId       string         `json:"telegram_id" gorm:"column:telegram_id;index"`
 | 
				
			||||||
	VerificationCode string         `json:"verification_code" gorm:"-:all"`                                    // this field is only for Email verification, don't save it to database!
 | 
						VerificationCode string         `json:"verification_code" gorm:"-:all"`                                    // this field is only for Email verification, don't save it to database!
 | 
				
			||||||
@@ -272,6 +273,14 @@ func (user *User) FillUserByGitHubId() error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (user *User) FillUserByLinuxDoId() error {
 | 
				
			||||||
 | 
						if user.LinuxDoId == "" {
 | 
				
			||||||
 | 
							return errors.New("LINUX DO id 为空!")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						DB.Where(User{LinuxDoId: user.LinuxDoId}).First(user)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (user *User) FillUserByWeChatId() error {
 | 
					func (user *User) FillUserByWeChatId() error {
 | 
				
			||||||
	if user.WeChatId == "" {
 | 
						if user.WeChatId == "" {
 | 
				
			||||||
		return errors.New("WeChat id 为空!")
 | 
							return errors.New("WeChat id 为空!")
 | 
				
			||||||
@@ -311,6 +320,10 @@ func IsGitHubIdAlreadyTaken(githubId string) bool {
 | 
				
			|||||||
	return DB.Where("github_id = ?", githubId).Find(&User{}).RowsAffected == 1
 | 
						return DB.Where("github_id = ?", githubId).Find(&User{}).RowsAffected == 1
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func IsLinuxDoIdAlreadyTaken(linuxdoId string) bool {
 | 
				
			||||||
 | 
						return DB.Where("linuxdo_id = ?", linuxdoId).Find(&User{}).RowsAffected == 1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func IsUsernameAlreadyTaken(username string) bool {
 | 
					func IsUsernameAlreadyTaken(username string) bool {
 | 
				
			||||||
	return DB.Where("username = ?", username).Find(&User{}).RowsAffected == 1
 | 
						return DB.Where("username = ?", username).Find(&User{}).RowsAffected == 1
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ func SetApiRouter(router *gin.Engine) {
 | 
				
			|||||||
		apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
 | 
							apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
 | 
				
			||||||
		apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
 | 
							apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
 | 
				
			||||||
		apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
 | 
							apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
 | 
				
			||||||
 | 
							apiRouter.GET("/oauth/linuxdo", middleware.CriticalRateLimit(), controller.LinuxDoOAuth)
 | 
				
			||||||
		apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
 | 
							apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
 | 
				
			||||||
		apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
 | 
							apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
 | 
				
			||||||
		apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind)
 | 
							apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import EditUser from './pages/User/EditUser';
 | 
				
			|||||||
import { API, getLogo, getSystemName, showError, showNotice } from './helpers';
 | 
					import { API, getLogo, getSystemName, showError, showNotice } from './helpers';
 | 
				
			||||||
import PasswordResetForm from './components/PasswordResetForm';
 | 
					import PasswordResetForm from './components/PasswordResetForm';
 | 
				
			||||||
import GitHubOAuth from './components/GitHubOAuth';
 | 
					import GitHubOAuth from './components/GitHubOAuth';
 | 
				
			||||||
 | 
					import LinuxDoOAuth from "./components/LinuxDoOAuth";
 | 
				
			||||||
import PasswordResetConfirm from './components/PasswordResetConfirm';
 | 
					import PasswordResetConfirm from './components/PasswordResetConfirm';
 | 
				
			||||||
import { UserContext } from './context/User';
 | 
					import { UserContext } from './context/User';
 | 
				
			||||||
import { StatusContext } from './context/Status';
 | 
					import { StatusContext } from './context/Status';
 | 
				
			||||||
@@ -170,6 +171,14 @@ function App() {
 | 
				
			|||||||
                        </Suspense>
 | 
					                        </Suspense>
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
 | 
					                <Route
 | 
				
			||||||
 | 
					                    path='/oauth/linuxdo'
 | 
				
			||||||
 | 
					                    element={
 | 
				
			||||||
 | 
					                        <Suspense fallback={<Loading></Loading>}>
 | 
				
			||||||
 | 
					                            <LinuxDoOAuth />
 | 
				
			||||||
 | 
					                        </Suspense>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
                <Route
 | 
					                <Route
 | 
				
			||||||
                    path='/setting'
 | 
					                    path='/setting'
 | 
				
			||||||
                    element={
 | 
					                    element={
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,8 @@ const GitHubOAuth = () => {
 | 
				
			|||||||
  let navigate = useNavigate();
 | 
					  let navigate = useNavigate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const sendCode = async (code, state, count) => {
 | 
					  const sendCode = async (code, state, count) => {
 | 
				
			||||||
    const res = await API.get(`/api/oauth/github?code=${code}&state=${state}`);
 | 
					    let aff = localStorage.getItem('aff');
 | 
				
			||||||
 | 
					    const res = await API.get(`/api/oauth/github?code=${code}&state=${state}&aff=${aff}`);
 | 
				
			||||||
    const { success, message, data } = res.data;
 | 
					    const { success, message, data } = res.data;
 | 
				
			||||||
    if (success) {
 | 
					    if (success) {
 | 
				
			||||||
      if (message === 'bind') {
 | 
					      if (message === 'bind') {
 | 
				
			||||||
@@ -41,6 +42,14 @@ const GitHubOAuth = () => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    let error = searchParams.get('error');
 | 
				
			||||||
 | 
					    if (error) {
 | 
				
			||||||
 | 
					      let errorDescription = searchParams.get('error_description');
 | 
				
			||||||
 | 
					      showError(`授权错误:${error}: ${errorDescription}`);
 | 
				
			||||||
 | 
					      navigate('/setting');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let code = searchParams.get('code');
 | 
					    let code = searchParams.get('code');
 | 
				
			||||||
    let state = searchParams.get('state');
 | 
					    let state = searchParams.get('state');
 | 
				
			||||||
    sendCode(code, state, 0).then();
 | 
					    sendCode(code, state, 0).then();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								web/src/components/LinuxDoIcon.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								web/src/components/LinuxDoIcon.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import {Icon} from '@douyinfe/semi-ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const LinuxDoIcon = (props) => {
 | 
				
			||||||
 | 
					    function CustomIcon() {
 | 
				
			||||||
 | 
					        return <svg className='icon' viewBox='0 0 24 24' version='1.1'
 | 
				
			||||||
 | 
					                    xmlns='http://www.w3.org/2000/svg' width='16' height='16' {...props}>
 | 
				
			||||||
 | 
					            <path
 | 
				
			||||||
 | 
					                d="M19.7,17.6c-0.1-0.2-0.2-0.4-0.2-0.6c0-0.4-0.2-0.7-0.5-1c-0.1-0.1-0.3-0.2-0.4-0.2c0.6-1.8-0.3-3.6-1.3-4.9c0,0,0,0,0,0c-0.8-1.2-2-2.1-1.9-3.7c0-1.9,0.2-5.4-3.3-5.1C8.5,2.3,9.5,6,9.4,7.3c0,1.1-0.5,2.2-1.3,3.1c-0.2,0.2-0.4,0.5-0.5,0.7c-1,1.2-1.5,2.8-1.5,4.3c-0.2,0.2-0.4,0.4-0.5,0.6c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.1-0.3,0.2-0.5,0.3c-0.4,0.1-0.7,0.3-0.9,0.7c-0.1,0.3-0.2,0.7-0.1,1.1c0.1,0.2,0.1,0.4,0,0.7c-0.2,0.4-0.2,0.9,0,1.4c0.3,0.4,0.8,0.5,1.5,0.6c0.5,0,1.1,0.2,1.6,0.4l0,0c0.5,0.3,1.1,0.5,1.7,0.5c0.3,0,0.7-0.1,1-0.2c0.3-0.2,0.5-0.4,0.6-0.7c0.4,0,1-0.2,1.7-0.2c0.6,0,1.2,0.2,2,0.1c0,0.1,0,0.2,0.1,0.3c0.2,0.5,0.7,0.9,1.3,1c0.1,0,0.1,0,0.2,0c0.8-0.1,1.6-0.5,2.1-1.1l0,0c0.4-0.4,0.9-0.7,1.4-0.9c0.6-0.3,1-0.5,1.1-1C20.3,18.6,20.1,18.2,19.7,17.6z M12.8,4.8c0.6,0.1,1.1,0.6,1,1.2c0,0.3-0.1,0.6-0.3,0.9c0,0,0,0-0.1,0c-0.2-0.1-0.3-0.1-0.4-0.2c0.1-0.1,0.1-0.3,0.2-0.5c0-0.4-0.2-0.7-0.4-0.7c-0.3,0-0.5,0.3-0.5,0.7c0,0,0,0.1,0,0.1c-0.1-0.1-0.3-0.1-0.4-0.2c0,0,0-0.1,0-0.1C11.8,5.5,12.2,4.9,12.8,4.8z M12.5,6.8c0.1,0.1,0.3,0.2,0.4,0.2c0.1,0,0.3,0.1,0.4,0.2c0.2,0.1,0.4,0.2,0.4,0.5c0,0.3-0.3,0.6-0.9,0.8c-0.2,0.1-0.3,0.1-0.4,0.2c-0.3,0.2-0.6,0.3-1,0.3c-0.3,0-0.6-0.2-0.8-0.4c-0.1-0.1-0.2-0.2-0.4-0.3C10.1,8.2,9.9,8,9.8,7.7c0-0.1,0.1-0.2,0.2-0.3c0.3-0.2,0.4-0.3,0.5-0.4l0.1-0.1c0.2-0.3,0.6-0.5,1-0.5C11.9,6.5,12.2,6.6,12.5,6.8z M10.4,5c0.4,0,0.7,0.4,0.8,1.1c0,0.1,0,0.1,0,0.2c-0.1,0-0.3,0.1-0.4,0.2c0,0,0-0.1,0-0.2c0-0.3-0.2-0.6-0.4-0.5c-0.2,0-0.3,0.3-0.3,0.6c0,0.2,0.1,0.3,0.2,0.4l0,0c0,0-0.1,0.1-0.2,0.1C9.9,6.7,9.7,6.4,9.7,6.1C9.7,5.5,10,5,10.4,5z M9.4,21.1c-0.7,0.3-1.6,0.2-2.2-0.2c-0.6-0.3-1.1-0.4-1.8-0.4c-0.5-0.1-1-0.1-1.1-0.3c-0.1-0.2-0.1-0.5,0.1-1c0.1-0.3,0.1-0.6,0-0.9c-0.1-0.3-0.1-0.5,0-0.8C4.5,17.2,4.7,17.1,5,17c0.3-0.1,0.5-0.2,0.7-0.4c0.1-0.1,0.2-0.2,0.3-0.4c0.3-0.4,0.5-0.6,0.8-0.6c0.6,0.1,1.1,1,1.5,1.9c0.2,0.3,0.4,0.7,0.7,1c0.4,0.5,0.9,1.2,0.9,1.6C9.9,20.6,9.7,20.9,9.4,21.1z M14.3,18.9c0,0.1,0,0.1-0.1,0.2c-1.2,0.9-2.8,1-4.1,0.3c-0.2-0.3-0.4-0.6-0.6-0.9c0.9-0.1,0.7-1.3-1.2-2.5c-2-1.3-0.6-3.7,0.1-4.8c0.1-0.1,0.1,0-0.3,0.8c-0.3,0.6-0.9,2.1-0.1,3.2c0-0.8,0.2-1.6,0.5-2.4c0.7-1.3,1.2-2.8,1.5-4.3c0.1,0.1,0.1,0.1,0.2,0.1c0.1,0.1,0.2,0.2,0.3,0.2c0.2,0.3,0.6,0.4,0.9,0.4c0,0,0.1,0,0.1,0c0.4,0,0.8-0.1,1.1-0.4c0.1-0.1,0.2-0.2,0.4-0.2c0.3-0.1,0.6-0.3,0.9-0.6c0.4,1.3,0.8,2.5,1.4,3.6c0.4,0.8,0.7,1.6,0.9,2.5c0.3,0,0.7,0.1,1,0.3c0.8,0.4,1.1,0.7,1,1.2c-0.1,0-0.1,0-0.2,0c0-0.3-0.2-0.6-0.9-0.9c-0.7-0.3-1.3-0.3-1.5,0.4c-0.1,0-0.2,0.1-0.3,0.1c-0.8,0.4-0.8,1.5-0.9,2.6C14.5,18.2,14.4,18.5,14.3,18.9z M18.9,19.5c-0.6,0.2-1.1,0.6-1.5,1.1c-0.4,0.6-1.1,1-1.9,0.9c-0.4,0-0.8-0.3-0.9-0.7c-0.1-0.6-0.1-1.2,0.2-1.8c0.1-0.4,0.2-0.7,0.3-1.1c0.1-1.2,0.1-1.9,0.6-2.2h0c0,0.5,0.3,0.8,0.7,1c0.5,0,1-0.1,1.4-0.5c0.1,0,0.1,0,0.2,0c0.3,0,0.5,0,0.7,0.2c0.2,0.2,0.3,0.5,0.3,0.7c0,0.3,0.2,0.6,0.3,0.9c0.5,0.5,0.5,0.8,0.5,0.9C19.7,19.1,19.3,19.3,18.9,19.5z M9.9,7.5c-0.1,0-0.1,0-0.1,0.1c0,0,0,0.1,0.1,0.1c0,0,0,0,0,0c0.1,0,0.1,0.1,0.1,0.1c0.3,0.4,0.8,0.6,1.4,0.7c0.5-0.1,1-0.2,1.5-0.6c0.2-0.1,0.4-0.2,0.6-0.3c0.1,0,0.1-0.1,0.1-0.1c0-0.1,0-0.1-0.1-0.1l0,0c-0.2,0.1-0.5,0.2-0.7,0.3c-0.4,0.3-0.9,0.5-1.4,0.5c-0.5,0-0.9-0.3-1.2-0.6C10.1,7.6,10,7.5,9.9,7.5z"
 | 
				
			||||||
 | 
					                fill="currentColor"/>
 | 
				
			||||||
 | 
					        </svg>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <Icon svg={<CustomIcon/>}/>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default LinuxDoIcon;
 | 
				
			||||||
							
								
								
									
										67
									
								
								web/src/components/LinuxDoOAuth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								web/src/components/LinuxDoOAuth.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					import React, { useContext, useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					import { Dimmer, Loader, Segment } from 'semantic-ui-react';
 | 
				
			||||||
 | 
					import { useNavigate, useSearchParams } from 'react-router-dom';
 | 
				
			||||||
 | 
					import { API, showError, showSuccess } from '../helpers';
 | 
				
			||||||
 | 
					import { UserContext } from '../context/User';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const LinuxDoOAuth = () => {
 | 
				
			||||||
 | 
					  const [searchParams, setSearchParams] = useSearchParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [userState, userDispatch] = useContext(UserContext);
 | 
				
			||||||
 | 
					  const [prompt, setPrompt] = useState('处理中...');
 | 
				
			||||||
 | 
					  const [processing, setProcessing] = useState(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let navigate = useNavigate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sendCode = async (code, state, count) => {
 | 
				
			||||||
 | 
					    let aff = localStorage.getItem('aff');
 | 
				
			||||||
 | 
					    const res = await API.get(`/api/oauth/linuxdo?code=${code}&state=${state}&aff=${aff}`);
 | 
				
			||||||
 | 
					    const { success, message, data } = res.data;
 | 
				
			||||||
 | 
					    if (success) {
 | 
				
			||||||
 | 
					      if (message === 'bind') {
 | 
				
			||||||
 | 
					        showSuccess('绑定成功!');
 | 
				
			||||||
 | 
					        navigate('/setting');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        userDispatch({ type: 'login', payload: data });
 | 
				
			||||||
 | 
					        localStorage.setItem('user', JSON.stringify(data));
 | 
				
			||||||
 | 
					        showSuccess('登录成功!');
 | 
				
			||||||
 | 
					        navigate('/');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      showError(message);
 | 
				
			||||||
 | 
					      if (count === 0) {
 | 
				
			||||||
 | 
					        setPrompt(`操作失败,重定向至登录界面中...`);
 | 
				
			||||||
 | 
					        navigate('/setting'); // in case this is failed to bind GitHub
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      count++;
 | 
				
			||||||
 | 
					      setPrompt(`出现错误,第 ${count} 次重试中...`);
 | 
				
			||||||
 | 
					      await new Promise((resolve) => setTimeout(resolve, count * 2000));
 | 
				
			||||||
 | 
					      await sendCode(code, state, count);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    let error = searchParams.get('error');
 | 
				
			||||||
 | 
					    if (error) {
 | 
				
			||||||
 | 
					      let errorDescription = searchParams.get('error_description');
 | 
				
			||||||
 | 
					      showError(`授权错误:${error}: ${errorDescription}`);
 | 
				
			||||||
 | 
					      navigate('/setting');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let code = searchParams.get('code');
 | 
				
			||||||
 | 
					    let state = searchParams.get('state');
 | 
				
			||||||
 | 
					    sendCode(code, state, 0).then();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Segment style={{ minHeight: '300px' }}>
 | 
				
			||||||
 | 
					      <Dimmer active inverted>
 | 
				
			||||||
 | 
					        <Loader size='large'>{prompt}</Loader>
 | 
				
			||||||
 | 
					      </Dimmer>
 | 
				
			||||||
 | 
					    </Segment>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default LinuxDoOAuth;
 | 
				
			||||||
@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from 'react';
 | 
				
			|||||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
 | 
					import { Link, useNavigate, useSearchParams } from 'react-router-dom';
 | 
				
			||||||
import { UserContext } from '../context/User';
 | 
					import { UserContext } from '../context/User';
 | 
				
			||||||
import { API, getLogo, isMobile, showError, showInfo, showSuccess, showWarning } from '../helpers';
 | 
					import { API, getLogo, isMobile, showError, showInfo, showSuccess, showWarning } from '../helpers';
 | 
				
			||||||
import { onGitHubOAuthClicked } from './utils';
 | 
					import { onGitHubOAuthClicked, onLinuxDoOAuthClicked } from './utils';
 | 
				
			||||||
import Turnstile from "react-turnstile";
 | 
					import Turnstile from "react-turnstile";
 | 
				
			||||||
import { Layout, Card, Image, Form, Button, Divider, Modal, Icon } from '@douyinfe/semi-ui';
 | 
					import { Layout, Card, Image, Form, Button, Divider, Modal, Icon } from '@douyinfe/semi-ui';
 | 
				
			||||||
import Title from "@douyinfe/semi-ui/lib/es/typography/title";
 | 
					import Title from "@douyinfe/semi-ui/lib/es/typography/title";
 | 
				
			||||||
@@ -10,6 +10,7 @@ import Text from "@douyinfe/semi-ui/lib/es/typography/text";
 | 
				
			|||||||
import TelegramLoginButton from 'react-telegram-login';
 | 
					import TelegramLoginButton from 'react-telegram-login';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { IconGithubLogo } from '@douyinfe/semi-icons';
 | 
					import { IconGithubLogo } from '@douyinfe/semi-icons';
 | 
				
			||||||
 | 
					import LinuxDoIcon from './LinuxDoIcon';
 | 
				
			||||||
import WeChatIcon from './WeChatIcon';
 | 
					import WeChatIcon from './WeChatIcon';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const LoginForm = () => {
 | 
					const LoginForm = () => {
 | 
				
			||||||
@@ -165,7 +166,7 @@ const LoginForm = () => {
 | 
				
			|||||||
                                        忘记密码 <Link to='/reset'>点击重置</Link>
 | 
					                                        忘记密码 <Link to='/reset'>点击重置</Link>
 | 
				
			||||||
                                    </Text>
 | 
					                                    </Text>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                                {status.github_oauth || status.wechat_login || status.telegram_oauth ? (
 | 
					                                {status.github_oauth || status.linuxdo_oauth || status.wechat_login || status.telegram_oauth ? (
 | 
				
			||||||
                                    <>
 | 
					                                    <>
 | 
				
			||||||
                                        <Divider margin='12px' align='center'>
 | 
					                                        <Divider margin='12px' align='center'>
 | 
				
			||||||
                                            第三方登录
 | 
					                                            第三方登录
 | 
				
			||||||
@@ -180,6 +181,16 @@ const LoginForm = () => {
 | 
				
			|||||||
                                            ) : (
 | 
					                                            ) : (
 | 
				
			||||||
                                                <></>
 | 
					                                                <></>
 | 
				
			||||||
                                            )}
 | 
					                                            )}
 | 
				
			||||||
 | 
					                                            {status.linuxdo_oauth ? (
 | 
				
			||||||
 | 
					                                                <Button
 | 
				
			||||||
 | 
					                                                    type='primary'
 | 
				
			||||||
 | 
					                                                    icon={<LinuxDoIcon />}
 | 
				
			||||||
 | 
					                                                    style={{color: '#000'}}
 | 
				
			||||||
 | 
					                                                    onClick={() => onLinuxDoOAuthClicked(status.linuxdo_client_id)}
 | 
				
			||||||
 | 
					                                                />
 | 
				
			||||||
 | 
					                                            ) : (
 | 
				
			||||||
 | 
					                                                <></>
 | 
				
			||||||
 | 
					                                            )}
 | 
				
			||||||
                                            {status.wechat_login ? (
 | 
					                                            {status.wechat_login ? (
 | 
				
			||||||
                                                <Button
 | 
					                                                <Button
 | 
				
			||||||
                                                    type='primary'
 | 
					                                                    type='primary'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ import {Link, useNavigate} from 'react-router-dom';
 | 
				
			|||||||
import {API, copy, isRoot, showError, showInfo, showNotice, showSuccess} from '../helpers';
 | 
					import {API, copy, isRoot, showError, showInfo, showNotice, showSuccess} from '../helpers';
 | 
				
			||||||
import Turnstile from 'react-turnstile';
 | 
					import Turnstile from 'react-turnstile';
 | 
				
			||||||
import {UserContext} from '../context/User';
 | 
					import {UserContext} from '../context/User';
 | 
				
			||||||
import {onGitHubOAuthClicked} from './utils';
 | 
					import {onGitHubOAuthClicked, onLinuxDoOAuthClicked} from './utils';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    Avatar, Banner,
 | 
					    Avatar, Banner,
 | 
				
			||||||
    Button,
 | 
					    Button,
 | 
				
			||||||
@@ -443,6 +443,27 @@ const PersonalSetting = () => {
 | 
				
			|||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div style={{marginTop: 10}}>
 | 
				
			||||||
 | 
					                                <Typography.Text strong>LINUX DO</Typography.Text>
 | 
				
			||||||
 | 
					                                <div style={{display: 'flex', justifyContent: 'space-between'}}>
 | 
				
			||||||
 | 
					                                    <div>
 | 
				
			||||||
 | 
					                                        <Input
 | 
				
			||||||
 | 
					                                            value={userState.user && userState.user.linuxdo_id !== '' ? userState.user.linuxdo_id : '未绑定'}
 | 
				
			||||||
 | 
					                                            readonly={true}
 | 
				
			||||||
 | 
					                                        ></Input>
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                    <div>
 | 
				
			||||||
 | 
					                                        <Button
 | 
				
			||||||
 | 
					                                            onClick={() => {onLinuxDoOAuthClicked(status.linuxdo_client_id)}}
 | 
				
			||||||
 | 
					                                            disabled={(userState.user && userState.user.linuxdo_id !== '') || !status.linuxdo_oauth}
 | 
				
			||||||
 | 
					                                        >
 | 
				
			||||||
 | 
					                                            {
 | 
				
			||||||
 | 
					                                                status.linuxdo_oauth ? '绑定' : '未启用'
 | 
				
			||||||
 | 
					                                            }
 | 
				
			||||||
 | 
					                                        </Button>
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <div style={{marginTop: 10}}>
 | 
					                            <div style={{marginTop: 10}}>
 | 
				
			||||||
                                <Typography.Text strong>Telegram</Typography.Text>
 | 
					                                <Typography.Text strong>Telegram</Typography.Text>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,9 @@ const SystemSetting = () => {
 | 
				
			|||||||
        GitHubOAuthEnabled: '',
 | 
					        GitHubOAuthEnabled: '',
 | 
				
			||||||
        GitHubClientId: '',
 | 
					        GitHubClientId: '',
 | 
				
			||||||
        GitHubClientSecret: '',
 | 
					        GitHubClientSecret: '',
 | 
				
			||||||
 | 
					        LinuxDoOAuthEnabled: '',
 | 
				
			||||||
 | 
					        LinuxDoClientId: '',
 | 
				
			||||||
 | 
					        LinuxDoClientSecret: '',
 | 
				
			||||||
        Notice: '',
 | 
					        Notice: '',
 | 
				
			||||||
        SMTPServer: '',
 | 
					        SMTPServer: '',
 | 
				
			||||||
        SMTPPort: '',
 | 
					        SMTPPort: '',
 | 
				
			||||||
@@ -82,6 +85,7 @@ const SystemSetting = () => {
 | 
				
			|||||||
            case 'PasswordRegisterEnabled':
 | 
					            case 'PasswordRegisterEnabled':
 | 
				
			||||||
            case 'EmailVerificationEnabled':
 | 
					            case 'EmailVerificationEnabled':
 | 
				
			||||||
            case 'GitHubOAuthEnabled':
 | 
					            case 'GitHubOAuthEnabled':
 | 
				
			||||||
 | 
					            case 'LinuxDoOAuthEnabled':
 | 
				
			||||||
            case 'WeChatAuthEnabled':
 | 
					            case 'WeChatAuthEnabled':
 | 
				
			||||||
            case 'TelegramOAuthEnabled':
 | 
					            case 'TelegramOAuthEnabled':
 | 
				
			||||||
            case 'TurnstileCheckEnabled':
 | 
					            case 'TurnstileCheckEnabled':
 | 
				
			||||||
@@ -129,6 +133,8 @@ const SystemSetting = () => {
 | 
				
			|||||||
            name === 'PayAddress' ||
 | 
					            name === 'PayAddress' ||
 | 
				
			||||||
            name === 'GitHubClientId' ||
 | 
					            name === 'GitHubClientId' ||
 | 
				
			||||||
            name === 'GitHubClientSecret' ||
 | 
					            name === 'GitHubClientSecret' ||
 | 
				
			||||||
 | 
					            name === 'LinuxDoClientId' ||
 | 
				
			||||||
 | 
					            name === 'LinuxDoClientSecret' ||
 | 
				
			||||||
            name === 'WeChatServerAddress' ||
 | 
					            name === 'WeChatServerAddress' ||
 | 
				
			||||||
            name === 'WeChatServerToken' ||
 | 
					            name === 'WeChatServerToken' ||
 | 
				
			||||||
            name === 'WeChatAccountQRCodeImageURL' ||
 | 
					            name === 'WeChatAccountQRCodeImageURL' ||
 | 
				
			||||||
@@ -243,6 +249,18 @@ const SystemSetting = () => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const submitLinuxDoOAuth = async () => {
 | 
				
			||||||
 | 
					        if (originInputs['LinuxDoClientId'] !== inputs.LinuxDoClientId) {
 | 
				
			||||||
 | 
					            await updateOption('LinuxDoClientId', inputs.LinuxDoClientId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            originInputs['LinuxDoClientSecret'] !== inputs.LinuxDoClientSecret &&
 | 
				
			||||||
 | 
					            inputs.LinuxDoClientSecret !== ''
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            await updateOption('LinuxDoClientSecret', inputs.LinuxDoClientSecret);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const submitTelegramSettings = async () => {
 | 
					    const submitTelegramSettings = async () => {
 | 
				
			||||||
        // await updateOption('TelegramOAuthEnabled', inputs.TelegramOAuthEnabled);
 | 
					        // await updateOption('TelegramOAuthEnabled', inputs.TelegramOAuthEnabled);
 | 
				
			||||||
        await updateOption('TelegramBotToken', inputs.TelegramBotToken);
 | 
					        await updateOption('TelegramBotToken', inputs.TelegramBotToken);
 | 
				
			||||||
@@ -412,6 +430,12 @@ const SystemSetting = () => {
 | 
				
			|||||||
                            name='GitHubOAuthEnabled'
 | 
					                            name='GitHubOAuthEnabled'
 | 
				
			||||||
                            onChange={handleInputChange}
 | 
					                            onChange={handleInputChange}
 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
 | 
					                        <Form.Checkbox
 | 
				
			||||||
 | 
					                            checked={inputs.LinuxDoOAuthEnabled === 'true'}
 | 
				
			||||||
 | 
					                            label='允许通过 LINUX DO 账户登录 & 注册'
 | 
				
			||||||
 | 
					                            name='LinuxDoOAuthEnabled'
 | 
				
			||||||
 | 
					                            onChange={handleInputChange}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
                        <Form.Checkbox
 | 
					                        <Form.Checkbox
 | 
				
			||||||
                            checked={inputs.WeChatAuthEnabled === 'true'}
 | 
					                            checked={inputs.WeChatAuthEnabled === 'true'}
 | 
				
			||||||
                            label='允许通过微信登录 & 注册'
 | 
					                            label='允许通过微信登录 & 注册'
 | 
				
			||||||
@@ -577,6 +601,44 @@ const SystemSetting = () => {
 | 
				
			|||||||
                        保存 GitHub OAuth 设置
 | 
					                        保存 GitHub OAuth 设置
 | 
				
			||||||
                    </Form.Button>
 | 
					                    </Form.Button>
 | 
				
			||||||
                    <Divider />
 | 
					                    <Divider />
 | 
				
			||||||
 | 
					                    <Header as='h3'>
 | 
				
			||||||
 | 
					                        配置 LINUX DO Oauth
 | 
				
			||||||
 | 
					                        <Header.Subheader>
 | 
				
			||||||
 | 
					                            用以支持通过 LINUX DO 进行登录注册,
 | 
				
			||||||
 | 
					                            <a href='https://connect.linux.do' target='_blank'>
 | 
				
			||||||
 | 
					                                点击此处
 | 
				
			||||||
 | 
					                            </a>
 | 
				
			||||||
 | 
					                            管理你的 LINUX DO OAuth
 | 
				
			||||||
 | 
					                        </Header.Subheader>
 | 
				
			||||||
 | 
					                    </Header>
 | 
				
			||||||
 | 
					                    <Message>
 | 
				
			||||||
 | 
					                        Homepage URL 填 <code>{inputs.ServerAddress}</code>
 | 
				
			||||||
 | 
					                        ,Authorization callback URL 填{' '}
 | 
				
			||||||
 | 
					                        <code>{`${inputs.ServerAddress}/oauth/linuxdo`}</code>
 | 
				
			||||||
 | 
					                    </Message>
 | 
				
			||||||
 | 
					                    <Form.Group widths={3}>
 | 
				
			||||||
 | 
					                        <Form.Input
 | 
				
			||||||
 | 
					                            label='LINUX DO Client ID'
 | 
				
			||||||
 | 
					                            name='LinuxDoClientId'
 | 
				
			||||||
 | 
					                            onChange={handleInputChange}
 | 
				
			||||||
 | 
					                            autoComplete='new-password'
 | 
				
			||||||
 | 
					                            value={inputs.LinuxDoClientId}
 | 
				
			||||||
 | 
					                            placeholder='输入你注册的 LINUX DO OAuth 的 ID'
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        <Form.Input
 | 
				
			||||||
 | 
					                            label='LINUX DO Client Secret'
 | 
				
			||||||
 | 
					                            name='LinuxDoClientSecret'
 | 
				
			||||||
 | 
					                            onChange={handleInputChange}
 | 
				
			||||||
 | 
					                            type='password'
 | 
				
			||||||
 | 
					                            autoComplete='new-password'
 | 
				
			||||||
 | 
					                            value={inputs.LinuxDoClientSecret}
 | 
				
			||||||
 | 
					                            placeholder='敏感信息不会发送到前端显示'
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </Form.Group>
 | 
				
			||||||
 | 
					                    <Form.Button onClick={submitLinuxDoOAuth}>
 | 
				
			||||||
 | 
					                        保存 LINUX DO OAuth 设置
 | 
				
			||||||
 | 
					                    </Form.Button>
 | 
				
			||||||
 | 
					                    <Divider />
 | 
				
			||||||
                    <Header as='h3'>
 | 
					                    <Header as='h3'>
 | 
				
			||||||
                        配置 WeChat Server
 | 
					                        配置 WeChat Server
 | 
				
			||||||
                        <Header.Subheader>
 | 
					                        <Header.Subheader>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,11 @@ export async function getOAuthState() {
 | 
				
			|||||||
export async function onGitHubOAuthClicked(github_client_id) {
 | 
					export async function onGitHubOAuthClicked(github_client_id) {
 | 
				
			||||||
  const state = await getOAuthState();
 | 
					  const state = await getOAuthState();
 | 
				
			||||||
  if (!state) return;
 | 
					  if (!state) return;
 | 
				
			||||||
  window.open(
 | 
					  location.href = `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`
 | 
				
			||||||
    `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`
 | 
					}
 | 
				
			||||||
  );
 | 
					
 | 
				
			||||||
 | 
					export async function onLinuxDoOAuthClicked(linuxdo_client_id) {
 | 
				
			||||||
 | 
					  const state = await getOAuthState();
 | 
				
			||||||
 | 
					  if (!state) return;
 | 
				
			||||||
 | 
					  location.href = `https://connect.linux.do/oauth2/authorize?client_id=${linuxdo_client_id}&response_type=code&state=${state}&scope=user:profile`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -95,6 +95,10 @@ const Home = () => {
 | 
				
			|||||||
                      GitHub 身份验证:
 | 
					                      GitHub 身份验证:
 | 
				
			||||||
                      {statusState?.status?.github_oauth === true ? '已启用' : '未启用'}
 | 
					                      {statusState?.status?.github_oauth === true ? '已启用' : '未启用'}
 | 
				
			||||||
                    </p>
 | 
					                    </p>
 | 
				
			||||||
 | 
					                    <p>
 | 
				
			||||||
 | 
					                      LINUX DO 身份验证:
 | 
				
			||||||
 | 
					                      {statusState?.status?.linuxdo_oauth === true ? '已启用' : '未启用'}
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
                    <p>
 | 
					                    <p>
 | 
				
			||||||
                      微信身份验证:
 | 
					                      微信身份验证:
 | 
				
			||||||
                      {statusState?.status?.wechat_login === true ? '已启用' : '未启用'}
 | 
					                      {statusState?.status?.wechat_login === true ? '已启用' : '未启用'}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,13 +13,14 @@ const EditUser = (props) => {
 | 
				
			|||||||
    display_name: '',
 | 
					    display_name: '',
 | 
				
			||||||
    password: '',
 | 
					    password: '',
 | 
				
			||||||
    github_id: '',
 | 
					    github_id: '',
 | 
				
			||||||
 | 
					    linuxdo_id: '',
 | 
				
			||||||
    wechat_id: '',
 | 
					    wechat_id: '',
 | 
				
			||||||
    email: '',
 | 
					    email: '',
 | 
				
			||||||
    quota: 0,
 | 
					    quota: 0,
 | 
				
			||||||
    group: 'default'
 | 
					    group: 'default'
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  const [groupOptions, setGroupOptions] = useState([]);
 | 
					  const [groupOptions, setGroupOptions] = useState([]);
 | 
				
			||||||
  const { username, display_name, password, github_id, wechat_id, telegram_id, email, quota, group } =
 | 
					  const { username, display_name, password, github_id, linuxdo_id, wechat_id, telegram_id, email, quota, group } =
 | 
				
			||||||
    inputs;
 | 
					    inputs;
 | 
				
			||||||
  const handleInputChange = (name, value) => {
 | 
					  const handleInputChange = (name, value) => {
 | 
				
			||||||
    setInputs((inputs) => ({ ...inputs, [name]: value }));
 | 
					    setInputs((inputs) => ({ ...inputs, [name]: value }));
 | 
				
			||||||
@@ -184,6 +185,16 @@ const EditUser = (props) => {
 | 
				
			|||||||
            placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
					            placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
				
			||||||
            readonly
 | 
					            readonly
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
 | 
					          <div style={{ marginTop: 20 }}>
 | 
				
			||||||
 | 
					            <Typography.Text>已绑定的 LINUX DO 账户</Typography.Text>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <Input
 | 
				
			||||||
 | 
					              name='linuxdo_id'
 | 
				
			||||||
 | 
					              value={linuxdo_id}
 | 
				
			||||||
 | 
					              autoComplete='new-password'
 | 
				
			||||||
 | 
					              placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
				
			||||||
 | 
					              readonly
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
          <div style={{ marginTop: 20 }}>
 | 
					          <div style={{ marginTop: 20 }}>
 | 
				
			||||||
            <Typography.Text>已绑定的微信账户</Typography.Text>
 | 
					            <Typography.Text>已绑定的微信账户</Typography.Text>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@@ -194,6 +205,9 @@ const EditUser = (props) => {
 | 
				
			|||||||
            placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
					            placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
				
			||||||
            readonly
 | 
					            readonly
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
 | 
					          <div style={{ marginTop: 20 }}>
 | 
				
			||||||
 | 
					            <Typography.Text>已绑定的 Telegram 账户</Typography.Text>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
          <Input
 | 
					          <Input
 | 
				
			||||||
            name='telegram_id'
 | 
					            name='telegram_id'
 | 
				
			||||||
            value={telegram_id}
 | 
					            value={telegram_id}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user