mirror of
https://github.com/linux-do/new-api.git
synced 2025-09-18 00:16:37 +08:00
Merge pull request #562 from seefs001/main
feat: integrate Linux DO OAuth authentication
This commit is contained in:
commit
6d47b2c5a1
@ -41,6 +41,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
|
||||||
@ -75,6 +76,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 = ""
|
||||||
|
265
controller/linuxdo.go
Normal file
265
controller/linuxdo.go
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"one-api/common"
|
||||||
|
"one-api/model"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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, c)
|
||||||
|
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")
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLinuxdoUserInfoByCode(code string, c *gin.Context) (*LinuxdoUser, error) {
|
||||||
|
if code == "" {
|
||||||
|
return nil, errors.New("invalid code")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get access token using Basic auth
|
||||||
|
tokenEndpoint := "https://connect.linux.do/oauth2/token"
|
||||||
|
credentials := common.LinuxDOClientId + ":" + common.LinuxDOClientSecret
|
||||||
|
basicAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(credentials))
|
||||||
|
|
||||||
|
// Get redirect URI from request
|
||||||
|
scheme := "http"
|
||||||
|
if c.Request.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
redirectURI := fmt.Sprintf("%s://%s/api/oauth/linuxdo", scheme, c.Request.Host)
|
||||||
|
|
||||||
|
data := url.Values{}
|
||||||
|
data.Set("grant_type", "authorization_code")
|
||||||
|
data.Set("code", code)
|
||||||
|
data.Set("redirect_uri", redirectURI)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", tokenEndpoint, strings.NewReader(data.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", basicAuth)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
client := http.Client{Timeout: 5 * time.Second}
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to connect to Linux DO server")
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var tokenRes struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&tokenRes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenRes.AccessToken == "" {
|
||||||
|
return nil, fmt.Errorf("failed to get access token: %s", tokenRes.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user info
|
||||||
|
userEndpoint := "https://connect.linux.do/api/user"
|
||||||
|
req, err = http.NewRequest("GET", userEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+tokenRes.AccessToken)
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
res2, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to get user info from Linux DO")
|
||||||
|
}
|
||||||
|
defer res2.Body.Close()
|
||||||
|
|
||||||
|
var linuxdoUser LinuxdoUser
|
||||||
|
if err := json.NewDecoder(res2.Body).Decode(&linuxdoUser); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if linuxdoUser.Id == 0 {
|
||||||
|
return nil, errors.New("invalid user info returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &linuxdoUser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LinuxdoOAuth(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
|
||||||
|
errorCode := c.Query("error")
|
||||||
|
if errorCode != "" {
|
||||||
|
errorDescription := c.Query("error_description")
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": errorDescription,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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, c)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.User{
|
||||||
|
LinuxDOId: strconv.Itoa(linuxdoUser.Id),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
if model.IsLinuxDOIdAlreadyTaken(user.LinuxDOId) {
|
||||||
|
err := user.FillUserByLinuxDOId()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user.Id == 0 {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "用户已注销",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if common.RegisterEnabled {
|
||||||
|
user.Username = "linuxdo_" + strconv.Itoa(model.GetMaxUserId()+1)
|
||||||
|
user.DisplayName = linuxdoUser.Name
|
||||||
|
user.Role = common.RoleCommonUser
|
||||||
|
user.Status = common.UserStatusEnabled
|
||||||
|
|
||||||
|
if err := user.Insert(0); 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)
|
||||||
|
}
|
@ -38,6 +38,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": "无法启用 LinuxDO OAuth,请先填入 LinuxDO Client Id 以及 LinuxDO 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{
|
||||||
|
1053
i18n/en.json
1053
i18n/en.json
File diff suppressed because it is too large
Load Diff
@ -31,6 +31,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)
|
||||||
@ -175,6 +176,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":
|
||||||
@ -267,6 +270,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":
|
||||||
|
@ -36,6 +36,7 @@ type User struct {
|
|||||||
AffHistoryQuota int `json:"aff_history_quota" gorm:"type:int;default:0;column:aff_history"` // 邀请历史额度
|
AffHistoryQuota int `json:"aff_history_quota" gorm:"type:int;default:0;column:aff_history"` // 邀请历史额度
|
||||||
InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"`
|
InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
LinuxDOId string `json:"linux_do_id" gorm:"column:linux_do_id;index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) GetAccessToken() string {
|
func (user *User) GetAccessToken() string {
|
||||||
@ -537,3 +538,17 @@ func GetUsernameById(id int) (username string, err error) {
|
|||||||
err = DB.Model(&User{}).Where("id = ?", id).Select("username").Find(&username).Error
|
err = DB.Model(&User{}).Where("id = ?", id).Select("username").Find(&username).Error
|
||||||
return username, err
|
return username, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsLinuxDOIdAlreadyTaken(linuxDOId string) bool {
|
||||||
|
var user User
|
||||||
|
err := DB.Unscoped().Where("linux_do_id = ?", linuxDOId).First(&user).Error
|
||||||
|
return !errors.Is(err, gorm.ErrRecordNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) FillUserByLinuxDOId() error {
|
||||||
|
if u.LinuxDOId == "" {
|
||||||
|
return errors.New("linux do id is empty")
|
||||||
|
}
|
||||||
|
err := DB.Where("linux_do_id = ?", u.LinuxDOId).First(u).Error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -25,6 +25,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)
|
||||||
|
@ -26,6 +26,7 @@ import Midjourney from './pages/Midjourney';
|
|||||||
import Pricing from './pages/Pricing/index.js';
|
import Pricing from './pages/Pricing/index.js';
|
||||||
import Task from "./pages/Task/index.js";
|
import Task from "./pages/Task/index.js";
|
||||||
import Playground from './components/Playground.js';
|
import Playground from './components/Playground.js';
|
||||||
|
import LinuxDoOAuth from './components/LinuxDoOAuth.js';
|
||||||
|
|
||||||
const Home = lazy(() => import('./pages/Home'));
|
const Home = lazy(() => import('./pages/Home'));
|
||||||
const Detail = lazy(() => import('./pages/Detail'));
|
const Detail = lazy(() => import('./pages/Detail'));
|
||||||
@ -181,6 +182,14 @@ function App() {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='/oauth/linuxdo'
|
||||||
|
element={
|
||||||
|
<Suspense fallback={<Loading></Loading>}>
|
||||||
|
<LinuxDoOAuth />
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='/setting'
|
path='/setting'
|
||||||
element={
|
element={
|
||||||
|
61
web/src/components/LinuxDoOAuth.js
Normal file
61
web/src/components/LinuxDoOAuth.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
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, updateAPI } from '../helpers';
|
||||||
|
import { UserContext } from '../context/User';
|
||||||
|
import { setUserData } from '../helpers/data.js';
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
const res = await API.get(`/api/oauth/linuxdo?code=${code}&state=${state}`);
|
||||||
|
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));
|
||||||
|
setUserData(data);
|
||||||
|
updateAPI()
|
||||||
|
showSuccess('登录成功!');
|
||||||
|
navigate('/token');
|
||||||
|
}
|
||||||
|
} 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 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;
|
@ -1,8 +1,15 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
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, showError, showInfo, showSuccess, updateAPI } from '../helpers';
|
import {
|
||||||
import { onGitHubOAuthClicked } from './utils';
|
API,
|
||||||
|
getLogo,
|
||||||
|
showError,
|
||||||
|
showInfo,
|
||||||
|
showSuccess,
|
||||||
|
updateAPI,
|
||||||
|
} from '../helpers';
|
||||||
|
import { onGitHubOAuthClicked, onLinuxDOOAuthClicked } from './utils';
|
||||||
import Turnstile from 'react-turnstile';
|
import Turnstile from 'react-turnstile';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -17,7 +24,7 @@ import Title from '@douyinfe/semi-ui/lib/es/typography/title';
|
|||||||
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
|
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, IconAlarm } from '@douyinfe/semi-icons';
|
||||||
import WeChatIcon from './WeChatIcon';
|
import WeChatIcon from './WeChatIcon';
|
||||||
import { setUserData } from '../helpers/data.js';
|
import { setUserData } from '../helpers/data.js';
|
||||||
|
|
||||||
@ -72,7 +79,7 @@ const LoginForm = () => {
|
|||||||
userDispatch({ type: 'login', payload: data });
|
userDispatch({ type: 'login', payload: data });
|
||||||
localStorage.setItem('user', JSON.stringify(data));
|
localStorage.setItem('user', JSON.stringify(data));
|
||||||
setUserData(data);
|
setUserData(data);
|
||||||
updateAPI()
|
updateAPI();
|
||||||
navigate('/');
|
navigate('/');
|
||||||
showSuccess('登录成功!');
|
showSuccess('登录成功!');
|
||||||
setShowWeChatLoginModal(false);
|
setShowWeChatLoginModal(false);
|
||||||
@ -103,7 +110,7 @@ const LoginForm = () => {
|
|||||||
if (success) {
|
if (success) {
|
||||||
userDispatch({ type: 'login', payload: data });
|
userDispatch({ type: 'login', payload: data });
|
||||||
setUserData(data);
|
setUserData(data);
|
||||||
updateAPI()
|
updateAPI();
|
||||||
showSuccess('登录成功!');
|
showSuccess('登录成功!');
|
||||||
if (username === 'root' && password === '123456') {
|
if (username === 'root' && password === '123456') {
|
||||||
Modal.error({
|
Modal.error({
|
||||||
@ -146,7 +153,7 @@ const LoginForm = () => {
|
|||||||
localStorage.setItem('user', JSON.stringify(data));
|
localStorage.setItem('user', JSON.stringify(data));
|
||||||
showSuccess('登录成功!');
|
showSuccess('登录成功!');
|
||||||
setUserData(data);
|
setUserData(data);
|
||||||
updateAPI()
|
updateAPI();
|
||||||
navigate('/');
|
navigate('/');
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
@ -214,7 +221,8 @@ const LoginForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
{status.github_oauth ||
|
{status.github_oauth ||
|
||||||
status.wechat_login ||
|
status.wechat_login ||
|
||||||
status.telegram_oauth ? (
|
status.telegram_oauth ||
|
||||||
|
status.linuxdo_oauth ? (
|
||||||
<>
|
<>
|
||||||
<Divider margin='12px' align='center'>
|
<Divider margin='12px' align='center'>
|
||||||
第三方登录
|
第三方登录
|
||||||
@ -237,6 +245,17 @@ const LoginForm = () => {
|
|||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
{status.linuxdo_oauth ? (
|
||||||
|
<Button
|
||||||
|
type='primary'
|
||||||
|
icon={<IconAlarm />}
|
||||||
|
onClick={() =>
|
||||||
|
onLinuxDOOAuthClicked(status.linuxdo_client_id)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
{status.wechat_login ? (
|
{status.wechat_login ? (
|
||||||
<Button
|
<Button
|
||||||
type='primary'
|
type='primary'
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from '../helpers';
|
} 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,
|
Avatar,
|
||||||
Banner,
|
Banner,
|
||||||
@ -519,7 +519,6 @@ const PersonalSetting = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: 10 }}>
|
<div style={{ marginTop: 10 }}>
|
||||||
<Typography.Text strong>Telegram</Typography.Text>
|
<Typography.Text strong>Telegram</Typography.Text>
|
||||||
<div
|
<div
|
||||||
@ -551,7 +550,36 @@ const PersonalSetting = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
<Typography.Text strong>LinuxDO</Typography.Text>
|
||||||
|
<div
|
||||||
|
style={{ display: 'flex', justifyContent: 'space-between' }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
value={
|
||||||
|
userState.user && userState.user.linux_do_id !== ''
|
||||||
|
? userState.user.linux_do_id
|
||||||
|
: '未绑定'
|
||||||
|
}
|
||||||
|
readonly={true}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onLinuxDOOAuthClicked(status.linuxdo_client_id);
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
(userState.user && userState.user.linux_do_id !== '') ||
|
||||||
|
!status.linuxdo_oauth
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{status.linuxdo_oauth ? '绑定' : '未启用'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div style={{ marginTop: 10 }}>
|
<div style={{ marginTop: 10 }}>
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={generateAccessToken}>
|
<Button onClick={generateAccessToken}>
|
||||||
|
@ -53,6 +53,9 @@ const SystemSetting = () => {
|
|||||||
TelegramOAuthEnabled: '',
|
TelegramOAuthEnabled: '',
|
||||||
TelegramBotToken: '',
|
TelegramBotToken: '',
|
||||||
TelegramBotName: '',
|
TelegramBotName: '',
|
||||||
|
LinuxDOOAuthEnabled: '',
|
||||||
|
LinuxDOClientId: '',
|
||||||
|
LinuxDOClientSecret: '',
|
||||||
});
|
});
|
||||||
const [originInputs, setOriginInputs] = useState({});
|
const [originInputs, setOriginInputs] = useState({});
|
||||||
let [loading, setLoading] = useState(false);
|
let [loading, setLoading] = useState(false);
|
||||||
@ -103,6 +106,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':
|
||||||
@ -163,7 +167,9 @@ const SystemSetting = () => {
|
|||||||
name === 'EmailDomainWhitelist' ||
|
name === 'EmailDomainWhitelist' ||
|
||||||
name === 'TopupGroupRatio' ||
|
name === 'TopupGroupRatio' ||
|
||||||
name === 'TelegramBotToken' ||
|
name === 'TelegramBotToken' ||
|
||||||
name === 'TelegramBotName'
|
name === 'TelegramBotName' ||
|
||||||
|
name === 'LinuxDOClientId' ||
|
||||||
|
name === 'LinuxDOClientSecret'
|
||||||
) {
|
) {
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
} else {
|
} else {
|
||||||
@ -182,7 +188,7 @@ const SystemSetting = () => {
|
|||||||
if (inputs.WorkerValidKey !== '') {
|
if (inputs.WorkerValidKey !== '') {
|
||||||
await updateOption('WorkerValidKey', inputs.WorkerValidKey);
|
await updateOption('WorkerValidKey', inputs.WorkerValidKey);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const submitPayAddress = async () => {
|
const submitPayAddress = async () => {
|
||||||
if (inputs.ServerAddress === '') {
|
if (inputs.ServerAddress === '') {
|
||||||
@ -320,6 +326,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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid columns={1}>
|
<Grid columns={1}>
|
||||||
<Grid.Column>
|
<Grid.Column>
|
||||||
@ -340,7 +358,15 @@ const SystemSetting = () => {
|
|||||||
更新服务器地址
|
更新服务器地址
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
<Header as='h3' inverted={isDark}>
|
<Header as='h3' inverted={isDark}>
|
||||||
代理设置(支持 <a href='https://github.com/Calcium-Ion/new-api-worker' target='_blank' rel='noreferrer'>new-api-worker</a>)
|
代理设置(支持{' '}
|
||||||
|
<a
|
||||||
|
href='https://github.com/Calcium-Ion/new-api-worker'
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
new-api-worker
|
||||||
|
</a>
|
||||||
|
)
|
||||||
</Header>
|
</Header>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
@ -358,9 +384,7 @@ const SystemSetting = () => {
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitWorker}>
|
<Form.Button onClick={submitWorker}>更新Worker设置</Form.Button>
|
||||||
更新Worker设置
|
|
||||||
</Form.Button>
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Header as='h3' inverted={isDark}>
|
<Header as='h3' inverted={isDark}>
|
||||||
支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
|
支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
|
||||||
@ -483,6 +507,12 @@ const SystemSetting = () => {
|
|||||||
name='GitHubOAuthEnabled'
|
name='GitHubOAuthEnabled'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
|
<Form.Checkbox
|
||||||
|
checked={inputs.LinuxDOOAuthEnabled === 'true'}
|
||||||
|
label='允许通过 LinuxDO 账户登录 & 注册'
|
||||||
|
name='LinuxDOOAuthEnabled'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
<Form.Checkbox
|
<Form.Checkbox
|
||||||
checked={inputs.WeChatAuthEnabled === 'true'}
|
checked={inputs.WeChatAuthEnabled === 'true'}
|
||||||
label='允许通过微信登录 & 注册'
|
label='允许通过微信登录 & 注册'
|
||||||
@ -781,6 +811,48 @@ const SystemSetting = () => {
|
|||||||
<Form.Button onClick={submitTurnstile}>
|
<Form.Button onClick={submitTurnstile}>
|
||||||
保存 Turnstile 设置
|
保存 Turnstile 设置
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
|
<Divider />
|
||||||
|
<Header as='h3' inverted={isDark}>
|
||||||
|
配置 LinuxDO OAuth App
|
||||||
|
<Header.Subheader>
|
||||||
|
用以支持通过 LinuxDO 进行登录注册,
|
||||||
|
<a
|
||||||
|
href='https://connect.linux.do/'
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
点击此处
|
||||||
|
</a>
|
||||||
|
管理你的 LinuxDO OAuth App
|
||||||
|
</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='LinuxDO Client ID'
|
||||||
|
name='LinuxDOClientId'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={inputs.LinuxDOClientId}
|
||||||
|
placeholder='输入你注册的 LinuxDO OAuth APP 的 ID'
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
label='LinuxDO Client Secret'
|
||||||
|
name='LinuxDOClientSecret'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
type='password'
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={inputs.LinuxDOClientSecret}
|
||||||
|
placeholder='敏感信息不会发送到前端显示'
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Button onClick={submitLinuxDOOAuth}>
|
||||||
|
保存 LinuxDO OAuth 设置
|
||||||
|
</Form.Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -19,6 +19,14 @@ export async function onGitHubOAuthClicked(github_client_id) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function onLinuxDOOAuthClicked(linuxdo_client_id) {
|
||||||
|
const state = await getOAuthState();
|
||||||
|
if (!state) return;
|
||||||
|
window.open(
|
||||||
|
`https://connect.linux.do/oauth2/authorize?response_type=code&client_id=${linuxdo_client_id}&state=${state}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let channelModels = undefined;
|
let channelModels = undefined;
|
||||||
export async function loadChannelModels() {
|
export async function loadChannelModels() {
|
||||||
const res = await API.get('/api/models');
|
const res = await API.get('/api/models');
|
||||||
|
@ -150,6 +150,12 @@ const Home = () => {
|
|||||||
? '已启用'
|
? '已启用'
|
||||||
: '未启用'}
|
: '未启用'}
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Linux DO 身份验证:
|
||||||
|
{statusState?.status?.linuxdo_oauth === true
|
||||||
|
? '已启用'
|
||||||
|
: '未启用'}
|
||||||
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
Loading…
Reference in New Issue
Block a user