mirror of
https://github.com/linux-do/new-api.git
synced 2025-09-18 00:16:37 +08:00
1. add LINUX DO oauth
2. fix oauth reg aff issue Signed-off-by: wozulong <>
This commit is contained in:
parent
299911d4cd
commit
7ddb7c586d
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user