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
bdb1a2fc
authored
Feb 28, 2025
by
wzxjohn
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add oidc support
parent
b1eff818
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
583 additions
and
54 deletions
+583
-54
common/constants.go
+8
-0
controller/misc.go
+37
-34
controller/oidc.go
+239
-0
controller/option.go
+7
-0
go.mod
+5
-5
go.sum
+10
-10
model/option.go
+21
-0
model/user.go
+13
-1
router/api-router.go
+1
-0
web/src/App.js
+8
-0
web/src/components/LoginForm.js
+14
-1
web/src/components/OIDCIcon.js
+23
-0
web/src/components/PersonalSetting.js
+31
-1
web/src/components/RegisterForm.js
+14
-1
web/src/components/SystemSetting.js
+120
-1
web/src/components/utils.js
+15
-0
web/src/pages/Home/index.js
+6
-0
web/src/pages/User/EditUser.js
+11
-0
No files found.
common/constants.go
View file @
bdb1a2fc
...
@@ -43,6 +43,7 @@ var PasswordLoginEnabled = true
...
@@ -43,6 +43,7 @@ var PasswordLoginEnabled = true
var
PasswordRegisterEnabled
=
true
var
PasswordRegisterEnabled
=
true
var
EmailVerificationEnabled
=
false
var
EmailVerificationEnabled
=
false
var
GitHubOAuthEnabled
=
false
var
GitHubOAuthEnabled
=
false
var
OIDCEnabled
=
false
var
LinuxDOOAuthEnabled
=
false
var
LinuxDOOAuthEnabled
=
false
var
WeChatAuthEnabled
=
false
var
WeChatAuthEnabled
=
false
var
TelegramOAuthEnabled
=
false
var
TelegramOAuthEnabled
=
false
...
@@ -78,6 +79,13 @@ var SMTPToken = ""
...
@@ -78,6 +79,13 @@ var SMTPToken = ""
var
GitHubClientId
=
""
var
GitHubClientId
=
""
var
GitHubClientSecret
=
""
var
GitHubClientSecret
=
""
var
OIDCClientId
=
""
var
OIDCClientSecret
=
""
var
OIDCWellKnown
=
""
var
OIDCAuthorizationEndpoint
=
""
var
OIDCTokenEndpoint
=
""
var
OIDCUserInfoEndpoint
=
""
var
LinuxDOClientId
=
""
var
LinuxDOClientId
=
""
var
LinuxDOClientSecret
=
""
var
LinuxDOClientSecret
=
""
...
...
controller/misc.go
View file @
bdb1a2fc
...
@@ -34,40 +34,43 @@ func GetStatus(c *gin.Context) {
...
@@ -34,40 +34,43 @@ func GetStatus(c *gin.Context) {
"success"
:
true
,
"success"
:
true
,
"message"
:
""
,
"message"
:
""
,
"data"
:
gin
.
H
{
"data"
:
gin
.
H
{
"version"
:
common
.
Version
,
"version"
:
common
.
Version
,
"start_time"
:
common
.
StartTime
,
"start_time"
:
common
.
StartTime
,
"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_oauth"
:
common
.
LinuxDOOAuthEnabled
,
"linuxdo_client_id"
:
common
.
LinuxDOClientId
,
"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
,
"logo"
:
common
.
Logo
,
"logo"
:
common
.
Logo
,
"footer_html"
:
common
.
Footer
,
"footer_html"
:
common
.
Footer
,
"wechat_qrcode"
:
common
.
WeChatAccountQRCodeImageURL
,
"wechat_qrcode"
:
common
.
WeChatAccountQRCodeImageURL
,
"wechat_login"
:
common
.
WeChatAuthEnabled
,
"wechat_login"
:
common
.
WeChatAuthEnabled
,
"server_address"
:
setting
.
ServerAddress
,
"server_address"
:
setting
.
ServerAddress
,
"price"
:
setting
.
Price
,
"price"
:
setting
.
Price
,
"min_topup"
:
setting
.
MinTopUp
,
"min_topup"
:
setting
.
MinTopUp
,
"turnstile_check"
:
common
.
TurnstileCheckEnabled
,
"turnstile_check"
:
common
.
TurnstileCheckEnabled
,
"turnstile_site_key"
:
common
.
TurnstileSiteKey
,
"turnstile_site_key"
:
common
.
TurnstileSiteKey
,
"top_up_link"
:
common
.
TopUpLink
,
"top_up_link"
:
common
.
TopUpLink
,
"docs_link"
:
operation_setting
.
GetGeneralSetting
()
.
DocsLink
,
"docs_link"
:
operation_setting
.
GetGeneralSetting
()
.
DocsLink
,
"quota_per_unit"
:
common
.
QuotaPerUnit
,
"quota_per_unit"
:
common
.
QuotaPerUnit
,
"display_in_currency"
:
common
.
DisplayInCurrencyEnabled
,
"display_in_currency"
:
common
.
DisplayInCurrencyEnabled
,
"enable_batch_update"
:
common
.
BatchUpdateEnabled
,
"enable_batch_update"
:
common
.
BatchUpdateEnabled
,
"enable_drawing"
:
common
.
DrawingEnabled
,
"enable_drawing"
:
common
.
DrawingEnabled
,
"enable_task"
:
common
.
TaskEnabled
,
"enable_task"
:
common
.
TaskEnabled
,
"enable_data_export"
:
common
.
DataExportEnabled
,
"enable_data_export"
:
common
.
DataExportEnabled
,
"data_export_default_time"
:
common
.
DataExportDefaultTime
,
"data_export_default_time"
:
common
.
DataExportDefaultTime
,
"default_collapse_sidebar"
:
common
.
DefaultCollapseSidebar
,
"default_collapse_sidebar"
:
common
.
DefaultCollapseSidebar
,
"enable_online_topup"
:
setting
.
PayAddress
!=
""
&&
setting
.
EpayId
!=
""
&&
setting
.
EpayKey
!=
""
,
"enable_online_topup"
:
setting
.
PayAddress
!=
""
&&
setting
.
EpayId
!=
""
&&
setting
.
EpayKey
!=
""
,
"mj_notify_enabled"
:
setting
.
MjNotifyEnabled
,
"mj_notify_enabled"
:
setting
.
MjNotifyEnabled
,
"chats"
:
setting
.
Chats
,
"chats"
:
setting
.
Chats
,
"demo_site_enabled"
:
operation_setting
.
DemoSiteEnabled
,
"demo_site_enabled"
:
operation_setting
.
DemoSiteEnabled
,
"self_use_mode_enabled"
:
operation_setting
.
SelfUseModeEnabled
,
"self_use_mode_enabled"
:
operation_setting
.
SelfUseModeEnabled
,
"oidc"
:
common
.
OIDCEnabled
,
"oidc_client_id"
:
common
.
OIDCClientId
,
"oidc_authorization_endpoint"
:
common
.
OIDCAuthorizationEndpoint
,
},
},
})
})
return
return
...
...
controller/oidc.go
0 → 100644
View file @
bdb1a2fc
package
controller
import
(
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"one-api/common"
"one-api/model"
"one-api/setting"
"strconv"
"strings"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
type
OidcResponse
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
OidcUser
struct
{
OpenID
string
`json:"sub"`
Email
string
`json:"email"`
Name
string
`json:"name"`
PreferredUsername
string
`json:"preferred_username"`
Picture
string
`json:"picture"`
}
func
getOidcUserInfoByCode
(
code
string
)
(
*
OidcUser
,
error
)
{
if
code
==
""
{
return
nil
,
errors
.
New
(
"无效的参数"
)
}
values
:=
url
.
Values
{}
values
.
Set
(
"client_id"
,
common
.
OIDCClientId
)
values
.
Set
(
"client_secret"
,
common
.
OIDCClientSecret
)
values
.
Set
(
"code"
,
code
)
values
.
Set
(
"grant_type"
,
"authorization_code"
)
values
.
Set
(
"redirect_uri"
,
fmt
.
Sprintf
(
"%s/oauth/oidc"
,
setting
.
ServerAddress
))
formData
:=
values
.
Encode
()
req
,
err
:=
http
.
NewRequest
(
"POST"
,
common
.
OIDCTokenEndpoint
,
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
(
"无法连接至 OIDC 服务器,请稍后重试!"
)
}
defer
res
.
Body
.
Close
()
var
oidcResponse
OidcResponse
err
=
json
.
NewDecoder
(
res
.
Body
)
.
Decode
(
&
oidcResponse
)
if
err
!=
nil
{
return
nil
,
err
}
if
oidcResponse
.
AccessToken
==
""
{
common
.
SysError
(
"OIDC 获取 Token 失败,请检查设置!"
)
return
nil
,
errors
.
New
(
"OIDC 获取 Token 失败,请检查设置!"
)
}
req
,
err
=
http
.
NewRequest
(
"GET"
,
common
.
OIDCUserInfoEndpoint
,
nil
)
if
err
!=
nil
{
return
nil
,
err
}
req
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
oidcResponse
.
AccessToken
)
res2
,
err
:=
client
.
Do
(
req
)
if
err
!=
nil
{
common
.
SysLog
(
err
.
Error
())
return
nil
,
errors
.
New
(
"无法连接至 OIDC 服务器,请稍后重试!"
)
}
defer
res2
.
Body
.
Close
()
if
res2
.
StatusCode
!=
http
.
StatusOK
{
common
.
SysError
(
"OIDC 获取用户信息失败!请检查设置!"
)
return
nil
,
errors
.
New
(
"OIDC 获取用户信息失败!请检查设置!"
)
}
var
oidcUser
OidcUser
err
=
json
.
NewDecoder
(
res2
.
Body
)
.
Decode
(
&
oidcUser
)
if
err
!=
nil
{
return
nil
,
err
}
if
oidcUser
.
OpenID
==
""
||
oidcUser
.
Email
==
""
{
common
.
SysError
(
"OIDC 获取用户信息为空!请检查设置!"
)
return
nil
,
errors
.
New
(
"OIDC 获取用户信息为空!请检查设置!"
)
}
return
&
oidcUser
,
nil
}
func
OidcAuth
(
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
{
OidcBind
(
c
)
return
}
if
!
common
.
OIDCEnabled
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"管理员未开启通过 OIDC 登录以及注册"
,
})
return
}
code
:=
c
.
Query
(
"code"
)
oidcUser
,
err
:=
getOidcUserInfoByCode
(
code
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
err
.
Error
(),
})
return
}
user
:=
model
.
User
{
OidcId
:
oidcUser
.
OpenID
,
}
if
model
.
IsOidcIdAlreadyTaken
(
user
.
OidcId
)
{
err
:=
user
.
FillUserByOidcId
()
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
err
.
Error
(),
})
return
}
}
else
{
if
common
.
RegisterEnabled
{
user
.
Email
=
oidcUser
.
Email
if
oidcUser
.
PreferredUsername
!=
""
{
user
.
Username
=
oidcUser
.
PreferredUsername
}
else
{
user
.
Username
=
"oidc_"
+
strconv
.
Itoa
(
model
.
GetMaxUserId
()
+
1
)
}
if
oidcUser
.
Name
!=
""
{
user
.
DisplayName
=
oidcUser
.
Name
}
else
{
user
.
DisplayName
=
"OIDC 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
OidcBind
(
c
*
gin
.
Context
)
{
if
!
common
.
OIDCEnabled
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"管理员未开启通过 OIDC 登录以及注册"
,
})
return
}
code
:=
c
.
Query
(
"code"
)
oidcUser
,
err
:=
getOidcUserInfoByCode
(
code
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
err
.
Error
(),
})
return
}
user
:=
model
.
User
{
OidcId
:
oidcUser
.
OpenID
,
}
if
model
.
IsOidcIdAlreadyTaken
(
user
.
OidcId
)
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"该 OIDC 账户已被绑定"
,
})
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
.
OidcId
=
oidcUser
.
OpenID
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
}
controller/option.go
View file @
bdb1a2fc
...
@@ -51,6 +51,13 @@ func UpdateOption(c *gin.Context) {
...
@@ -51,6 +51,13 @@ func UpdateOption(c *gin.Context) {
})
})
return
return
}
}
case
"OIDCEnabled"
:
if
option
.
Value
==
"true"
&&
common
.
OIDCClientId
==
""
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"无法启用 OIDC 登录,请先填入 OIDC Client Id 以及 OIDC Client Secret!"
,
})
}
case
"LinuxDOOAuthEnabled"
:
case
"LinuxDOOAuthEnabled"
:
if
option
.
Value
==
"true"
&&
common
.
LinuxDOClientId
==
""
{
if
option
.
Value
==
"true"
&&
common
.
LinuxDOClientId
==
""
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
...
...
go.mod
View file @
bdb1a2fc
...
@@ -28,9 +28,9 @@ require (
...
@@ -28,9 +28,9 @@ require (
github.com/samber/lo v1.39.0
github.com/samber/lo v1.39.0
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shopspring/decimal v1.4.0
github.com/shopspring/decimal v1.4.0
golang.org/x/crypto v0.
27
.0
golang.org/x/crypto v0.
35
.0
golang.org/x/image v0.23.0
golang.org/x/image v0.23.0
golang.org/x/net v0.
28
.0
golang.org/x/net v0.
35
.0
gorm.io/driver/mysql v1.4.3
gorm.io/driver/mysql v1.4.3
gorm.io/driver/postgres v1.5.2
gorm.io/driver/postgres v1.5.2
gorm.io/gorm v1.25.2
gorm.io/gorm v1.25.2
...
@@ -84,9 +84,9 @@ require (
...
@@ -84,9 +84,9 @@ require (
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
golang.org/x/sync v0.1
0
.0 // indirect
golang.org/x/sync v0.1
1
.0 // indirect
golang.org/x/sys v0.
27
.0 // indirect
golang.org/x/sys v0.
30
.0 // indirect
golang.org/x/text v0.2
1
.0 // indirect
golang.org/x/text v0.2
2
.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/libc v1.22.5 // indirect
...
...
go.sum
View file @
bdb1a2fc
...
@@ -217,18 +217,18 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu
...
@@ -217,18 +217,18 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.
27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A
=
golang.org/x/crypto v0.
35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs
=
golang.org/x/crypto v0.
27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70
=
golang.org/x/crypto v0.
35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ
=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.
28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE
=
golang.org/x/net v0.
35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8
=
golang.org/x/net v0.
28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg
=
golang.org/x/net v0.
35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk
=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1
0.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ
=
golang.org/x/sync v0.1
1.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w
=
golang.org/x/sync v0.1
0
.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.1
1
.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
...
@@ -239,14 +239,14 @@ golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBc
...
@@ -239,14 +239,14 @@ golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.
27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s
=
golang.org/x/sys v0.
30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc
=
golang.org/x/sys v0.
27
.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.
30
.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.2
1.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo
=
golang.org/x/text v0.2
2.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM
=
golang.org/x/text v0.2
1.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ
=
golang.org/x/text v0.2
2.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY
=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
...
...
model/option.go
View file @
bdb1a2fc
...
@@ -35,6 +35,7 @@ func InitOptionMap() {
...
@@ -35,6 +35,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
[
"OIDCEnabled"
]
=
strconv
.
FormatBool
(
common
.
OIDCEnabled
)
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
)
...
@@ -77,6 +78,12 @@ func InitOptionMap() {
...
@@ -77,6 +78,12 @@ func InitOptionMap() {
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"GitHubClientId"
]
=
""
common
.
OptionMap
[
"GitHubClientId"
]
=
""
common
.
OptionMap
[
"GitHubClientSecret"
]
=
""
common
.
OptionMap
[
"GitHubClientSecret"
]
=
""
common
.
OptionMap
[
"OIDCClientId"
]
=
""
common
.
OptionMap
[
"OIDCClientSecret"
]
=
""
common
.
OptionMap
[
"OIDCWellKnown"
]
=
""
common
.
OptionMap
[
"OIDCAuthorizationEndpoint"
]
=
""
common
.
OptionMap
[
"OIDCTokenEndpoint"
]
=
""
common
.
OptionMap
[
"OIDCUserInfoEndpoint"
]
=
""
common
.
OptionMap
[
"TelegramBotToken"
]
=
""
common
.
OptionMap
[
"TelegramBotToken"
]
=
""
common
.
OptionMap
[
"TelegramBotName"
]
=
""
common
.
OptionMap
[
"TelegramBotName"
]
=
""
common
.
OptionMap
[
"WeChatServerAddress"
]
=
""
common
.
OptionMap
[
"WeChatServerAddress"
]
=
""
...
@@ -200,6 +207,8 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -200,6 +207,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
"OIDCEnabled"
:
common
.
OIDCEnabled
=
boolValue
case
"LinuxDOOAuthEnabled"
:
case
"LinuxDOOAuthEnabled"
:
common
.
LinuxDOOAuthEnabled
=
boolValue
common
.
LinuxDOOAuthEnabled
=
boolValue
case
"WeChatAuthEnabled"
:
case
"WeChatAuthEnabled"
:
...
@@ -298,6 +307,18 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -298,6 +307,18 @@ 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
"OIDCClientId"
:
common
.
OIDCClientId
=
value
case
"OIDCClientSecret"
:
common
.
OIDCClientSecret
=
value
case
"OIDCWellKnown"
:
common
.
OIDCWellKnown
=
value
case
"OIDCAuthorizationEndpoint"
:
common
.
OIDCAuthorizationEndpoint
=
value
case
"OIDCTokenEndpoint"
:
common
.
OIDCTokenEndpoint
=
value
case
"OIDCUserInfoEndpoint"
:
common
.
OIDCUserInfoEndpoint
=
value
case
"LinuxDOClientId"
:
case
"LinuxDOClientId"
:
common
.
LinuxDOClientId
=
value
common
.
LinuxDOClientId
=
value
case
"LinuxDOClientSecret"
:
case
"LinuxDOClientSecret"
:
...
...
model/user.go
View file @
bdb1a2fc
...
@@ -9,7 +9,6 @@ import (
...
@@ -9,7 +9,6 @@ import (
"strings"
"strings"
"github.com/bytedance/gopkg/util/gopool"
"github.com/bytedance/gopkg/util/gopool"
"gorm.io/gorm"
"gorm.io/gorm"
)
)
...
@@ -24,6 +23,7 @@ type User struct {
...
@@ -24,6 +23,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"`
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"`
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!
...
@@ -442,6 +442,14 @@ func (user *User) FillUserByGitHubId() error {
...
@@ -442,6 +442,14 @@ func (user *User) FillUserByGitHubId() error {
return
nil
return
nil
}
}
func
(
user
*
User
)
FillUserByOidcId
()
error
{
if
user
.
OidcId
==
""
{
return
errors
.
New
(
"oidc id 为空!"
)
}
DB
.
Where
(
User
{
OidcId
:
user
.
OidcId
})
.
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 为空!"
)
...
@@ -473,6 +481,10 @@ func IsGitHubIdAlreadyTaken(githubId string) bool {
...
@@ -473,6 +481,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
IsOidcIdAlreadyTaken
(
oidcId
string
)
bool
{
return
DB
.
Where
(
"oidc_id = ?"
,
oidcId
)
.
Find
(
&
User
{})
.
RowsAffected
==
1
}
func
IsTelegramIdAlreadyTaken
(
telegramId
string
)
bool
{
func
IsTelegramIdAlreadyTaken
(
telegramId
string
)
bool
{
return
DB
.
Unscoped
()
.
Where
(
"telegram_id = ?"
,
telegramId
)
.
Find
(
&
User
{})
.
RowsAffected
==
1
return
DB
.
Unscoped
()
.
Where
(
"telegram_id = ?"
,
telegramId
)
.
Find
(
&
User
{})
.
RowsAffected
==
1
}
}
...
...
router/api-router.go
View file @
bdb1a2fc
...
@@ -25,6 +25,7 @@ func SetApiRouter(router *gin.Engine) {
...
@@ -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/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
)
apiRouter
.
GET
(
"/oauth/wechat"
,
middleware
.
CriticalRateLimit
(),
controller
.
WeChatAuth
)
apiRouter
.
GET
(
"/oauth/wechat"
,
middleware
.
CriticalRateLimit
(),
controller
.
WeChatAuth
)
...
...
web/src/App.js
View file @
bdb1a2fc
...
@@ -161,6 +161,14 @@ function App() {
...
@@ -161,6 +161,14 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
'/oauth/oidc'
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
OAuth2Callback
type
=
'oidc'
><
/OAuth2Callback
>
<
/Suspense
>
}
/
>
<
Route
path
=
'/oauth/linuxdo'
path
=
'/oauth/linuxdo'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
...
...
web/src/components/LoginForm.js
View file @
bdb1a2fc
...
@@ -9,7 +9,7 @@ import {
...
@@ -9,7 +9,7 @@ import {
showSuccess
,
showSuccess
,
updateAPI
,
updateAPI
,
}
from
'../helpers'
;
}
from
'../helpers'
;
import
{
onGitHubOAuthClicked
,
onLinuxDOOAuthClicked
}
from
'./utils'
;
import
{
onGitHubOAuthClicked
,
onOIDCClicked
,
onLinuxDOOAuthClicked
}
from
'./utils'
;
import
Turnstile
from
'react-turnstile'
;
import
Turnstile
from
'react-turnstile'
;
import
{
import
{
Button
,
Button
,
...
@@ -25,6 +25,7 @@ import Text from '@douyinfe/semi-ui/lib/es/typography/text';
...
@@ -25,6 +25,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
,
IconAlarm
}
from
'@douyinfe/semi-icons'
;
import
{
IconGithubLogo
,
IconAlarm
}
from
'@douyinfe/semi-icons'
;
import
OIDCIcon
from
'./OIDCIcon.js'
;
import
WeChatIcon
from
'./WeChatIcon'
;
import
WeChatIcon
from
'./WeChatIcon'
;
import
{
setUserData
}
from
'../helpers/data.js'
;
import
{
setUserData
}
from
'../helpers/data.js'
;
import
LinuxDoIcon
from
'./LinuxDoIcon.js'
;
import
LinuxDoIcon
from
'./LinuxDoIcon.js'
;
...
@@ -229,6 +230,7 @@ const LoginForm = () => {
...
@@ -229,6 +230,7 @@ const LoginForm = () => {
<
/Text
>
<
/Text
>
<
/div
>
<
/div
>
{
status
.
github_oauth
||
{
status
.
github_oauth
||
status
.
oidc
||
status
.
wechat_login
||
status
.
wechat_login
||
status
.
telegram_oauth
||
status
.
telegram_oauth
||
status
.
linuxdo_oauth
?
(
status
.
linuxdo_oauth
?
(
...
@@ -254,6 +256,17 @@ const LoginForm = () => {
...
@@ -254,6 +256,17 @@ const LoginForm = () => {
)
:
(
)
:
(
<><
/
>
<><
/
>
)}
)}
{
status
.
oidc
?
(
<
Button
type
=
'primary'
icon
=
{
<
OIDCIcon
/>
}
onClick
=
{()
=>
onOIDCClicked
(
status
.
oidc_authorization_endpoint
,
status
.
oidc_client_id
)
}
/
>
)
:
(
<><
/
>
)}
{
status
.
linuxdo_oauth
?
(
{
status
.
linuxdo_oauth
?
(
<
Button
<
Button
icon
=
{
<
LinuxDoIcon
/>
}
icon
=
{
<
LinuxDoIcon
/>
}
...
...
web/src/components/OIDCIcon.js
0 → 100644
View file @
bdb1a2fc
import
React
from
'react'
;
import
{
Icon
}
from
'@douyinfe/semi-ui'
;
const
OIDCIcon
=
(
props
)
=>
{
function
CustomIcon
()
{
return
(
<
svg
t
=
"1723135116886"
className
=
"icon"
viewBox
=
"0 0 1024 1024"
version
=
"1.1"
xmlns
=
"http://www.w3.org/2000/svg"
p
-
id
=
"10969"
width
=
"1em"
height
=
"1em"
>
<
path
d
=
"M512 960C265 960 64 759 64 512S265 64 512 64s448 201 448 448-201 448-448 448z m0-882.6c-239.7 0-434.6 195-434.6 434.6s195 434.6 434.6 434.6 434.6-195 434.6-434.6S751.7 77.4 512 77.4z"
p
-
id
=
"10970"
fill
=
"#2c2c2c"
stroke
=
"#2c2c2c"
stroke
-
width
=
"60"
><
/path
>
<
path
d
=
"M197.7 512c0-78.3 31.6-98.8 87.2-98.8 56.2 0 87.2 20.5 87.2 98.8s-31 98.8-87.2 98.8c-55.7 0-87.2-20.5-87.2-98.8z m130.4 0c0-46.8-7.8-64.5-43.2-64.5-35.2 0-42.9 17.7-42.9 64.5 0 47.1 7.8 63.7 42.9 63.7 35.4 0 43.2-16.6 43.2-63.7zM409.7 415.9h42.1V608h-42.1V415.9zM653.9 512c0 74.2-37.1 96.1-93.6 96.1h-65.9V415.9h65.9c56.5 0 93.6 16.1 93.6 96.1z m-43.5 0c0-49.3-17.7-60.6-52.3-60.6h-21.6v120.7h21.6c35.4 0 52.3-13.3 52.3-60.1zM686.5 512c0-74.2 36.3-98.8 92.7-98.8 18.3 0 33.2 2.2 44.8 6.4v36.3c-11.9-4.2-26-6.6-42.1-6.6-34.6 0-49.8 15.5-49.8 62.6 0 50.1 15.2 62.6 49.3 62.6 15.8 0 30.2-2.2 44.8-7.5v36c-11.3 4.7-28.5 8-46.8 8-56.1-0.2-92.9-18.7-92.9-99z"
p
-
id
=
"10971"
fill
=
"#2c2c2c"
stroke
=
"#2c2c2c"
stroke
-
width
=
"20"
><
/path
>
<
/svg
>
);
}
return
<
Icon
svg
=
{
<
CustomIcon
/>
}
/>
;
};
export
default
OIDCIcon
;
\ No newline at end of file
web/src/components/PersonalSetting.js
View file @
bdb1a2fc
...
@@ -10,7 +10,7 @@ import {
...
@@ -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
,
onLinuxDOOAuthClicked
}
from
'./utils'
;
import
{
onGitHubOAuthClicked
,
on
OIDCClicked
,
on
LinuxDOOAuthClicked
}
from
'./utils'
;
import
{
import
{
Avatar
,
Avatar
,
Banner
,
Banner
,
...
@@ -641,6 +641,36 @@ const PersonalSetting = () => {
...
@@ -641,6 +641,36 @@ const PersonalSetting = () => {
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
{
t
(
'OIDC'
)}
<
/Typography.Text
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'space-between'
}}
>
<
div
>
<
Input
value
=
{
userState
.
user
&&
userState
.
user
.
oidc_id
!==
''
?
userState
.
user
.
oidc_id
:
t
(
'未绑定'
)
}
readonly
=
{
true
}
><
/Input
>
<
/div
>
<
div
>
<
Button
onClick
=
{()
=>
{
onOIDCClicked
(
status
.
oidc_authorization_endpoint
,
status
.
oidc_client_id
);
}}
disabled
=
{
(
userState
.
user
&&
userState
.
user
.
oidc_id
!==
''
)
||
!
status
.
oidc
}
>
{
status
.
oidc
?
t
(
'绑定'
)
:
t
(
'未启用'
)}
<
/Button
>
<
/div
>
<
/div
>
<
/div
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
{
t
(
'Telegram'
)}
<
/Typography.Text
>
<
Typography
.
Text
strong
>
{
t
(
'Telegram'
)}
<
/Typography.Text
>
<
div
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'space-between'
}}
style
=
{{
display
:
'flex'
,
justifyContent
:
'space-between'
}}
...
...
web/src/components/RegisterForm.js
View file @
bdb1a2fc
...
@@ -6,7 +6,8 @@ import { Button, Card, Divider, Form, Icon, Layout, Modal } from '@douyinfe/semi
...
@@ -6,7 +6,8 @@ import { Button, Card, Divider, Form, Icon, Layout, Modal } from '@douyinfe/semi
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
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
{
IconGithubLogo
}
from
'@douyinfe/semi-icons'
;
import
{
IconGithubLogo
}
from
'@douyinfe/semi-icons'
;
import
{
onGitHubOAuthClicked
,
onLinuxDOOAuthClicked
}
from
'./utils.js'
;
import
{
onGitHubOAuthClicked
,
onLinuxDOOAuthClicked
,
onOIDCClicked
}
from
'./utils.js'
;
import
OIDCIcon
from
"./OIDCIcon.js"
;
import
LinuxDoIcon
from
'./LinuxDoIcon.js'
;
import
LinuxDoIcon
from
'./LinuxDoIcon.js'
;
import
WeChatIcon
from
'./WeChatIcon.js'
;
import
WeChatIcon
from
'./WeChatIcon.js'
;
import
TelegramLoginButton
from
'react-telegram-login/src'
;
import
TelegramLoginButton
from
'react-telegram-login/src'
;
...
@@ -262,6 +263,7 @@ const RegisterForm = () => {
...
@@ -262,6 +263,7 @@ const RegisterForm = () => {
<
/Text
>
<
/Text
>
<
/div
>
<
/div
>
{
status
.
github_oauth
||
{
status
.
github_oauth
||
status
.
oidc
||
status
.
wechat_login
||
status
.
wechat_login
||
status
.
telegram_oauth
||
status
.
telegram_oauth
||
status
.
linuxdo_oauth
?
(
status
.
linuxdo_oauth
?
(
...
@@ -287,6 +289,17 @@ const RegisterForm = () => {
...
@@ -287,6 +289,17 @@ const RegisterForm = () => {
)
:
(
)
:
(
<><
/
>
<><
/
>
)}
)}
{
status
.
oidc
?
(
<
Button
type
=
'primary'
icon
=
{
<
OIDCIcon
/>
}
onClick
=
{()
=>
onOIDCClicked
(
status
.
oidc_authorization_endpoint
,
status
.
oidc_client_id
)
}
/
>
)
:
(
<><
/
>
)}
{
status
.
linuxdo_oauth
?
(
{
status
.
linuxdo_oauth
?
(
<
Button
<
Button
icon
=
{
<
LinuxDoIcon
/>
}
icon
=
{
<
LinuxDoIcon
/>
}
...
...
web/src/components/SystemSetting.js
View file @
bdb1a2fc
...
@@ -20,6 +20,13 @@ const SystemSetting = () => {
...
@@ -20,6 +20,13 @@ const SystemSetting = () => {
GitHubOAuthEnabled
:
''
,
GitHubOAuthEnabled
:
''
,
GitHubClientId
:
''
,
GitHubClientId
:
''
,
GitHubClientSecret
:
''
,
GitHubClientSecret
:
''
,
OIDCEnabled
:
''
,
OIDCClientId
:
''
,
OIDCClientSecret
:
''
,
OIDCWellKnown
:
''
,
OIDCAuthorizationEndpoint
:
''
,
OIDCTokenEndpoint
:
''
,
OIDCUserInfoEndpoint
:
''
,
Notice
:
''
,
Notice
:
''
,
SMTPServer
:
''
,
SMTPServer
:
''
,
SMTPPort
:
''
,
SMTPPort
:
''
,
...
@@ -106,6 +113,7 @@ const SystemSetting = () => {
...
@@ -106,6 +113,7 @@ const SystemSetting = () => {
case
'PasswordRegisterEnabled'
:
case
'PasswordRegisterEnabled'
:
case
'EmailVerificationEnabled'
:
case
'EmailVerificationEnabled'
:
case
'GitHubOAuthEnabled'
:
case
'GitHubOAuthEnabled'
:
case
'OIDCEnabled'
:
case
'LinuxDOOAuthEnabled'
:
case
'LinuxDOOAuthEnabled'
:
case
'WeChatAuthEnabled'
:
case
'WeChatAuthEnabled'
:
case
'TelegramOAuthEnabled'
:
case
'TelegramOAuthEnabled'
:
...
@@ -159,6 +167,12 @@ const SystemSetting = () => {
...
@@ -159,6 +167,12 @@ const SystemSetting = () => {
name
===
'PayAddress'
||
name
===
'PayAddress'
||
name
===
'GitHubClientId'
||
name
===
'GitHubClientId'
||
name
===
'GitHubClientSecret'
||
name
===
'GitHubClientSecret'
||
name
===
'OIDCWellKnown'
||
name
===
'OIDCClientId'
||
name
===
'OIDCClientSecret'
||
name
===
'OIDCAuthorizationEndpoint'
||
name
===
'OIDCTokenEndpoint'
||
name
===
'OIDCUserInfoEndpoint'
||
name
===
'WeChatServerAddress'
||
name
===
'WeChatServerAddress'
||
name
===
'WeChatServerToken'
||
name
===
'WeChatServerToken'
||
name
===
'WeChatAccountQRCodeImageURL'
||
name
===
'WeChatAccountQRCodeImageURL'
||
...
@@ -286,6 +300,43 @@ const SystemSetting = () => {
...
@@ -286,6 +300,43 @@ const SystemSetting = () => {
}
}
};
};
const
submitOIDCSettings
=
async
()
=>
{
if
(
inputs
.
OIDCWellKnown
!==
''
)
{
if
(
!
inputs
.
OIDCWellKnown
.
startsWith
(
'http://'
)
&&
!
inputs
.
OIDCWellKnown
.
startsWith
(
'https://'
))
{
showError
(
'Well-Known URL 必须以 http:// 或 https:// 开头'
);
return
;
}
try
{
const
res
=
await
API
.
get
(
inputs
.
OIDCWellKnown
);
inputs
.
OIDCAuthorizationEndpoint
=
res
.
data
[
'authorization_endpoint'
];
inputs
.
OIDCTokenEndpoint
=
res
.
data
[
'token_endpoint'
];
inputs
.
OIDCUserInfoEndpoint
=
res
.
data
[
'userinfo_endpoint'
];
showSuccess
(
'获取 OIDC 配置成功!'
);
}
catch
(
err
)
{
showError
(
"获取 OIDC 配置失败,请检查网络状况和 Well-Known URL 是否正确"
);
}
}
if
(
originInputs
[
'OIDCWellKnown'
]
!==
inputs
.
OIDCWellKnown
)
{
await
updateOption
(
'OIDCWellKnown'
,
inputs
.
OIDCWellKnown
);
}
if
(
originInputs
[
'OIDCClientId'
]
!==
inputs
.
OIDCClientId
)
{
await
updateOption
(
'OIDCClientId'
,
inputs
.
OIDCClientId
);
}
if
(
originInputs
[
'OIDCClientSecret'
]
!==
inputs
.
OIDCClientSecret
&&
inputs
.
OIDCClientSecret
!==
''
)
{
await
updateOption
(
'OIDCClientSecret'
,
inputs
.
OIDCClientSecret
);
}
if
(
originInputs
[
'OIDCAuthorizationEndpoint'
]
!==
inputs
.
OIDCAuthorizationEndpoint
)
{
await
updateOption
(
'OIDCAuthorizationEndpoint'
,
inputs
.
OIDCAuthorizationEndpoint
);
}
if
(
originInputs
[
'OIDCTokenEndpoint'
]
!==
inputs
.
OIDCTokenEndpoint
)
{
await
updateOption
(
'OIDCTokenEndpoint'
,
inputs
.
OIDCTokenEndpoint
);
}
if
(
originInputs
[
'OIDCUserInfoEndpoint'
]
!==
inputs
.
OIDCUserInfoEndpoint
)
{
await
updateOption
(
'OIDCUserInfoEndpoint'
,
inputs
.
OIDCUserInfoEndpoint
);
}
}
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
);
...
@@ -370,7 +421,7 @@ const SystemSetting = () => {
...
@@ -370,7 +421,7 @@ const SystemSetting = () => {
<
/Header
>
<
/Header
>
<
Message
info
>
<
Message
info
>
注意:代理功能仅对图片请求和
Webhook
请求生效,不会影响其他
API
请求。如需配置
API
请求代理,请参考
注意:代理功能仅对图片请求和
Webhook
请求生效,不会影响其他
API
请求。如需配置
API
请求代理,请参考
<
a
<
a
href
=
'https://github.com/Calcium-Ion/new-api/blob/main/docs/channel/other_setting.md'
href
=
'https://github.com/Calcium-Ion/new-api/blob/main/docs/channel/other_setting.md'
target
=
'_blank'
target
=
'_blank'
rel
=
'noreferrer'
rel
=
'noreferrer'
...
@@ -519,6 +570,12 @@ const SystemSetting = () => {
...
@@ -519,6 +570,12 @@ const SystemSetting = () => {
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
OIDCEnabled
===
'true'
}
label
=
'允许通过 OIDC 登录 & 注册'
name
=
'OIDCEnabled'
onChange
=
{
handleInputChange
}
/
>
<
Form
.
Checkbox
checked
=
{
inputs
.
LinuxDOOAuthEnabled
===
'true'
}
checked
=
{
inputs
.
LinuxDOOAuthEnabled
===
'true'
}
label
=
'允许通过 LinuxDO 账户登录 & 注册'
label
=
'允许通过 LinuxDO 账户登录 & 注册'
name
=
'LinuxDOOAuthEnabled'
name
=
'LinuxDOOAuthEnabled'
...
@@ -864,6 +921,68 @@ const SystemSetting = () => {
...
@@ -864,6 +921,68 @@ const SystemSetting = () => {
<
Form
.
Button
onClick
=
{
submitLinuxDOOAuth
}
>
<
Form
.
Button
onClick
=
{
submitLinuxDOOAuth
}
>
保存
LinuxDO
OAuth
设置
保存
LinuxDO
OAuth
设置
<
/Form.Button
>
<
/Form.Button
>
<
Divider
/>
<
Header
as
=
'h3'
inverted
=
{
isDark
}
>
配置
OIDC
<
Header
.
Subheader
>
用以支持通过
OIDC
登录,例如
Okta
、
Auth0
等兼容
OIDC
协议的
IdP
<
/Header.Subheader
>
<
/Header
>
<
Message
>
主页链接填
<
code
>
{
inputs
.
ServerAddress
}
<
/code>
,
重定向
URL
填
<
code
>
{
`
${
inputs
.
ServerAddress
}
/oauth/oidc`
}
<
/code
>
<
/Message
>
<
Message
>
若你的
OIDC
Provider
支持
Discovery
Endpoint
,你可以仅填写
OIDC
Well
-
Known
URL
,系统会自动获取
OIDC
配置
<
/Message
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Input
label
=
'Client ID'
name
=
'OIDCClientId'
onChange
=
{
handleInputChange
}
value
=
{
inputs
.
OIDCClientId
}
placeholder
=
'输入 OIDC 的 Client ID'
/>
<
Form
.
Input
label
=
'Client Secret'
name
=
'OIDCClientSecret'
onChange
=
{
handleInputChange
}
type
=
'password'
value
=
{
inputs
.
OIDCClientSecret
}
placeholder
=
'敏感信息不会发送到前端显示'
/>
<
Form
.
Input
label
=
'Well-Known URL'
name
=
'OIDCWellKnown'
onChange
=
{
handleInputChange
}
value
=
{
inputs
.
OIDCWellKnown
}
placeholder
=
'请输入 OIDC 的 Well-Known URL'
/>
<
Form
.
Input
label
=
'Authorization Endpoint'
name
=
'OIDCAuthorizationEndpoint'
onChange
=
{
handleInputChange
}
value
=
{
inputs
.
OIDCAuthorizationEndpoint
}
placeholder
=
'输入 OIDC 的 Authorization Endpoint'
/>
<
Form
.
Input
label
=
'Token Endpoint'
name
=
'OIDCTokenEndpoint'
onChange
=
{
handleInputChange
}
value
=
{
inputs
.
OIDCTokenEndpoint
}
placeholder
=
'输入 OIDC 的 Token Endpoint'
/>
<
Form
.
Input
label
=
'Userinfo Endpoint'
name
=
'OIDCUserInfoEndpoint'
onChange
=
{
handleInputChange
}
value
=
{
inputs
.
OIDCUserInfoEndpoint
}
placeholder
=
'输入 OIDC 的 Userinfo Endpoint'
/>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{
submitOIDCSettings
}
>
保存
OIDC
设置
<
/Form.Button
>
<
/Form
>
<
/Form
>
<
/Grid.Column
>
<
/Grid.Column
>
<
/Grid
>
<
/Grid
>
...
...
web/src/components/utils.js
View file @
bdb1a2fc
...
@@ -16,6 +16,21 @@ export async function getOAuthState() {
...
@@ -16,6 +16,21 @@ export async function getOAuthState() {
}
}
}
}
export
async
function
onOIDCClicked
(
auth_url
,
client_id
,
openInNewTab
=
false
)
{
const
state
=
await
getOAuthState
();
if
(
!
state
)
return
;
const
redirect_uri
=
`
${
window
.
location
.
origin
}
/oauth/oidc`
;
const
response_type
=
"code"
;
const
scope
=
"openid profile email"
;
const
url
=
`
${
auth_url
}
?client_id=
${
client_id
}
&redirect_uri=
${
redirect_uri
}
&response_type=
${
response_type
}
&scope=
${
scope
}
&state=
${
state
}
`
;
if
(
openInNewTab
)
{
window
.
open
(
url
);
}
else
{
window
.
location
.
href
=
url
;
}
}
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
;
...
...
web/src/pages/Home/index.js
View file @
bdb1a2fc
...
@@ -152,6 +152,12 @@ const Home = () => {
...
@@ -152,6 +152,12 @@ const Home = () => {
:
t
(
'未启用'
)}
:
t
(
'未启用'
)}
<
/p
>
<
/p
>
<
p
>
<
p
>
{
t
(
'OIDC 身份验证'
)}
:
{
statusState
?.
status
?.
oidc
===
true
?
t
(
'已启用'
)
:
t
(
'未启用'
)}
<
/p
>
<
p
>
{
t
(
'微信身份验证'
)}
:
{
t
(
'微信身份验证'
)}
:
{
statusState
?.
status
?.
wechat_login
===
true
{
statusState
?.
status
?.
wechat_login
===
true
?
t
(
'已启用'
)
?
t
(
'已启用'
)
...
...
web/src/pages/User/EditUser.js
View file @
bdb1a2fc
...
@@ -26,6 +26,7 @@ const EditUser = (props) => {
...
@@ -26,6 +26,7 @@ const EditUser = (props) => {
display_name
:
''
,
display_name
:
''
,
password
:
''
,
password
:
''
,
github_id
:
''
,
github_id
:
''
,
oidc_id
:
''
,
wechat_id
:
''
,
wechat_id
:
''
,
email
:
''
,
email
:
''
,
quota
:
0
,
quota
:
0
,
...
@@ -37,6 +38,7 @@ const EditUser = (props) => {
...
@@ -37,6 +38,7 @@ const EditUser = (props) => {
display_name
,
display_name
,
password
,
password
,
github_id
,
github_id
,
oidc_id
,
wechat_id
,
wechat_id
,
telegram_id
,
telegram_id
,
email
,
email
,
...
@@ -233,6 +235,15 @@ const EditUser = (props) => {
...
@@ -233,6 +235,15 @@ const EditUser = (props) => {
readonly
readonly
/>
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
{
t
(
'`已绑定的 OIDC 账户'
)}
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'oidc_id'
value
=
{
oidc_id
}
placeholder
=
{
t
(
'此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
)}
readonly
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
{
t
(
'已绑定的微信账户'
)}
<
/Typography.Text
>
<
Typography
.
Text
>
{
t
(
'已绑定的微信账户'
)}
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
...
...
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