Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
352801cd
authored
Nov 22, 2025
by
StageDog
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 关联 discord 账号
parent
9b6b8d0e
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
454 additions
and
1 deletions
+454
-1
README.en.md
+1
-0
README.md
+1
-0
common/constants.go
+3
-0
controller/discord.go
+224
-0
controller/misc.go
+2
-0
controller/option.go
+8
-0
controller/user.go
+1
-0
docs/api/web_api.md
+1
-0
model/option.go
+9
-0
model/user.go
+13
-0
router/api-router.go
+1
-0
web/src/App.jsx
+8
-0
web/src/components/auth/LoginForm.jsx
+33
-0
web/src/components/auth/RegisterForm.jsx
+27
-0
web/src/components/settings/SystemSetting.jsx
+65
-0
web/src/components/settings/personal/cards/AccountManagement.jsx
+43
-1
web/src/components/table/users/modals/EditUserModal.jsx
+2
-0
web/src/helpers/api.js
+11
-0
web/src/i18n/locales/zh.json
+1
-0
No files found.
README.en.md
View file @
352801cd
...
@@ -193,6 +193,7 @@ docker run --name new-api -d --restart always \
...
@@ -193,6 +193,7 @@ docker run --name new-api -d --restart always \
### 🔐 Authorization and Security
### 🔐 Authorization and Security
-
😈 Discord authorization login
-
🤖 LinuxDO authorization login
-
🤖 LinuxDO authorization login
-
📱 Telegram authorization login
-
📱 Telegram authorization login
-
🔑 OIDC unified authentication
-
🔑 OIDC unified authentication
...
...
README.md
View file @
352801cd
...
@@ -193,6 +193,7 @@ docker run --name new-api -d --restart always \
...
@@ -193,6 +193,7 @@ docker run --name new-api -d --restart always \
### 🔐 授权与安全
### 🔐 授权与安全
-
😈 Discord 授权登录
-
🤖 LinuxDO 授权登录
-
🤖 LinuxDO 授权登录
-
📱 Telegram 授权登录
-
📱 Telegram 授权登录
-
🔑 OIDC 统一认证
-
🔑 OIDC 统一认证
...
...
common/constants.go
View file @
352801cd
...
@@ -44,6 +44,7 @@ var PasswordLoginEnabled = true
...
@@ -44,6 +44,7 @@ var PasswordLoginEnabled = true
var
PasswordRegisterEnabled
=
true
var
PasswordRegisterEnabled
=
true
var
EmailVerificationEnabled
=
false
var
EmailVerificationEnabled
=
false
var
GitHubOAuthEnabled
=
false
var
GitHubOAuthEnabled
=
false
var
DiscordOAuthEnabled
=
false
var
LinuxDOOAuthEnabled
=
false
var
LinuxDOOAuthEnabled
=
false
var
WeChatAuthEnabled
=
false
var
WeChatAuthEnabled
=
false
var
TelegramOAuthEnabled
=
false
var
TelegramOAuthEnabled
=
false
...
@@ -82,6 +83,8 @@ var SMTPToken = ""
...
@@ -82,6 +83,8 @@ var SMTPToken = ""
var
GitHubClientId
=
""
var
GitHubClientId
=
""
var
GitHubClientSecret
=
""
var
GitHubClientSecret
=
""
var
DiscordClientId
=
""
var
DiscordClientSecret
=
""
var
LinuxDOClientId
=
""
var
LinuxDOClientId
=
""
var
LinuxDOClientSecret
=
""
var
LinuxDOClientSecret
=
""
var
LinuxDOMinimumTrustLevel
=
0
var
LinuxDOMinimumTrustLevel
=
0
...
...
controller/discord.go
0 → 100644
View file @
352801cd
package
controller
import
(
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/setting/system_setting"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
type
DiscordResponse
struct
{
AccessToken
string
`json:"access_token"`
IDToken
string
`json:"id_token"`
RefreshToken
string
`json:"refresh_token"`
TokenType
string
`json:"token_type"`
ExpiresIn
int
`json:"expires_in"`
Scope
string
`json:"scope"`
}
type
DiscordUser
struct
{
UID
string
`json:"id"`
ID
string
`json:"username"`
Name
string
`json:"global_name"`
}
func
getDiscordUserInfoByCode
(
code
string
)
(
*
DiscordUser
,
error
)
{
if
code
==
""
{
return
nil
,
errors
.
New
(
"无效的参数"
)
}
values
:=
url
.
Values
{}
values
.
Set
(
"client_id"
,
common
.
DiscordClientId
)
values
.
Set
(
"client_secret"
,
common
.
DiscordClientSecret
)
values
.
Set
(
"code"
,
code
)
values
.
Set
(
"grant_type"
,
"authorization_code"
)
values
.
Set
(
"redirect_uri"
,
fmt
.
Sprintf
(
"%s/oauth/discord"
,
system_setting
.
ServerAddress
))
formData
:=
values
.
Encode
()
req
,
err
:=
http
.
NewRequest
(
"POST"
,
"https://discord.com/api/v10/oauth2/token"
,
strings
.
NewReader
(
formData
))
if
err
!=
nil
{
return
nil
,
err
}
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
{
common
.
SysLog
(
err
.
Error
())
return
nil
,
errors
.
New
(
"无法连接至 Discord 服务器,请稍后重试!"
)
}
defer
res
.
Body
.
Close
()
var
discordResponse
DiscordResponse
err
=
json
.
NewDecoder
(
res
.
Body
)
.
Decode
(
&
discordResponse
)
if
err
!=
nil
{
return
nil
,
err
}
if
discordResponse
.
AccessToken
==
""
{
common
.
SysError
(
"Discord 获取 Token 失败,请检查设置!"
)
return
nil
,
errors
.
New
(
"Discord 获取 Token 失败,请检查设置!"
)
}
req
,
err
=
http
.
NewRequest
(
"GET"
,
"https://discord.com/api/v10/users/@me"
,
nil
)
if
err
!=
nil
{
return
nil
,
err
}
req
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
discordResponse
.
AccessToken
)
res2
,
err
:=
client
.
Do
(
req
)
if
err
!=
nil
{
common
.
SysLog
(
err
.
Error
())
return
nil
,
errors
.
New
(
"无法连接至 Discord 服务器,请稍后重试!"
)
}
defer
res2
.
Body
.
Close
()
if
res2
.
StatusCode
!=
http
.
StatusOK
{
common
.
SysError
(
"Discord 获取用户信息失败!请检查设置!"
)
return
nil
,
errors
.
New
(
"Discord 获取用户信息失败!请检查设置!"
)
}
var
discordUser
DiscordUser
err
=
json
.
NewDecoder
(
res2
.
Body
)
.
Decode
(
&
discordUser
)
if
err
!=
nil
{
return
nil
,
err
}
if
discordUser
.
UID
==
""
||
discordUser
.
ID
==
""
{
common
.
SysError
(
"Discord 获取用户信息为空!请检查设置!"
)
return
nil
,
errors
.
New
(
"Discord 获取用户信息为空!请检查设置!"
)
}
return
&
discordUser
,
nil
}
func
DiscordOAuth
(
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
{
DiscordBind
(
c
)
return
}
if
!
common
.
DiscordOAuthEnabled
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"管理员未开启通过 discord 登录以及注册"
,
})
return
}
code
:=
c
.
Query
(
"code"
)
discordUser
,
err
:=
getDiscordUserInfoByCode
(
code
)
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
user
:=
model
.
User
{
DiscordId
:
discordUser
.
UID
,
}
if
model
.
IsDiscordIdAlreadyTaken
(
user
.
DiscordId
)
{
err
:=
user
.
FillUserByDiscordId
()
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
err
.
Error
(),
})
return
}
}
else
{
if
common
.
RegisterEnabled
{
if
discordUser
.
ID
!=
""
{
user
.
Username
=
discordUser
.
ID
}
else
{
user
.
Username
=
"discord_"
+
strconv
.
Itoa
(
model
.
GetMaxUserId
()
+
1
)
}
if
discordUser
.
Name
!=
""
{
user
.
DisplayName
=
discordUser
.
Name
}
else
{
user
.
DisplayName
=
"Discord User"
}
err
:=
user
.
Insert
(
0
)
if
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
DiscordBind
(
c
*
gin
.
Context
)
{
if
!
common
.
DiscordOAuthEnabled
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"管理员未开启通过 Discord 登录以及注册"
,
})
return
}
code
:=
c
.
Query
(
"code"
)
discordUser
,
err
:=
getDiscordUserInfoByCode
(
code
)
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
user
:=
model
.
User
{
DiscordId
:
discordUser
.
UID
,
}
if
model
.
IsDiscordIdAlreadyTaken
(
user
.
DiscordId
)
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"该 Discord 账户已被绑定"
,
})
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
{
common
.
ApiError
(
c
,
err
)
return
}
user
.
DiscordId
=
discordUser
.
UID
err
=
user
.
Update
(
false
)
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
"bind"
,
})
}
controller/misc.go
View file @
352801cd
...
@@ -52,6 +52,8 @@ func GetStatus(c *gin.Context) {
...
@@ -52,6 +52,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
,
"discord_oauth"
:
common
.
DiscordOAuthEnabled
,
"discord_client_id"
:
common
.
DiscordClientId
,
"linuxdo_oauth"
:
common
.
LinuxDOOAuthEnabled
,
"linuxdo_oauth"
:
common
.
LinuxDOOAuthEnabled
,
"linuxdo_client_id"
:
common
.
LinuxDOClientId
,
"linuxdo_client_id"
:
common
.
LinuxDOClientId
,
"linuxdo_minimum_trust_level"
:
common
.
LinuxDOMinimumTrustLevel
,
"linuxdo_minimum_trust_level"
:
common
.
LinuxDOMinimumTrustLevel
,
...
...
controller/option.go
View file @
352801cd
...
@@ -71,6 +71,14 @@ func UpdateOption(c *gin.Context) {
...
@@ -71,6 +71,14 @@ func UpdateOption(c *gin.Context) {
})
})
return
return
}
}
case
"DiscordOAuthEnabled"
:
if
option
.
Value
==
"true"
&&
common
.
DiscordClientId
==
""
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"无法启用 Discord OAuth,请先填入 Discord Client Id 以及 Discord Client Secret!"
,
})
return
}
case
"oidc.enabled"
:
case
"oidc.enabled"
:
if
option
.
Value
==
"true"
&&
system_setting
.
GetOIDCSettings
()
.
ClientId
==
""
{
if
option
.
Value
==
"true"
&&
system_setting
.
GetOIDCSettings
()
.
ClientId
==
""
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
...
...
controller/user.go
View file @
352801cd
...
@@ -453,6 +453,7 @@ func GetSelf(c *gin.Context) {
...
@@ -453,6 +453,7 @@ func GetSelf(c *gin.Context) {
"status"
:
user
.
Status
,
"status"
:
user
.
Status
,
"email"
:
user
.
Email
,
"email"
:
user
.
Email
,
"github_id"
:
user
.
GitHubId
,
"github_id"
:
user
.
GitHubId
,
"discord_id"
:
user
.
DiscordId
,
"oidc_id"
:
user
.
OidcId
,
"oidc_id"
:
user
.
OidcId
,
"wechat_id"
:
user
.
WeChatId
,
"wechat_id"
:
user
.
WeChatId
,
"telegram_id"
:
user
.
TelegramId
,
"telegram_id"
:
user
.
TelegramId
,
...
...
docs/api/web_api.md
View file @
352801cd
...
@@ -42,6 +42,7 @@
...
@@ -42,6 +42,7 @@
| 方法 | 路径 | 鉴权 | 说明 |
| 方法 | 路径 | 鉴权 | 说明 |
|------|------|------|------|
|------|------|------|------|
| GET | /api/oauth/github | 公开 | GitHub OAuth 跳转 |
| GET | /api/oauth/github | 公开 | GitHub OAuth 跳转 |
| GET | /api/oauth/discord | 公开 | Discord 通用 OAuth 跳转 |
| GET | /api/oauth/oidc | 公开 | OIDC 通用 OAuth 跳转 |
| GET | /api/oauth/oidc | 公开 | OIDC 通用 OAuth 跳转 |
| GET | /api/oauth/linuxdo | 公开 | LinuxDo OAuth 跳转 |
| GET | /api/oauth/linuxdo | 公开 | LinuxDo OAuth 跳转 |
| GET | /api/oauth/wechat | 公开 | 微信扫码登录跳转 |
| GET | /api/oauth/wechat | 公开 | 微信扫码登录跳转 |
...
...
model/option.go
View file @
352801cd
...
@@ -38,6 +38,7 @@ func InitOptionMap() {
...
@@ -38,6 +38,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
[
"DiscordOAuthEnabled"
]
=
strconv
.
FormatBool
(
common
.
DiscordOAuthEnabled
)
common
.
OptionMap
[
"LinuxDOOAuthEnabled"
]
=
strconv
.
FormatBool
(
common
.
LinuxDOOAuthEnabled
)
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
)
...
@@ -95,6 +96,8 @@ func InitOptionMap() {
...
@@ -95,6 +96,8 @@ func InitOptionMap() {
common
.
OptionMap
[
"PayMethods"
]
=
operation_setting
.
PayMethods2JsonString
()
common
.
OptionMap
[
"PayMethods"
]
=
operation_setting
.
PayMethods2JsonString
()
common
.
OptionMap
[
"GitHubClientId"
]
=
""
common
.
OptionMap
[
"GitHubClientId"
]
=
""
common
.
OptionMap
[
"GitHubClientSecret"
]
=
""
common
.
OptionMap
[
"GitHubClientSecret"
]
=
""
common
.
OptionMap
[
"DiscordClientId"
]
=
""
common
.
OptionMap
[
"DiscordClientSecret"
]
=
""
common
.
OptionMap
[
"TelegramBotToken"
]
=
""
common
.
OptionMap
[
"TelegramBotToken"
]
=
""
common
.
OptionMap
[
"TelegramBotName"
]
=
""
common
.
OptionMap
[
"TelegramBotName"
]
=
""
common
.
OptionMap
[
"WeChatServerAddress"
]
=
""
common
.
OptionMap
[
"WeChatServerAddress"
]
=
""
...
@@ -224,6 +227,8 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -224,6 +227,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
"DiscordOAuthEnabled"
:
common
.
DiscordOAuthEnabled
=
boolValue
case
"LinuxDOOAuthEnabled"
:
case
"LinuxDOOAuthEnabled"
:
common
.
LinuxDOOAuthEnabled
=
boolValue
common
.
LinuxDOOAuthEnabled
=
boolValue
case
"WeChatAuthEnabled"
:
case
"WeChatAuthEnabled"
:
...
@@ -360,6 +365,10 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -360,6 +365,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
"DiscordClientId"
:
common
.
DiscordClientId
=
value
case
"DiscordClientSecret"
:
common
.
DiscordClientSecret
=
value
case
"LinuxDOClientId"
:
case
"LinuxDOClientId"
:
common
.
LinuxDOClientId
=
value
common
.
LinuxDOClientId
=
value
case
"LinuxDOClientSecret"
:
case
"LinuxDOClientSecret"
:
...
...
model/user.go
View file @
352801cd
...
@@ -27,6 +27,7 @@ type User struct {
...
@@ -27,6 +27,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"`
DiscordId
string
`json:"discord_id" gorm:"column:discord_id;index"`
OidcId
string
`json:"oidc_id" gorm:"column:oidc_id;index"`
OidcId
string
`json:"oidc_id" gorm:"column:oidc_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"`
...
@@ -539,6 +540,14 @@ func (user *User) FillUserByGitHubId() error {
...
@@ -539,6 +540,14 @@ func (user *User) FillUserByGitHubId() error {
return
nil
return
nil
}
}
func
(
user
*
User
)
FillUserByDiscordId
()
error
{
if
user
.
DiscordId
==
""
{
return
errors
.
New
(
"discord id 为空!"
)
}
DB
.
Where
(
User
{
DiscordId
:
user
.
DiscordId
})
.
First
(
user
)
return
nil
}
func
(
user
*
User
)
FillUserByOidcId
()
error
{
func
(
user
*
User
)
FillUserByOidcId
()
error
{
if
user
.
OidcId
==
""
{
if
user
.
OidcId
==
""
{
return
errors
.
New
(
"oidc id 为空!"
)
return
errors
.
New
(
"oidc id 为空!"
)
...
@@ -578,6 +587,10 @@ func IsGitHubIdAlreadyTaken(githubId string) bool {
...
@@ -578,6 +587,10 @@ func IsGitHubIdAlreadyTaken(githubId string) bool {
return
DB
.
Unscoped
()
.
Where
(
"github_id = ?"
,
githubId
)
.
Find
(
&
User
{})
.
RowsAffected
==
1
return
DB
.
Unscoped
()
.
Where
(
"github_id = ?"
,
githubId
)
.
Find
(
&
User
{})
.
RowsAffected
==
1
}
}
func
IsDiscordIdAlreadyTaken
(
discordId
string
)
bool
{
return
DB
.
Where
(
"discord_id = ?"
,
discordId
)
.
Find
(
&
User
{})
.
RowsAffected
==
1
}
func
IsOidcIdAlreadyTaken
(
oidcId
string
)
bool
{
func
IsOidcIdAlreadyTaken
(
oidcId
string
)
bool
{
return
DB
.
Where
(
"oidc_id = ?"
,
oidcId
)
.
Find
(
&
User
{})
.
RowsAffected
==
1
return
DB
.
Where
(
"oidc_id = ?"
,
oidcId
)
.
Find
(
&
User
{})
.
RowsAffected
==
1
}
}
...
...
router/api-router.go
View file @
352801cd
...
@@ -30,6 +30,7 @@ func SetApiRouter(router *gin.Engine) {
...
@@ -30,6 +30,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/discord"
,
middleware
.
CriticalRateLimit
(),
controller
.
DiscordOAuth
)
apiRouter
.
GET
(
"/oauth/oidc"
,
middleware
.
CriticalRateLimit
(),
controller
.
OidcAuth
)
apiRouter
.
GET
(
"/oauth/oidc"
,
middleware
.
CriticalRateLimit
(),
controller
.
OidcAuth
)
apiRouter
.
GET
(
"/oauth/linuxdo"
,
middleware
.
CriticalRateLimit
(),
controller
.
LinuxdoOAuth
)
apiRouter
.
GET
(
"/oauth/linuxdo"
,
middleware
.
CriticalRateLimit
(),
controller
.
LinuxdoOAuth
)
apiRouter
.
GET
(
"/oauth/state"
,
middleware
.
CriticalRateLimit
(),
controller
.
GenerateOAuthCode
)
apiRouter
.
GET
(
"/oauth/state"
,
middleware
.
CriticalRateLimit
(),
controller
.
GenerateOAuthCode
)
...
...
web/src/App.jsx
View file @
352801cd
...
@@ -193,6 +193,14 @@ function App() {
...
@@ -193,6 +193,14 @@ function App() {
}
}
/>
/>
<
Route
<
Route
path=
'/oauth/discord'
element=
{
<
Suspense
fallback=
{
<
Loading
></
Loading
>
}
key=
{
location
.
pathname
}
>
<
OAuth2Callback
type=
'discord'
></
OAuth2Callback
>
</
Suspense
>
}
/>
<
Route
path=
'/oauth/oidc'
path=
'/oauth/oidc'
element=
{
element=
{
<
Suspense
fallback=
{
<
Loading
></
Loading
>
}
>
<
Suspense
fallback=
{
<
Loading
></
Loading
>
}
>
...
...
web/src/components/auth/LoginForm.jsx
View file @
352801cd
...
@@ -30,6 +30,7 @@ import {
...
@@ -30,6 +30,7 @@ import {
getSystemName
,
getSystemName
,
setUserData
,
setUserData
,
onGitHubOAuthClicked
,
onGitHubOAuthClicked
,
onDiscordOAuthClicked
,
onOIDCClicked
,
onOIDCClicked
,
onLinuxDOOAuthClicked
,
onLinuxDOOAuthClicked
,
prepareCredentialRequestOptions
,
prepareCredentialRequestOptions
,
...
@@ -53,6 +54,7 @@ import WeChatIcon from '../common/logo/WeChatIcon';
...
@@ -53,6 +54,7 @@ import WeChatIcon from '../common/logo/WeChatIcon';
import
LinuxDoIcon
from
'../common/logo/LinuxDoIcon'
;
import
LinuxDoIcon
from
'../common/logo/LinuxDoIcon'
;
import
TwoFAVerification
from
'./TwoFAVerification'
;
import
TwoFAVerification
from
'./TwoFAVerification'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
SiDiscord
}
from
'react-icons/si'
;
const
LoginForm
=
()
=>
{
const
LoginForm
=
()
=>
{
let
navigate
=
useNavigate
();
let
navigate
=
useNavigate
();
...
@@ -73,6 +75,7 @@ const LoginForm = () => {
...
@@ -73,6 +75,7 @@ const LoginForm = () => {
const
[
showEmailLogin
,
setShowEmailLogin
]
=
useState
(
false
);
const
[
showEmailLogin
,
setShowEmailLogin
]
=
useState
(
false
);
const
[
wechatLoading
,
setWechatLoading
]
=
useState
(
false
);
const
[
wechatLoading
,
setWechatLoading
]
=
useState
(
false
);
const
[
githubLoading
,
setGithubLoading
]
=
useState
(
false
);
const
[
githubLoading
,
setGithubLoading
]
=
useState
(
false
);
const
[
discordLoading
,
setDiscordLoading
]
=
useState
(
false
);
const
[
oidcLoading
,
setOidcLoading
]
=
useState
(
false
);
const
[
oidcLoading
,
setOidcLoading
]
=
useState
(
false
);
const
[
linuxdoLoading
,
setLinuxdoLoading
]
=
useState
(
false
);
const
[
linuxdoLoading
,
setLinuxdoLoading
]
=
useState
(
false
);
const
[
emailLoginLoading
,
setEmailLoginLoading
]
=
useState
(
false
);
const
[
emailLoginLoading
,
setEmailLoginLoading
]
=
useState
(
false
);
...
@@ -298,6 +301,21 @@ const LoginForm = () => {
...
@@ -298,6 +301,21 @@ const LoginForm = () => {
}
}
};
};
// 包装的Discord登录点击处理
const
handleDiscordClick
=
()
=>
{
if
((
hasUserAgreement
||
hasPrivacyPolicy
)
&&
!
agreedToTerms
)
{
showInfo
(
t
(
'请先阅读并同意用户协议和隐私政策'
));
return
;
}
setDiscordLoading
(
true
);
try
{
onDiscordOAuthClicked
(
status
.
discord_client_id
);
}
finally
{
// 由于重定向,这里不会执行到,但为了完整性添加
setTimeout
(()
=>
setDiscordLoading
(
false
),
3000
);
}
};
// 包装的OIDC登录点击处理
// 包装的OIDC登录点击处理
const
handleOIDCClick
=
()
=>
{
const
handleOIDCClick
=
()
=>
{
if
((
hasUserAgreement
||
hasPrivacyPolicy
)
&&
!
agreedToTerms
)
{
if
((
hasUserAgreement
||
hasPrivacyPolicy
)
&&
!
agreedToTerms
)
{
...
@@ -472,6 +490,19 @@ const LoginForm = () => {
...
@@ -472,6 +490,19 @@ const LoginForm = () => {
</
Button
>
</
Button
>
)
}
)
}
{
status
.
discord_oauth
&&
(
<
Button
theme=
'outline'
className=
'w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
type=
'tertiary'
icon=
{
<
SiDiscord
style=
{
{
color
:
'#5865F2'
,
width
:
'20px'
,
height
:
'20px'
}
}
/>
}
onClick=
{
handleDiscordClick
}
loading=
{
discordLoading
}
>
<
span
className=
'ml-3'
>
{
t
(
'使用 Discord 继续'
)
}
</
span
>
</
Button
>
)
}
{
status
.
oidc_enabled
&&
(
{
status
.
oidc_enabled
&&
(
<
Button
<
Button
theme=
'outline'
theme=
'outline'
...
@@ -714,6 +745,7 @@ const LoginForm = () => {
...
@@ -714,6 +745,7 @@ const LoginForm = () => {
</
Form
>
</
Form
>
{
(
status
.
github_oauth
||
{
(
status
.
github_oauth
||
status
.
discord_oauth
||
status
.
oidc_enabled
||
status
.
oidc_enabled
||
status
.
wechat_login
||
status
.
wechat_login
||
status
.
linuxdo_oauth
||
status
.
linuxdo_oauth
||
...
@@ -849,6 +881,7 @@ const LoginForm = () => {
...
@@ -849,6 +881,7 @@ const LoginForm = () => {
{
showEmailLogin
||
{
showEmailLogin
||
!
(
!
(
status
.
github_oauth
||
status
.
github_oauth
||
status
.
discord_oauth
||
status
.
oidc_enabled
||
status
.
oidc_enabled
||
status
.
wechat_login
||
status
.
wechat_login
||
status
.
linuxdo_oauth
||
status
.
linuxdo_oauth
||
...
...
web/src/components/auth/RegisterForm.jsx
View file @
352801cd
...
@@ -28,6 +28,7 @@ import {
...
@@ -28,6 +28,7 @@ import {
updateAPI
,
updateAPI
,
getSystemName
,
getSystemName
,
setUserData
,
setUserData
,
onDiscordOAuthClicked
,
}
from
'../../helpers'
;
}
from
'../../helpers'
;
import
Turnstile
from
'react-turnstile'
;
import
Turnstile
from
'react-turnstile'
;
import
{
Button
,
Card
,
Checkbox
,
Divider
,
Form
,
Icon
,
Modal
}
from
'@douyinfe/semi-ui'
;
import
{
Button
,
Card
,
Checkbox
,
Divider
,
Form
,
Icon
,
Modal
}
from
'@douyinfe/semi-ui'
;
...
@@ -51,6 +52,7 @@ import WeChatIcon from '../common/logo/WeChatIcon';
...
@@ -51,6 +52,7 @@ import WeChatIcon from '../common/logo/WeChatIcon';
import
TelegramLoginButton
from
'react-telegram-login/src'
;
import
TelegramLoginButton
from
'react-telegram-login/src'
;
import
{
UserContext
}
from
'../../context/User'
;
import
{
UserContext
}
from
'../../context/User'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
SiDiscord
}
from
'react-icons/si'
;
const
RegisterForm
=
()
=>
{
const
RegisterForm
=
()
=>
{
let
navigate
=
useNavigate
();
let
navigate
=
useNavigate
();
...
@@ -72,6 +74,7 @@ const RegisterForm = () => {
...
@@ -72,6 +74,7 @@ const RegisterForm = () => {
const
[
showEmailRegister
,
setShowEmailRegister
]
=
useState
(
false
);
const
[
showEmailRegister
,
setShowEmailRegister
]
=
useState
(
false
);
const
[
wechatLoading
,
setWechatLoading
]
=
useState
(
false
);
const
[
wechatLoading
,
setWechatLoading
]
=
useState
(
false
);
const
[
githubLoading
,
setGithubLoading
]
=
useState
(
false
);
const
[
githubLoading
,
setGithubLoading
]
=
useState
(
false
);
const
[
discordLoading
,
setDiscordLoading
]
=
useState
(
false
);
const
[
oidcLoading
,
setOidcLoading
]
=
useState
(
false
);
const
[
oidcLoading
,
setOidcLoading
]
=
useState
(
false
);
const
[
linuxdoLoading
,
setLinuxdoLoading
]
=
useState
(
false
);
const
[
linuxdoLoading
,
setLinuxdoLoading
]
=
useState
(
false
);
const
[
emailRegisterLoading
,
setEmailRegisterLoading
]
=
useState
(
false
);
const
[
emailRegisterLoading
,
setEmailRegisterLoading
]
=
useState
(
false
);
...
@@ -264,6 +267,15 @@ const RegisterForm = () => {
...
@@ -264,6 +267,15 @@ const RegisterForm = () => {
}
}
};
};
const
handleDiscordClick
=
()
=>
{
setDiscordLoading
(
true
);
try
{
onDiscordOAuthClicked
(
status
.
discord_client_id
);
}
finally
{
setTimeout
(()
=>
setDiscordLoading
(
false
),
3000
);
}
};
const
handleOIDCClick
=
()
=>
{
const
handleOIDCClick
=
()
=>
{
setOidcLoading
(
true
);
setOidcLoading
(
true
);
try
{
try
{
...
@@ -377,6 +389,19 @@ const RegisterForm = () => {
...
@@ -377,6 +389,19 @@ const RegisterForm = () => {
</
Button
>
</
Button
>
)
}
)
}
{
status
.
discord_oauth
&&
(
<
Button
theme=
'outline'
className=
'w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
type=
'tertiary'
icon=
{
<
SiDiscord
style=
{
{
color
:
'#5865F2'
,
width
:
'20px'
,
height
:
'20px'
}
}
/>
}
onClick=
{
handleDiscordClick
}
loading=
{
discordLoading
}
>
<
span
className=
'ml-3'
>
{
t
(
'使用 Discord 继续'
)
}
</
span
>
</
Button
>
)
}
{
status
.
oidc_enabled
&&
(
{
status
.
oidc_enabled
&&
(
<
Button
<
Button
theme=
'outline'
theme=
'outline'
...
@@ -591,6 +616,7 @@ const RegisterForm = () => {
...
@@ -591,6 +616,7 @@ const RegisterForm = () => {
</
Form
>
</
Form
>
{
(
status
.
github_oauth
||
{
(
status
.
github_oauth
||
status
.
discord_oauth
||
status
.
oidc_enabled
||
status
.
oidc_enabled
||
status
.
wechat_login
||
status
.
wechat_login
||
status
.
linuxdo_oauth
||
status
.
linuxdo_oauth
||
...
@@ -686,6 +712,7 @@ const RegisterForm = () => {
...
@@ -686,6 +712,7 @@ const RegisterForm = () => {
{
showEmailRegister
||
{
showEmailRegister
||
!
(
!
(
status
.
github_oauth
||
status
.
github_oauth
||
status
.
discord_oauth
||
status
.
oidc_enabled
||
status
.
oidc_enabled
||
status
.
wechat_login
||
status
.
wechat_login
||
status
.
linuxdo_oauth
||
status
.
linuxdo_oauth
||
...
...
web/src/components/settings/SystemSetting.jsx
View file @
352801cd
...
@@ -52,6 +52,9 @@ const SystemSetting = () => {
...
@@ -52,6 +52,9 @@ const SystemSetting = () => {
GitHubOAuthEnabled
:
''
,
GitHubOAuthEnabled
:
''
,
GitHubClientId
:
''
,
GitHubClientId
:
''
,
GitHubClientSecret
:
''
,
GitHubClientSecret
:
''
,
DiscordOAuthEnabled
:
''
,
DiscordClientId
:
''
,
DiscordClientSecret
:
''
,
'oidc.enabled'
:
''
,
'oidc.enabled'
:
''
,
'oidc.client_id'
:
''
,
'oidc.client_id'
:
''
,
'oidc.client_secret'
:
''
,
'oidc.client_secret'
:
''
,
...
@@ -179,6 +182,7 @@ const SystemSetting = () => {
...
@@ -179,6 +182,7 @@ const SystemSetting = () => {
case
'EmailAliasRestrictionEnabled'
:
case
'EmailAliasRestrictionEnabled'
:
case
'SMTPSSLEnabled'
:
case
'SMTPSSLEnabled'
:
case
'LinuxDOOAuthEnabled'
:
case
'LinuxDOOAuthEnabled'
:
case
'DiscordOAuthEnabled'
:
case
'oidc.enabled'
:
case
'oidc.enabled'
:
case
'passkey.enabled'
:
case
'passkey.enabled'
:
case
'passkey.allow_insecure_origin'
:
case
'passkey.allow_insecure_origin'
:
...
@@ -473,6 +477,27 @@ const SystemSetting = () => {
...
@@ -473,6 +477,27 @@ const SystemSetting = () => {
}
}
};
};
const
submitDiscordOAuth
=
async
()
=>
{
const
options
=
[];
if
(
originInputs
[
'DiscordClientId'
]
!==
inputs
.
DiscordClientId
)
{
options
.
push
({
key
:
'DiscordClientId'
,
value
:
inputs
.
DiscordClientId
});
}
if
(
originInputs
[
'DiscordClientSecret'
]
!==
inputs
.
DiscordClientSecret
&&
inputs
.
DiscordClientSecret
!==
''
)
{
options
.
push
({
key
:
'DiscordClientSecret'
,
value
:
inputs
.
DiscordClientSecret
,
});
}
if
(
options
.
length
>
0
)
{
await
updateOptions
(
options
);
}
};
const
submitOIDCSettings
=
async
()
=>
{
const
submitOIDCSettings
=
async
()
=>
{
if
(
inputs
[
'oidc.well_known'
]
&&
inputs
[
'oidc.well_known'
]
!==
''
)
{
if
(
inputs
[
'oidc.well_known'
]
&&
inputs
[
'oidc.well_known'
]
!==
''
)
{
if
(
if
(
...
@@ -1015,6 +1040,15 @@ const SystemSetting = () => {
...
@@ -1015,6 +1040,15 @@ const SystemSetting = () => {
{
t
(
'允许通过 GitHub 账户登录 & 注册'
)
}
{
t
(
'允许通过 GitHub 账户登录 & 注册'
)
}
</
Form
.
Checkbox
>
</
Form
.
Checkbox
>
<
Form
.
Checkbox
<
Form
.
Checkbox
field=
'DiscordOAuthEnabled'
noLabel
onChange=
{
(
e
)
=>
handleCheckboxChange
(
'DiscordOAuthEnabled'
,
e
)
}
>
{
t
(
'允许通过 Discord 账户登录 & 注册'
)
}
</
Form
.
Checkbox
>
<
Form
.
Checkbox
field=
'LinuxDOOAuthEnabled'
field=
'LinuxDOOAuthEnabled'
noLabel
noLabel
onChange=
{
(
e
)
=>
onChange=
{
(
e
)
=>
...
@@ -1411,6 +1445,37 @@ const SystemSetting = () => {
...
@@ -1411,6 +1445,37 @@ const SystemSetting = () => {
</
Form
.
Section
>
</
Form
.
Section
>
</
Card
>
</
Card
>
<
Card
>
<
Card
>
<
Form
.
Section
text=
{
t
(
'配置 Discord OAuth'
)
}
>
<
Text
>
{
t
(
'用以支持通过 Discord 进行登录注册'
)
}
</
Text
>
<
Banner
type=
'info'
description=
{
`${t('Homepage URL 填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')},${t('Authorization callback URL 填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')}/oauth/discord`
}
style=
{
{
marginBottom
:
20
,
marginTop
:
16
}
}
/>
<
Row
gutter=
{
{
xs
:
8
,
sm
:
16
,
md
:
24
,
lg
:
24
,
xl
:
24
,
xxl
:
24
}
}
>
<
Col
xs=
{
24
}
sm=
{
24
}
md=
{
12
}
lg=
{
12
}
xl=
{
12
}
>
<
Form
.
Input
field=
'DiscordClientId'
label=
{
t
(
'Discord Client ID'
)
}
/>
</
Col
>
<
Col
xs=
{
24
}
sm=
{
24
}
md=
{
12
}
lg=
{
12
}
xl=
{
12
}
>
<
Form
.
Input
field=
'DiscordClientSecret'
label=
{
t
(
'Discord Client Secret'
)
}
type=
'password'
placeholder=
{
t
(
'敏感信息不会发送到前端显示'
)
}
/>
</
Col
>
</
Row
>
<
Button
onClick=
{
submitDiscordOAuth
}
>
{
t
(
'保存 Discord OAuth 设置'
)
}
</
Button
>
</
Form
.
Section
>
</
Card
>
<
Card
>
<
Form
.
Section
text=
{
t
(
'配置 Linux DO OAuth'
)
}
>
<
Form
.
Section
text=
{
t
(
'配置 Linux DO OAuth'
)
}
>
<
Text
>
<
Text
>
{
t
(
'用以支持通过 Linux DO 进行登录注册'
)
}
{
t
(
'用以支持通过 Linux DO 进行登录注册'
)
}
...
...
web/src/components/settings/personal/cards/AccountManagement.jsx
View file @
352801cd
...
@@ -38,13 +38,14 @@ import {
...
@@ -38,13 +38,14 @@ import {
IconLock
,
IconLock
,
IconDelete
,
IconDelete
,
}
from
'@douyinfe/semi-icons'
;
}
from
'@douyinfe/semi-icons'
;
import
{
SiTelegram
,
SiWechat
,
SiLinux
}
from
'react-icons/si'
;
import
{
SiTelegram
,
SiWechat
,
SiLinux
,
SiDiscord
}
from
'react-icons/si'
;
import
{
UserPlus
,
ShieldCheck
}
from
'lucide-react'
;
import
{
UserPlus
,
ShieldCheck
}
from
'lucide-react'
;
import
TelegramLoginButton
from
'react-telegram-login'
;
import
TelegramLoginButton
from
'react-telegram-login'
;
import
{
import
{
onGitHubOAuthClicked
,
onGitHubOAuthClicked
,
onOIDCClicked
,
onOIDCClicked
,
onLinuxDOOAuthClicked
,
onLinuxDOOAuthClicked
,
onDiscordOAuthClicked
,
}
from
'../../../../helpers'
;
}
from
'../../../../helpers'
;
import
TwoFASetting
from
'../components/TwoFASetting'
;
import
TwoFASetting
from
'../components/TwoFASetting'
;
...
@@ -247,6 +248,47 @@ const AccountManagement = ({
...
@@ -247,6 +248,47 @@ const AccountManagement = ({
</
div
>
</
div
>
</
Card
>
</
Card
>
{
/* Discord绑定 */
}
<
Card
className=
'!rounded-xl'
>
<
div
className=
'flex items-center justify-between gap-3'
>
<
div
className=
'flex items-center flex-1 min-w-0'
>
<
div
className=
'w-10 h-10 rounded-full bg-slate-100 dark:bg-slate-700 flex items-center justify-center mr-3 flex-shrink-0'
>
<
SiDiscord
size=
{
20
}
className=
'text-slate-600 dark:text-slate-300'
/>
</
div
>
<
div
className=
'flex-1 min-w-0'
>
<
div
className=
'font-medium text-gray-900'
>
{
t
(
'Discord'
)
}
</
div
>
<
div
className=
'text-sm text-gray-500 truncate'
>
{
renderAccountInfo
(
userState
.
user
?.
discord_id
,
t
(
'Discord ID'
),
)
}
</
div
>
</
div
>
</
div
>
<
div
className=
'flex-shrink-0'
>
<
Button
type=
'primary'
theme=
'outline'
size=
'small'
onClick=
{
()
=>
onDiscordOAuthClicked
(
status
.
discord_client_id
)
}
disabled=
{
isBound
(
userState
.
user
?.
discord_id
)
||
!
status
.
discord_oauth
}
>
{
status
.
discord_oauth
?
t
(
'绑定'
)
:
t
(
'未启用'
)
}
</
Button
>
</
div
>
</
div
>
</
Card
>
{
/* OIDC绑定 */
}
{
/* OIDC绑定 */
}
<
Card
className=
'!rounded-xl'
>
<
Card
className=
'!rounded-xl'
>
<
div
className=
'flex items-center justify-between gap-3'
>
<
div
className=
'flex items-center justify-between gap-3'
>
...
...
web/src/components/table/users/modals/EditUserModal.jsx
View file @
352801cd
...
@@ -72,6 +72,7 @@ const EditUserModal = (props) => {
...
@@ -72,6 +72,7 @@ const EditUserModal = (props) => {
password
:
''
,
password
:
''
,
github_id
:
''
,
github_id
:
''
,
oidc_id
:
''
,
oidc_id
:
''
,
discord_id
:
''
,
wechat_id
:
''
,
wechat_id
:
''
,
telegram_id
:
''
,
telegram_id
:
''
,
email
:
''
,
email
:
''
,
...
@@ -332,6 +333,7 @@ const EditUserModal = (props) => {
...
@@ -332,6 +333,7 @@ const EditUserModal = (props) => {
<
Row
gutter=
{
12
}
>
<
Row
gutter=
{
12
}
>
{
[
{
[
'github_id'
,
'github_id'
,
'discord_id'
,
'oidc_id'
,
'oidc_id'
,
'wechat_id'
,
'wechat_id'
,
'email'
,
'email'
,
...
...
web/src/helpers/api.js
View file @
352801cd
...
@@ -231,6 +231,17 @@ export async function getOAuthState() {
...
@@ -231,6 +231,17 @@ export async function getOAuthState() {
}
}
}
}
export
async
function
onDiscordOAuthClicked
(
client_id
)
{
const
state
=
await
getOAuthState
();
if
(
!
state
)
return
;
const
redirect_uri
=
`
${
window
.
location
.
origin
}
/oauth/discord`
;
const
response_type
=
'code'
;
const
scope
=
'identify+openid'
;
window
.
open
(
`https://discord.com/oauth2/authorize?client_id=
${
client_id
}
&redirect_uri=
${
redirect_uri
}
&response_type=
${
response_type
}
&scope=
${
scope
}
&state=
${
state
}
`
,
);
}
export
async
function
onOIDCClicked
(
auth_url
,
client_id
,
openInNewTab
=
false
)
{
export
async
function
onOIDCClicked
(
auth_url
,
client_id
,
openInNewTab
=
false
)
{
const
state
=
await
getOAuthState
();
const
state
=
await
getOAuthState
();
if
(
!
state
)
return
;
if
(
!
state
)
return
;
...
...
web/src/i18n/locales/zh.json
View file @
352801cd
...
@@ -257,6 +257,7 @@
...
@@ -257,6 +257,7 @@
"余额充值管理"
:
"余额充值管理"
,
"余额充值管理"
:
"余额充值管理"
,
"你似乎并没有修改什么"
:
"你似乎并没有修改什么"
,
"你似乎并没有修改什么"
:
"你似乎并没有修改什么"
,
"使用 GitHub 继续"
:
"使用 GitHub 继续"
,
"使用 GitHub 继续"
:
"使用 GitHub 继续"
,
"使用 Discord 继续"
:
"使用 Discord 继续"
,
"使用 JSON 对象格式,格式为:{
\"
组名
\"
: [最多请求次数, 最多请求完成次数]}"
:
"使用 JSON 对象格式,格式为:{
\"
组名
\"
: [最多请求次数, 最多请求完成次数]}"
,
"使用 JSON 对象格式,格式为:{
\"
组名
\"
: [最多请求次数, 最多请求完成次数]}"
:
"使用 JSON 对象格式,格式为:{
\"
组名
\"
: [最多请求次数, 最多请求完成次数]}"
,
"使用 LinuxDO 继续"
:
"使用 LinuxDO 继续"
,
"使用 LinuxDO 继续"
:
"使用 LinuxDO 继续"
,
"使用 OIDC 继续"
:
"使用 OIDC 继续"
,
"使用 OIDC 继续"
:
"使用 OIDC 继续"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment