Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
b2e07121
Commit
b2e07121
authored
Apr 22, 2026
by
IanShaw027
Browse files
fix(settings): preserve oauth config compatibility on upgrade
parent
767f2f2d
Changes
8
Show whitespace changes
Inline
Side-by-side
backend/internal/config/config.go
View file @
b2e07121
...
...
@@ -70,6 +70,7 @@ type Config struct {
JWT
JWTConfig
`mapstructure:"jwt"`
Totp
TotpConfig
`mapstructure:"totp"`
LinuxDo
LinuxDoConnectConfig
`mapstructure:"linuxdo_connect"`
WeChat
WeChatConnectConfig
`mapstructure:"wechat_connect"`
OIDC
OIDCConnectConfig
`mapstructure:"oidc_connect"`
Default
DefaultConfig
`mapstructure:"default"`
RateLimit
RateLimitConfig
`mapstructure:"rate_limit"`
...
...
@@ -190,6 +191,25 @@ type LinuxDoConnectConfig struct {
UserInfoUsernamePath
string
`mapstructure:"userinfo_username_path"`
}
type
WeChatConnectConfig
struct
{
Enabled
bool
`mapstructure:"enabled"`
AppID
string
`mapstructure:"app_id"`
AppSecret
string
`mapstructure:"app_secret"`
OpenAppID
string
`mapstructure:"open_app_id"`
OpenAppSecret
string
`mapstructure:"open_app_secret"`
MPAppID
string
`mapstructure:"mp_app_id"`
MPAppSecret
string
`mapstructure:"mp_app_secret"`
MobileAppID
string
`mapstructure:"mobile_app_id"`
MobileAppSecret
string
`mapstructure:"mobile_app_secret"`
OpenEnabled
bool
`mapstructure:"open_enabled"`
MPEnabled
bool
`mapstructure:"mp_enabled"`
MobileEnabled
bool
`mapstructure:"mobile_enabled"`
Mode
string
`mapstructure:"mode"`
Scopes
string
`mapstructure:"scopes"`
RedirectURL
string
`mapstructure:"redirect_url"`
FrontendRedirectURL
string
`mapstructure:"frontend_redirect_url"`
}
type
OIDCConnectConfig
struct
{
Enabled
bool
`mapstructure:"enabled"`
ProviderName
string
`mapstructure:"provider_name"`
// 显示名: "Keycloak" 等
...
...
@@ -218,6 +238,217 @@ type OIDCConnectConfig struct {
UserInfoUsernamePath
string
`mapstructure:"userinfo_username_path"`
}
const
(
defaultWeChatConnectMode
=
"open"
defaultWeChatConnectScopes
=
"snsapi_login"
defaultWeChatConnectFrontendRedirect
=
"/auth/wechat/callback"
)
func
firstNonEmptyString
(
values
...
string
)
string
{
for
_
,
value
:=
range
values
{
if
trimmed
:=
strings
.
TrimSpace
(
value
);
trimmed
!=
""
{
return
trimmed
}
}
return
""
}
func
normalizeWeChatConnectMode
(
raw
string
)
string
{
switch
strings
.
ToLower
(
strings
.
TrimSpace
(
raw
))
{
case
"mp"
:
return
"mp"
case
"mobile"
:
return
"mobile"
default
:
return
defaultWeChatConnectMode
}
}
func
normalizeWeChatConnectStoredMode
(
openEnabled
,
mpEnabled
,
mobileEnabled
bool
,
mode
string
)
string
{
mode
=
normalizeWeChatConnectMode
(
mode
)
switch
mode
{
case
"open"
:
if
openEnabled
{
return
"open"
}
case
"mp"
:
if
mpEnabled
{
return
"mp"
}
case
"mobile"
:
if
mobileEnabled
{
return
"mobile"
}
}
switch
{
case
openEnabled
:
return
"open"
case
mpEnabled
:
return
"mp"
case
mobileEnabled
:
return
"mobile"
default
:
return
mode
}
}
func
defaultWeChatConnectScopesForMode
(
mode
string
)
string
{
switch
normalizeWeChatConnectMode
(
mode
)
{
case
"mp"
:
return
"snsapi_userinfo"
case
"mobile"
:
return
""
default
:
return
defaultWeChatConnectScopes
}
}
func
normalizeWeChatConnectScopes
(
raw
,
mode
string
)
string
{
switch
normalizeWeChatConnectMode
(
mode
)
{
case
"mp"
:
switch
strings
.
TrimSpace
(
raw
)
{
case
"snsapi_base"
:
return
"snsapi_base"
case
"snsapi_userinfo"
:
return
"snsapi_userinfo"
default
:
return
defaultWeChatConnectScopesForMode
(
mode
)
}
case
"mobile"
:
return
""
default
:
return
defaultWeChatConnectScopes
}
}
func
shouldApplyLegacyWeChatEnv
(
configKey
,
envKey
string
)
bool
{
if
viper
.
InConfig
(
configKey
)
{
return
false
}
_
,
hasNewEnv
:=
os
.
LookupEnv
(
envKey
)
return
!
hasNewEnv
}
func
applyLegacyWeChatConnectEnvCompatibility
(
cfg
*
WeChatConnectConfig
)
{
if
cfg
==
nil
{
return
}
legacyOpenAppID
:=
""
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.open_app_id"
,
"WECHAT_CONNECT_OPEN_APP_ID"
)
&&
shouldApplyLegacyWeChatEnv
(
"wechat_connect.app_id"
,
"WECHAT_CONNECT_APP_ID"
)
{
legacyOpenAppID
=
strings
.
TrimSpace
(
os
.
Getenv
(
"WECHAT_OAUTH_OPEN_APP_ID"
))
if
legacyOpenAppID
!=
""
{
cfg
.
OpenAppID
=
legacyOpenAppID
}
}
legacyOpenAppSecret
:=
""
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.open_app_secret"
,
"WECHAT_CONNECT_OPEN_APP_SECRET"
)
&&
shouldApplyLegacyWeChatEnv
(
"wechat_connect.app_secret"
,
"WECHAT_CONNECT_APP_SECRET"
)
{
legacyOpenAppSecret
=
strings
.
TrimSpace
(
os
.
Getenv
(
"WECHAT_OAUTH_OPEN_APP_SECRET"
))
if
legacyOpenAppSecret
!=
""
{
cfg
.
OpenAppSecret
=
legacyOpenAppSecret
}
}
legacyMPAppID
:=
""
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.mp_app_id"
,
"WECHAT_CONNECT_MP_APP_ID"
)
&&
shouldApplyLegacyWeChatEnv
(
"wechat_connect.app_id"
,
"WECHAT_CONNECT_APP_ID"
)
{
legacyMPAppID
=
strings
.
TrimSpace
(
os
.
Getenv
(
"WECHAT_OAUTH_MP_APP_ID"
))
if
legacyMPAppID
!=
""
{
cfg
.
MPAppID
=
legacyMPAppID
}
}
legacyMPAppSecret
:=
""
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.mp_app_secret"
,
"WECHAT_CONNECT_MP_APP_SECRET"
)
&&
shouldApplyLegacyWeChatEnv
(
"wechat_connect.app_secret"
,
"WECHAT_CONNECT_APP_SECRET"
)
{
legacyMPAppSecret
=
strings
.
TrimSpace
(
os
.
Getenv
(
"WECHAT_OAUTH_MP_APP_SECRET"
))
if
legacyMPAppSecret
!=
""
{
cfg
.
MPAppSecret
=
legacyMPAppSecret
}
}
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.frontend_redirect_url"
,
"WECHAT_CONNECT_FRONTEND_REDIRECT_URL"
)
{
if
legacyFrontend
:=
strings
.
TrimSpace
(
os
.
Getenv
(
"WECHAT_OAUTH_FRONTEND_REDIRECT_URL"
));
legacyFrontend
!=
""
{
cfg
.
FrontendRedirectURL
=
legacyFrontend
}
}
hasLegacyOpen
:=
legacyOpenAppID
!=
""
&&
legacyOpenAppSecret
!=
""
hasLegacyMP
:=
legacyMPAppID
!=
""
&&
legacyMPAppSecret
!=
""
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.enabled"
,
"WECHAT_CONNECT_ENABLED"
)
&&
(
hasLegacyOpen
||
hasLegacyMP
)
{
cfg
.
Enabled
=
true
}
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.open_enabled"
,
"WECHAT_CONNECT_OPEN_ENABLED"
)
&&
hasLegacyOpen
{
cfg
.
OpenEnabled
=
true
}
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.mp_enabled"
,
"WECHAT_CONNECT_MP_ENABLED"
)
&&
hasLegacyMP
{
cfg
.
MPEnabled
=
true
}
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.mode"
,
"WECHAT_CONNECT_MODE"
)
{
switch
{
case
hasLegacyMP
&&
!
hasLegacyOpen
:
cfg
.
Mode
=
"mp"
case
hasLegacyOpen
:
cfg
.
Mode
=
"open"
}
}
if
shouldApplyLegacyWeChatEnv
(
"wechat_connect.scopes"
,
"WECHAT_CONNECT_SCOPES"
)
{
switch
{
case
hasLegacyMP
&&
!
hasLegacyOpen
:
cfg
.
Scopes
=
defaultWeChatConnectScopesForMode
(
"mp"
)
case
hasLegacyOpen
:
cfg
.
Scopes
=
defaultWeChatConnectScopesForMode
(
"open"
)
}
}
}
func
normalizeWeChatConnectConfig
(
cfg
*
WeChatConnectConfig
)
{
if
cfg
==
nil
{
return
}
cfg
.
AppID
=
strings
.
TrimSpace
(
cfg
.
AppID
)
cfg
.
AppSecret
=
strings
.
TrimSpace
(
cfg
.
AppSecret
)
cfg
.
OpenAppID
=
strings
.
TrimSpace
(
cfg
.
OpenAppID
)
cfg
.
OpenAppSecret
=
strings
.
TrimSpace
(
cfg
.
OpenAppSecret
)
cfg
.
MPAppID
=
strings
.
TrimSpace
(
cfg
.
MPAppID
)
cfg
.
MPAppSecret
=
strings
.
TrimSpace
(
cfg
.
MPAppSecret
)
cfg
.
MobileAppID
=
strings
.
TrimSpace
(
cfg
.
MobileAppID
)
cfg
.
MobileAppSecret
=
strings
.
TrimSpace
(
cfg
.
MobileAppSecret
)
cfg
.
Mode
=
normalizeWeChatConnectMode
(
cfg
.
Mode
)
cfg
.
RedirectURL
=
strings
.
TrimSpace
(
cfg
.
RedirectURL
)
cfg
.
FrontendRedirectURL
=
strings
.
TrimSpace
(
cfg
.
FrontendRedirectURL
)
cfg
.
AppID
=
firstNonEmptyString
(
cfg
.
AppID
,
cfg
.
OpenAppID
,
cfg
.
MPAppID
,
cfg
.
MobileAppID
)
cfg
.
AppSecret
=
firstNonEmptyString
(
cfg
.
AppSecret
,
cfg
.
OpenAppSecret
,
cfg
.
MPAppSecret
,
cfg
.
MobileAppSecret
)
cfg
.
OpenAppID
=
firstNonEmptyString
(
cfg
.
OpenAppID
,
cfg
.
AppID
)
cfg
.
OpenAppSecret
=
firstNonEmptyString
(
cfg
.
OpenAppSecret
,
cfg
.
AppSecret
)
cfg
.
MPAppID
=
firstNonEmptyString
(
cfg
.
MPAppID
,
cfg
.
AppID
)
cfg
.
MPAppSecret
=
firstNonEmptyString
(
cfg
.
MPAppSecret
,
cfg
.
AppSecret
)
cfg
.
MobileAppID
=
firstNonEmptyString
(
cfg
.
MobileAppID
,
cfg
.
AppID
)
cfg
.
MobileAppSecret
=
firstNonEmptyString
(
cfg
.
MobileAppSecret
,
cfg
.
AppSecret
)
if
!
cfg
.
OpenEnabled
&&
!
cfg
.
MPEnabled
&&
!
cfg
.
MobileEnabled
&&
cfg
.
Enabled
{
switch
cfg
.
Mode
{
case
"mp"
:
cfg
.
MPEnabled
=
true
case
"mobile"
:
cfg
.
MobileEnabled
=
true
default
:
cfg
.
OpenEnabled
=
true
}
}
cfg
.
Mode
=
normalizeWeChatConnectStoredMode
(
cfg
.
OpenEnabled
,
cfg
.
MPEnabled
,
cfg
.
MobileEnabled
,
cfg
.
Mode
)
cfg
.
Scopes
=
normalizeWeChatConnectScopes
(
cfg
.
Scopes
,
cfg
.
Mode
)
if
cfg
.
FrontendRedirectURL
==
""
{
cfg
.
FrontendRedirectURL
=
defaultWeChatConnectFrontendRedirect
}
}
// TokenRefreshConfig OAuth token自动刷新配置
type
TokenRefreshConfig
struct
{
// 是否启用自动刷新
...
...
@@ -1012,6 +1243,8 @@ func load(allowMissingJWTSecret bool) (*Config, error) {
cfg
.
LinuxDo
.
UserInfoEmailPath
=
strings
.
TrimSpace
(
cfg
.
LinuxDo
.
UserInfoEmailPath
)
cfg
.
LinuxDo
.
UserInfoIDPath
=
strings
.
TrimSpace
(
cfg
.
LinuxDo
.
UserInfoIDPath
)
cfg
.
LinuxDo
.
UserInfoUsernamePath
=
strings
.
TrimSpace
(
cfg
.
LinuxDo
.
UserInfoUsernamePath
)
applyLegacyWeChatConnectEnvCompatibility
(
&
cfg
.
WeChat
)
normalizeWeChatConnectConfig
(
&
cfg
.
WeChat
)
cfg
.
OIDC
.
ProviderName
=
strings
.
TrimSpace
(
cfg
.
OIDC
.
ProviderName
)
cfg
.
OIDC
.
ClientID
=
strings
.
TrimSpace
(
cfg
.
OIDC
.
ClientID
)
cfg
.
OIDC
.
ClientSecret
=
strings
.
TrimSpace
(
cfg
.
OIDC
.
ClientSecret
)
...
...
@@ -1207,6 +1440,24 @@ func setDefaults() {
viper
.
SetDefault
(
"linuxdo_connect.userinfo_id_path"
,
""
)
viper
.
SetDefault
(
"linuxdo_connect.userinfo_username_path"
,
""
)
// WeChat Connect OAuth 登录
viper
.
SetDefault
(
"wechat_connect.enabled"
,
false
)
viper
.
SetDefault
(
"wechat_connect.app_id"
,
""
)
viper
.
SetDefault
(
"wechat_connect.app_secret"
,
""
)
viper
.
SetDefault
(
"wechat_connect.open_app_id"
,
""
)
viper
.
SetDefault
(
"wechat_connect.open_app_secret"
,
""
)
viper
.
SetDefault
(
"wechat_connect.mp_app_id"
,
""
)
viper
.
SetDefault
(
"wechat_connect.mp_app_secret"
,
""
)
viper
.
SetDefault
(
"wechat_connect.mobile_app_id"
,
""
)
viper
.
SetDefault
(
"wechat_connect.mobile_app_secret"
,
""
)
viper
.
SetDefault
(
"wechat_connect.open_enabled"
,
false
)
viper
.
SetDefault
(
"wechat_connect.mp_enabled"
,
false
)
viper
.
SetDefault
(
"wechat_connect.mobile_enabled"
,
false
)
viper
.
SetDefault
(
"wechat_connect.mode"
,
defaultWeChatConnectMode
)
viper
.
SetDefault
(
"wechat_connect.scopes"
,
defaultWeChatConnectScopes
)
viper
.
SetDefault
(
"wechat_connect.redirect_url"
,
""
)
viper
.
SetDefault
(
"wechat_connect.frontend_redirect_url"
,
defaultWeChatConnectFrontendRedirect
)
// Generic OIDC OAuth 登录
viper
.
SetDefault
(
"oidc_connect.enabled"
,
false
)
viper
.
SetDefault
(
"oidc_connect.provider_name"
,
"OIDC"
)
...
...
@@ -1222,8 +1473,8 @@ func setDefaults() {
viper
.
SetDefault
(
"oidc_connect.redirect_url"
,
""
)
viper
.
SetDefault
(
"oidc_connect.frontend_redirect_url"
,
"/auth/oidc/callback"
)
viper
.
SetDefault
(
"oidc_connect.token_auth_method"
,
"client_secret_post"
)
viper
.
SetDefault
(
"oidc_connect.use_pkce"
,
fals
e
)
viper
.
SetDefault
(
"oidc_connect.validate_id_token"
,
fals
e
)
viper
.
SetDefault
(
"oidc_connect.use_pkce"
,
tru
e
)
viper
.
SetDefault
(
"oidc_connect.validate_id_token"
,
tru
e
)
viper
.
SetDefault
(
"oidc_connect.allowed_signing_algs"
,
"RS256,ES256,PS256"
)
viper
.
SetDefault
(
"oidc_connect.clock_skew_seconds"
,
120
)
viper
.
SetDefault
(
"oidc_connect.require_email_verified"
,
false
)
...
...
@@ -1664,6 +1915,45 @@ func (c *Config) Validate() error {
warnIfInsecureURL
(
"linuxdo_connect.redirect_url"
,
c
.
LinuxDo
.
RedirectURL
)
warnIfInsecureURL
(
"linuxdo_connect.frontend_redirect_url"
,
c
.
LinuxDo
.
FrontendRedirectURL
)
}
if
c
.
WeChat
.
Enabled
{
weChat
:=
c
.
WeChat
normalizeWeChatConnectConfig
(
&
weChat
)
if
weChat
.
OpenEnabled
{
if
strings
.
TrimSpace
(
weChat
.
OpenAppID
)
==
""
{
return
fmt
.
Errorf
(
"wechat_connect.open_app_id is required when wechat_connect.open_enabled=true"
)
}
if
strings
.
TrimSpace
(
weChat
.
OpenAppSecret
)
==
""
{
return
fmt
.
Errorf
(
"wechat_connect.open_app_secret is required when wechat_connect.open_enabled=true"
)
}
}
if
weChat
.
MPEnabled
{
if
strings
.
TrimSpace
(
weChat
.
MPAppID
)
==
""
{
return
fmt
.
Errorf
(
"wechat_connect.mp_app_id is required when wechat_connect.mp_enabled=true"
)
}
if
strings
.
TrimSpace
(
weChat
.
MPAppSecret
)
==
""
{
return
fmt
.
Errorf
(
"wechat_connect.mp_app_secret is required when wechat_connect.mp_enabled=true"
)
}
}
if
weChat
.
MobileEnabled
{
if
strings
.
TrimSpace
(
weChat
.
MobileAppID
)
==
""
{
return
fmt
.
Errorf
(
"wechat_connect.mobile_app_id is required when wechat_connect.mobile_enabled=true"
)
}
if
strings
.
TrimSpace
(
weChat
.
MobileAppSecret
)
==
""
{
return
fmt
.
Errorf
(
"wechat_connect.mobile_app_secret is required when wechat_connect.mobile_enabled=true"
)
}
}
if
v
:=
strings
.
TrimSpace
(
weChat
.
RedirectURL
);
v
!=
""
{
if
err
:=
ValidateAbsoluteHTTPURL
(
v
);
err
!=
nil
{
return
fmt
.
Errorf
(
"wechat_connect.redirect_url invalid: %w"
,
err
)
}
warnIfInsecureURL
(
"wechat_connect.redirect_url"
,
v
)
}
if
err
:=
ValidateFrontendRedirectURL
(
weChat
.
FrontendRedirectURL
);
err
!=
nil
{
return
fmt
.
Errorf
(
"wechat_connect.frontend_redirect_url invalid: %w"
,
err
)
}
warnIfInsecureURL
(
"wechat_connect.frontend_redirect_url"
,
weChat
.
FrontendRedirectURL
)
}
if
c
.
OIDC
.
Enabled
{
if
strings
.
TrimSpace
(
c
.
OIDC
.
ClientID
)
==
""
{
return
fmt
.
Errorf
(
"oidc_connect.client_id is required when oidc_connect.enabled=true"
)
...
...
backend/internal/config/config_test.go
View file @
b2e07121
...
...
@@ -225,6 +225,37 @@ func TestLoadSchedulingConfigFromEnv(t *testing.T) {
}
}
func
TestLoadWeChatConnectConfigFromLegacyEnv
(
t
*
testing
.
T
)
{
resetViperWithJWTSecret
(
t
)
t
.
Setenv
(
"WECHAT_OAUTH_OPEN_APP_ID"
,
"wx-open-app"
)
t
.
Setenv
(
"WECHAT_OAUTH_OPEN_APP_SECRET"
,
"wx-open-secret"
)
t
.
Setenv
(
"WECHAT_OAUTH_MP_APP_ID"
,
"wx-mp-app"
)
t
.
Setenv
(
"WECHAT_OAUTH_MP_APP_SECRET"
,
"wx-mp-secret"
)
t
.
Setenv
(
"WECHAT_OAUTH_FRONTEND_REDIRECT_URL"
,
"/auth/wechat/legacy-callback"
)
cfg
,
err
:=
Load
()
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
cfg
.
WeChat
.
Enabled
)
require
.
True
(
t
,
cfg
.
WeChat
.
OpenEnabled
)
require
.
True
(
t
,
cfg
.
WeChat
.
MPEnabled
)
require
.
False
(
t
,
cfg
.
WeChat
.
MobileEnabled
)
require
.
Equal
(
t
,
"open"
,
cfg
.
WeChat
.
Mode
)
require
.
Equal
(
t
,
"wx-open-app"
,
cfg
.
WeChat
.
OpenAppID
)
require
.
Equal
(
t
,
"wx-open-secret"
,
cfg
.
WeChat
.
OpenAppSecret
)
require
.
Equal
(
t
,
"wx-mp-app"
,
cfg
.
WeChat
.
MPAppID
)
require
.
Equal
(
t
,
"wx-mp-secret"
,
cfg
.
WeChat
.
MPAppSecret
)
require
.
Equal
(
t
,
"/auth/wechat/legacy-callback"
,
cfg
.
WeChat
.
FrontendRedirectURL
)
}
func
TestLoadDefaultOIDCSecurityDefaults
(
t
*
testing
.
T
)
{
resetViperWithJWTSecret
(
t
)
cfg
,
err
:=
Load
()
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
cfg
.
OIDC
.
UsePKCE
)
require
.
True
(
t
,
cfg
.
OIDC
.
ValidateIDToken
)
}
func
TestLoadForcedCodexInstructionsTemplate
(
t
*
testing
.
T
)
{
resetViperWithJWTSecret
(
t
)
...
...
@@ -424,7 +455,7 @@ func TestValidateOIDCAllowsIssuerOnlyEndpointsWithDiscoveryFallback(t *testing.T
}
}
func
TestValidateOIDCAllows
Disabling
PKCEAndIDTokenValidation
(
t
*
testing
.
T
)
{
func
TestValidateOIDCAllows
ExplicitCompatibilityOverridesFor
PKCEAndIDTokenValidation
(
t
*
testing
.
T
)
{
resetViperWithJWTSecret
(
t
)
cfg
,
err
:=
Load
()
...
...
backend/internal/handler/admin/setting_handler.go
View file @
b2e07121
...
...
@@ -565,6 +565,15 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
req
.
WeChatConnectScopes
=
strings
.
TrimSpace
(
req
.
WeChatConnectScopes
)
req
.
WeChatConnectRedirectURL
=
strings
.
TrimSpace
(
req
.
WeChatConnectRedirectURL
)
req
.
WeChatConnectFrontendRedirectURL
=
strings
.
TrimSpace
(
req
.
WeChatConnectFrontendRedirectURL
)
req
.
WeChatConnectAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectAppID
,
previousSettings
.
WeChatConnectAppID
))
req
.
WeChatConnectRedirectURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectRedirectURL
,
previousSettings
.
WeChatConnectRedirectURL
))
req
.
WeChatConnectFrontendRedirectURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectFrontendRedirectURL
,
previousSettings
.
WeChatConnectFrontendRedirectURL
))
if
req
.
WeChatConnectMode
==
""
{
req
.
WeChatConnectMode
=
strings
.
ToLower
(
strings
.
TrimSpace
(
previousSettings
.
WeChatConnectMode
))
}
if
req
.
WeChatConnectScopes
==
""
{
req
.
WeChatConnectScopes
=
strings
.
TrimSpace
(
previousSettings
.
WeChatConnectScopes
)
}
if
req
.
WeChatConnectMPEnabled
&&
req
.
WeChatConnectMobileEnabled
{
response
.
BadRequest
(
c
,
"WeChat Official Account and Mobile App cannot be enabled at the same time"
)
...
...
@@ -598,9 +607,9 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
}
req
.
WeChatConnectOpenAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectOpenAppID
,
req
.
WeChatConnectAppID
))
req
.
WeChatConnectMPAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectMPAppID
,
req
.
WeChatConnectAppID
))
req
.
WeChatConnectMobileAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectMobileAppID
,
req
.
WeChatConnectAppID
))
req
.
WeChatConnectOpenAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectOpenAppID
,
req
.
WeChatConnectAppID
,
previousSettings
.
WeChatConnectOpenAppID
,
previousSettings
.
WeChatConnectAppID
))
req
.
WeChatConnectMPAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectMPAppID
,
req
.
WeChatConnectAppID
,
previousSettings
.
WeChatConnectMPAppID
,
previousSettings
.
WeChatConnectAppID
))
req
.
WeChatConnectMobileAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
WeChatConnectMobileAppID
,
req
.
WeChatConnectAppID
,
previousSettings
.
WeChatConnectMobileAppID
,
previousSettings
.
WeChatConnectAppID
))
if
req
.
WeChatConnectOpenAppSecret
==
""
{
req
.
WeChatConnectOpenAppSecret
=
strings
.
TrimSpace
(
firstNonEmpty
(
previousSettings
.
WeChatConnectOpenAppSecret
,
previousSettings
.
WeChatConnectAppSecret
,
req
.
WeChatConnectAppSecret
))
...
...
@@ -691,10 +700,35 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
req
.
OIDCConnectUserInfoEmailPath
=
strings
.
TrimSpace
(
req
.
OIDCConnectUserInfoEmailPath
)
req
.
OIDCConnectUserInfoIDPath
=
strings
.
TrimSpace
(
req
.
OIDCConnectUserInfoIDPath
)
req
.
OIDCConnectUserInfoUsernamePath
=
strings
.
TrimSpace
(
req
.
OIDCConnectUserInfoUsernamePath
)
if
req
.
OIDCConnectProviderName
==
""
{
req
.
OIDCConnectProviderName
=
"OIDC"
req
.
OIDCConnectProviderName
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectProviderName
,
previousSettings
.
OIDCConnectProviderName
,
"OIDC"
))
req
.
OIDCConnectClientID
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectClientID
,
previousSettings
.
OIDCConnectClientID
))
req
.
OIDCConnectIssuerURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectIssuerURL
,
previousSettings
.
OIDCConnectIssuerURL
))
req
.
OIDCConnectDiscoveryURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectDiscoveryURL
,
previousSettings
.
OIDCConnectDiscoveryURL
))
req
.
OIDCConnectAuthorizeURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectAuthorizeURL
,
previousSettings
.
OIDCConnectAuthorizeURL
))
req
.
OIDCConnectTokenURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectTokenURL
,
previousSettings
.
OIDCConnectTokenURL
))
req
.
OIDCConnectUserInfoURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectUserInfoURL
,
previousSettings
.
OIDCConnectUserInfoURL
))
req
.
OIDCConnectJWKSURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectJWKSURL
,
previousSettings
.
OIDCConnectJWKSURL
))
req
.
OIDCConnectScopes
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectScopes
,
previousSettings
.
OIDCConnectScopes
,
"openid email profile"
))
req
.
OIDCConnectRedirectURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectRedirectURL
,
previousSettings
.
OIDCConnectRedirectURL
))
req
.
OIDCConnectFrontendRedirectURL
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectFrontendRedirectURL
,
previousSettings
.
OIDCConnectFrontendRedirectURL
,
"/auth/oidc/callback"
))
req
.
OIDCConnectTokenAuthMethod
=
strings
.
ToLower
(
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectTokenAuthMethod
,
previousSettings
.
OIDCConnectTokenAuthMethod
,
"client_secret_post"
)))
req
.
OIDCConnectAllowedSigningAlgs
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectAllowedSigningAlgs
,
previousSettings
.
OIDCConnectAllowedSigningAlgs
,
"RS256,ES256,PS256"
))
req
.
OIDCConnectUserInfoEmailPath
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectUserInfoEmailPath
,
previousSettings
.
OIDCConnectUserInfoEmailPath
))
req
.
OIDCConnectUserInfoIDPath
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectUserInfoIDPath
,
previousSettings
.
OIDCConnectUserInfoIDPath
))
req
.
OIDCConnectUserInfoUsernamePath
=
strings
.
TrimSpace
(
firstNonEmpty
(
req
.
OIDCConnectUserInfoUsernamePath
,
previousSettings
.
OIDCConnectUserInfoUsernamePath
))
if
!
req
.
OIDCConnectUsePKCE
{
req
.
OIDCConnectUsePKCE
=
previousSettings
.
OIDCConnectUsePKCE
}
if
!
req
.
OIDCConnectValidateIDToken
{
req
.
OIDCConnectValidateIDToken
=
previousSettings
.
OIDCConnectValidateIDToken
}
if
req
.
OIDCConnectClockSkewSeconds
==
0
{
req
.
OIDCConnectClockSkewSeconds
=
previousSettings
.
OIDCConnectClockSkewSeconds
if
req
.
OIDCConnectClockSkewSeconds
==
0
{
req
.
OIDCConnectClockSkewSeconds
=
120
}
}
if
req
.
OIDCConnectClientID
==
""
{
response
.
BadRequest
(
c
,
"OIDC Client ID is required when enabled"
)
return
...
...
backend/internal/server/api_contract_test.go
View file @
b2e07121
...
...
@@ -784,6 +784,198 @@ func TestAPIContracts(t *testing.T) {
}
}`
,
},
{
name
:
"GET /api/v1/admin/settings falls back to config oauth defaults"
,
setup
:
func
(
t
*
testing
.
T
,
deps
*
contractDeps
)
{
t
.
Helper
()
deps
.
cfg
.
OIDC
=
config
.
OIDCConnectConfig
{
Enabled
:
true
,
ProviderName
:
"ConfigOIDC"
,
ClientID
:
"oidc-config-client"
,
ClientSecret
:
"oidc-config-secret"
,
IssuerURL
:
"https://issuer.example.com"
,
RedirectURL
:
"https://api.example.com/api/v1/auth/oauth/oidc/callback"
,
FrontendRedirectURL
:
"/auth/oidc/callback"
,
Scopes
:
"openid email profile"
,
TokenAuthMethod
:
"client_secret_post"
,
UsePKCE
:
true
,
ValidateIDToken
:
true
,
AllowedSigningAlgs
:
"RS256,ES256,PS256"
,
ClockSkewSeconds
:
120
,
}
deps
.
cfg
.
WeChat
=
config
.
WeChatConnectConfig
{
Enabled
:
true
,
OpenEnabled
:
true
,
OpenAppID
:
"wx-open-config"
,
OpenAppSecret
:
"wx-open-secret"
,
Mode
:
"open"
,
Scopes
:
"snsapi_login"
,
FrontendRedirectURL
:
"/auth/wechat/callback"
,
}
deps
.
settingRepo
.
SetAll
(
map
[
string
]
string
{
service
.
SettingKeyRegistrationEnabled
:
"true"
,
service
.
SettingKeyEmailVerifyEnabled
:
"false"
,
service
.
SettingKeyRegistrationEmailSuffixWhitelist
:
"[]"
,
})
},
method
:
http
.
MethodGet
,
path
:
"/api/v1/admin/settings"
,
wantStatus
:
http
.
StatusOK
,
wantJSON
:
`{
"code": 0,
"message": "success",
"data": {
"registration_enabled": true,
"email_verify_enabled": false,
"registration_email_suffix_whitelist": [],
"promo_code_enabled": true,
"password_reset_enabled": false,
"frontend_url": "",
"invitation_code_enabled": false,
"totp_enabled": false,
"totp_encryption_key_configured": false,
"smtp_host": "",
"smtp_port": 587,
"smtp_username": "",
"smtp_password_configured": false,
"smtp_from_email": "",
"smtp_from_name": "",
"smtp_use_tls": false,
"turnstile_enabled": false,
"turnstile_site_key": "",
"turnstile_secret_key_configured": false,
"linuxdo_connect_enabled": false,
"linuxdo_connect_client_id": "",
"linuxdo_connect_client_secret_configured": false,
"linuxdo_connect_redirect_url": "",
"oidc_connect_enabled": true,
"oidc_connect_provider_name": "ConfigOIDC",
"oidc_connect_client_id": "oidc-config-client",
"oidc_connect_client_secret_configured": true,
"oidc_connect_issuer_url": "https://issuer.example.com",
"oidc_connect_discovery_url": "",
"oidc_connect_authorize_url": "",
"oidc_connect_token_url": "",
"oidc_connect_userinfo_url": "",
"oidc_connect_jwks_url": "",
"oidc_connect_scopes": "openid email profile",
"oidc_connect_redirect_url": "https://api.example.com/api/v1/auth/oauth/oidc/callback",
"oidc_connect_frontend_redirect_url": "/auth/oidc/callback",
"oidc_connect_token_auth_method": "client_secret_post",
"oidc_connect_use_pkce": true,
"oidc_connect_validate_id_token": true,
"oidc_connect_allowed_signing_algs": "RS256,ES256,PS256",
"oidc_connect_clock_skew_seconds": 120,
"oidc_connect_require_email_verified": false,
"oidc_connect_userinfo_email_path": "",
"oidc_connect_userinfo_id_path": "",
"oidc_connect_userinfo_username_path": "",
"site_name": "Sub2API",
"site_logo": "",
"site_subtitle": "Subscription to API Conversion Platform",
"api_base_url": "",
"contact_info": "",
"doc_url": "",
"home_content": "",
"hide_ccs_import_button": false,
"purchase_subscription_enabled": false,
"purchase_subscription_url": "",
"table_default_page_size": 20,
"table_page_size_options": [10, 20, 50],
"custom_menu_items": [],
"custom_endpoints": [],
"default_concurrency": 0,
"default_balance": 0,
"default_subscriptions": [],
"enable_model_fallback": false,
"fallback_model_anthropic": "claude-3-5-sonnet-20241022",
"fallback_model_openai": "gpt-4o",
"fallback_model_gemini": "gemini-2.5-pro",
"fallback_model_antigravity": "gemini-2.5-pro",
"enable_identity_patch": true,
"identity_patch_prompt": "",
"ops_monitoring_enabled": false,
"ops_realtime_monitoring_enabled": true,
"ops_query_mode_default": "auto",
"ops_metrics_interval_seconds": 60,
"min_claude_code_version": "",
"max_claude_code_version": "",
"allow_ungrouped_key_scheduling": false,
"backend_mode_enabled": false,
"enable_fingerprint_unification": true,
"enable_metadata_passthrough": false,
"enable_cch_signing": false,
"web_search_emulation_enabled": false,
"payment_visible_method_alipay_source": "",
"payment_visible_method_wxpay_source": "",
"payment_visible_method_alipay_enabled": false,
"payment_visible_method_wxpay_enabled": false,
"openai_advanced_scheduler_enabled": false,
"payment_enabled": false,
"payment_min_amount": 0,
"payment_max_amount": 0,
"payment_daily_limit": 0,
"payment_order_timeout_minutes": 0,
"payment_max_pending_orders": 0,
"payment_enabled_types": null,
"payment_balance_disabled": false,
"payment_balance_recharge_multiplier": 0,
"payment_recharge_fee_rate": 0,
"payment_load_balance_strategy": "",
"payment_product_name_prefix": "",
"payment_product_name_suffix": "",
"payment_help_image_url": "",
"payment_help_text": "",
"payment_cancel_rate_limit_enabled": false,
"payment_cancel_rate_limit_max": 0,
"payment_cancel_rate_limit_window": 0,
"payment_cancel_rate_limit_unit": "",
"payment_cancel_rate_limit_window_mode": "",
"balance_low_notify_enabled": false,
"account_quota_notify_enabled": false,
"balance_low_notify_threshold": 0,
"balance_low_notify_recharge_url": "",
"account_quota_notify_emails": [],
"wechat_connect_enabled": true,
"wechat_connect_app_id": "wx-open-config",
"wechat_connect_app_secret_configured": true,
"wechat_connect_mode": "open",
"wechat_connect_open_enabled": true,
"wechat_connect_open_app_id": "wx-open-config",
"wechat_connect_open_app_secret_configured": true,
"wechat_connect_mp_enabled": false,
"wechat_connect_mp_app_id": "wx-open-config",
"wechat_connect_mp_app_secret_configured": true,
"wechat_connect_mobile_enabled": false,
"wechat_connect_mobile_app_id": "wx-open-config",
"wechat_connect_mobile_app_secret_configured": true,
"wechat_connect_redirect_url": "",
"wechat_connect_frontend_redirect_url": "/auth/wechat/callback",
"wechat_connect_scopes": "snsapi_login",
"auth_source_default_email_balance": 0,
"auth_source_default_email_concurrency": 5,
"auth_source_default_email_subscriptions": [],
"auth_source_default_email_grant_on_signup": false,
"auth_source_default_email_grant_on_first_bind": false,
"auth_source_default_linuxdo_balance": 0,
"auth_source_default_linuxdo_concurrency": 5,
"auth_source_default_linuxdo_subscriptions": [],
"auth_source_default_linuxdo_grant_on_signup": false,
"auth_source_default_linuxdo_grant_on_first_bind": false,
"auth_source_default_oidc_balance": 0,
"auth_source_default_oidc_concurrency": 5,
"auth_source_default_oidc_subscriptions": [],
"auth_source_default_oidc_grant_on_signup": false,
"auth_source_default_oidc_grant_on_first_bind": false,
"auth_source_default_wechat_balance": 0,
"auth_source_default_wechat_concurrency": 5,
"auth_source_default_wechat_subscriptions": [],
"auth_source_default_wechat_grant_on_signup": false,
"auth_source_default_wechat_grant_on_first_bind": false,
"force_email_on_third_party_signup": false
}
}`
,
},
{
name
:
"POST /api/v1/admin/accounts/bulk-update"
,
method
:
http
.
MethodPost
,
...
...
@@ -827,6 +1019,7 @@ func TestAPIContracts(t *testing.T) {
type
contractDeps
struct
{
now
time
.
Time
router
http
.
Handler
cfg
*
config
.
Config
apiKeyRepo
*
stubApiKeyRepo
groupRepo
*
stubGroupRepo
userSubRepo
*
stubUserSubscriptionRepo
...
...
@@ -947,6 +1140,7 @@ func newContractDeps(t *testing.T) *contractDeps {
return
&
contractDeps
{
now
:
now
,
router
:
r
,
cfg
:
cfg
,
apiKeyRepo
:
apiKeyRepo
,
groupRepo
:
groupRepo
,
userSubRepo
:
userSubRepo
,
...
...
backend/internal/service/setting_service.go
View file @
b2e07121
...
...
@@ -245,15 +245,107 @@ func parseWeChatConnectCapabilitySettings(settings map[string]string, enabled bo
}
func
normalizeWeChatConnectStoredMode
(
openEnabled
,
mpEnabled
,
mobileEnabled
bool
,
mode
string
)
string
{
mode
=
normalizeWeChatConnectModeSetting
(
mode
)
switch
mode
{
case
"open"
:
if
openEnabled
{
return
"open"
}
case
"mp"
:
if
mpEnabled
{
return
"mp"
}
case
"mobile"
:
if
mobileEnabled
{
return
"mobile"
}
}
switch
{
case
openEnabled
:
return
"open"
case
mpEnabled
:
return
"mp"
case
mobileEnabled
:
return
"mobile"
case
openEnabled
:
return
"open"
default
:
return
normalizeWeChatConnectModeSetting
(
mode
)
return
mode
}
}
func
mergeWeChatConnectCapabilitySettings
(
settings
map
[
string
]
string
,
base
config
.
WeChatConnectConfig
,
enabled
bool
,
mode
string
)
(
bool
,
bool
,
bool
)
{
mode
=
normalizeWeChatConnectModeSetting
(
firstNonEmpty
(
mode
,
base
.
Mode
))
rawOpen
,
hasOpen
:=
settings
[
SettingKeyWeChatConnectOpenEnabled
]
rawMP
,
hasMP
:=
settings
[
SettingKeyWeChatConnectMPEnabled
]
rawMobile
,
hasMobile
:=
settings
[
SettingKeyWeChatConnectMobileEnabled
]
openConfigured
:=
hasOpen
&&
strings
.
TrimSpace
(
rawOpen
)
!=
""
mpConfigured
:=
hasMP
&&
strings
.
TrimSpace
(
rawMP
)
!=
""
mobileConfigured
:=
hasMobile
&&
strings
.
TrimSpace
(
rawMobile
)
!=
""
if
openConfigured
||
mpConfigured
||
mobileConfigured
{
return
parseWeChatConnectCapabilitySettings
(
settings
,
enabled
,
mode
)
}
if
!
enabled
{
return
false
,
false
,
false
}
if
base
.
OpenEnabled
||
base
.
MPEnabled
||
base
.
MobileEnabled
{
return
base
.
OpenEnabled
,
base
.
MPEnabled
,
base
.
MobileEnabled
}
return
parseWeChatConnectCapabilitySettings
(
settings
,
enabled
,
mode
)
}
func
(
s
*
SettingService
)
effectiveWeChatConnectOAuthConfig
(
settings
map
[
string
]
string
)
WeChatConnectOAuthConfig
{
base
:=
config
.
WeChatConnectConfig
{}
if
s
!=
nil
&&
s
.
cfg
!=
nil
{
base
=
s
.
cfg
.
WeChat
}
enabled
:=
base
.
Enabled
if
raw
,
ok
:=
settings
[
SettingKeyWeChatConnectEnabled
];
ok
{
enabled
=
strings
.
TrimSpace
(
raw
)
==
"true"
}
legacyAppID
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectAppID
],
base
.
AppID
,
base
.
OpenAppID
,
base
.
MPAppID
,
base
.
MobileAppID
,
))
legacyAppSecret
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectAppSecret
],
base
.
AppSecret
,
base
.
OpenAppSecret
,
base
.
MPAppSecret
,
base
.
MobileAppSecret
,
))
openAppID
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectOpenAppID
],
base
.
OpenAppID
,
legacyAppID
))
openAppSecret
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectOpenAppSecret
],
base
.
OpenAppSecret
,
legacyAppSecret
))
mpAppID
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMPAppID
],
base
.
MPAppID
,
legacyAppID
))
mpAppSecret
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMPAppSecret
],
base
.
MPAppSecret
,
legacyAppSecret
))
mobileAppID
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMobileAppID
],
base
.
MobileAppID
,
legacyAppID
))
mobileAppSecret
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMobileAppSecret
],
base
.
MobileAppSecret
,
legacyAppSecret
))
modeRaw
:=
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMode
],
base
.
Mode
)
openEnabled
,
mpEnabled
,
mobileEnabled
:=
mergeWeChatConnectCapabilitySettings
(
settings
,
base
,
enabled
,
modeRaw
)
mode
:=
normalizeWeChatConnectStoredMode
(
openEnabled
,
mpEnabled
,
mobileEnabled
,
modeRaw
)
return
WeChatConnectOAuthConfig
{
Enabled
:
enabled
,
LegacyAppID
:
legacyAppID
,
LegacyAppSecret
:
legacyAppSecret
,
OpenAppID
:
openAppID
,
OpenAppSecret
:
openAppSecret
,
MPAppID
:
mpAppID
,
MPAppSecret
:
mpAppSecret
,
MobileAppID
:
mobileAppID
,
MobileAppSecret
:
mobileAppSecret
,
OpenEnabled
:
openEnabled
,
MPEnabled
:
mpEnabled
,
MobileEnabled
:
mobileEnabled
,
Mode
:
mode
,
Scopes
:
normalizeWeChatConnectScopeSetting
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectScopes
],
base
.
Scopes
),
mode
),
RedirectURL
:
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectRedirectURL
],
base
.
RedirectURL
)),
FrontendRedirectURL
:
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectFrontendRedirectURL
],
base
.
FrontendRedirectURL
,
defaultWeChatConnectFrontend
)),
}
}
...
...
@@ -535,32 +627,7 @@ func DefaultWeChatConnectScopesForMode(mode string) string {
}
func
(
s
*
SettingService
)
parseWeChatConnectOAuthConfig
(
settings
map
[
string
]
string
)
(
WeChatConnectOAuthConfig
,
error
)
{
enabled
:=
settings
[
SettingKeyWeChatConnectEnabled
]
==
"true"
mode
:=
normalizeWeChatConnectModeSetting
(
settings
[
SettingKeyWeChatConnectMode
])
openEnabled
,
mpEnabled
,
mobileEnabled
:=
parseWeChatConnectCapabilitySettings
(
settings
,
enabled
,
mode
)
mode
=
normalizeWeChatConnectStoredMode
(
openEnabled
,
mpEnabled
,
mobileEnabled
,
mode
)
cfg
:=
WeChatConnectOAuthConfig
{
Enabled
:
enabled
,
LegacyAppID
:
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectAppID
]),
LegacyAppSecret
:
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectAppSecret
]),
OpenAppID
:
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectOpenAppID
],
settings
[
SettingKeyWeChatConnectAppID
])),
OpenAppSecret
:
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectOpenAppSecret
],
settings
[
SettingKeyWeChatConnectAppSecret
])),
MPAppID
:
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMPAppID
],
settings
[
SettingKeyWeChatConnectAppID
])),
MPAppSecret
:
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMPAppSecret
],
settings
[
SettingKeyWeChatConnectAppSecret
])),
MobileAppID
:
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMobileAppID
],
settings
[
SettingKeyWeChatConnectAppID
])),
MobileAppSecret
:
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMobileAppSecret
],
settings
[
SettingKeyWeChatConnectAppSecret
])),
OpenEnabled
:
openEnabled
,
MPEnabled
:
mpEnabled
,
MobileEnabled
:
mobileEnabled
,
Mode
:
mode
,
Scopes
:
normalizeWeChatConnectScopeSetting
(
settings
[
SettingKeyWeChatConnectScopes
],
mode
),
RedirectURL
:
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectRedirectURL
]),
FrontendRedirectURL
:
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectFrontendRedirectURL
]),
}
if
cfg
.
FrontendRedirectURL
==
""
{
cfg
.
FrontendRedirectURL
=
defaultWeChatConnectFrontend
}
cfg
:=
s
.
effectiveWeChatConnectOAuthConfig
(
settings
)
if
!
cfg
.
Enabled
||
(
!
cfg
.
OpenEnabled
&&
!
cfg
.
MPEnabled
)
{
return
WeChatConnectOAuthConfig
{},
infraerrors
.
NotFound
(
"OAUTH_DISABLED"
,
"wechat oauth is disabled"
)
...
...
@@ -589,15 +656,11 @@ func (s *SettingService) parseWeChatConnectOAuthConfig(settings map[string]strin
return
WeChatConnectOAuthConfig
{},
infraerrors
.
InternalServer
(
"OAUTH_CONFIG_INVALID"
,
"wechat oauth mobile app secret not configured"
)
}
}
if
cfg
.
RedirectURL
==
""
{
return
WeChatConnectOAuthConfig
{},
infraerrors
.
InternalServer
(
"OAUTH_CONFIG_INVALID"
,
"wechat oauth redirect url not configured"
)
}
if
cfg
.
FrontendRedirectURL
==
""
{
return
WeChatConnectOAuthConfig
{},
infraerrors
.
InternalServer
(
"OAUTH_CONFIG_INVALID"
,
"wechat oauth frontend redirect url not configured"
)
}
if
err
:=
config
.
ValidateAbsoluteHTTPURL
(
cfg
.
RedirectURL
);
err
!=
nil
{
if
v
:=
strings
.
TrimSpace
(
cfg
.
RedirectURL
);
v
!=
""
{
if
err
:=
config
.
ValidateAbsoluteHTTPURL
(
v
);
err
!=
nil
{
return
WeChatConnectOAuthConfig
{},
infraerrors
.
InternalServer
(
"OAUTH_CONFIG_INVALID"
,
"wechat oauth redirect url invalid"
)
}
}
if
err
:=
config
.
ValidateFrontendRedirectURL
(
cfg
.
FrontendRedirectURL
);
err
!=
nil
{
return
WeChatConnectOAuthConfig
{},
infraerrors
.
InternalServer
(
"OAUTH_CONFIG_INVALID"
,
"wechat oauth frontend redirect url invalid"
)
}
...
...
@@ -605,31 +668,14 @@ func (s *SettingService) parseWeChatConnectOAuthConfig(settings map[string]strin
}
func
(
s
*
SettingService
)
weChatOAuthCapabilitiesFromSettings
(
settings
map
[
string
]
string
)
(
bool
,
bool
,
bool
,
bool
)
{
if
settings
[
SettingKeyWeChatConnectEnabled
]
!=
"true"
{
cfg
:=
s
.
effectiveWeChatConnectOAuthConfig
(
settings
)
if
!
cfg
.
Enabled
{
return
false
,
false
,
false
,
false
}
mode
:=
normalizeWeChatConnectModeSetting
(
settings
[
SettingKeyWeChatConnectMode
])
openEnabled
,
mpEnabled
,
mobileEnabled
:=
parseWeChatConnectCapabilitySettings
(
settings
,
true
,
mode
)
redirectURL
:=
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectRedirectURL
])
frontendRedirectURL
:=
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectFrontendRedirectURL
])
if
frontendRedirectURL
==
""
{
frontendRedirectURL
=
defaultWeChatConnectFrontend
}
legacyAppID
:=
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectAppID
])
legacyAppSecret
:=
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectAppSecret
])
openAppID
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectOpenAppID
],
legacyAppID
))
openAppSecret
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectOpenAppSecret
],
legacyAppSecret
))
mpAppID
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMPAppID
],
legacyAppID
))
mpAppSecret
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMPAppSecret
],
legacyAppSecret
))
mobileAppID
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMobileAppID
],
legacyAppID
))
mobileAppSecret
:=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMobileAppSecret
],
legacyAppSecret
))
webRedirectReady
:=
redirectURL
!=
""
&&
frontendRedirectURL
!=
""
openReady
:=
openEnabled
&&
webRedirectReady
&&
openAppID
!=
""
&&
openAppSecret
!=
""
mpReady
:=
mpEnabled
&&
webRedirectReady
&&
mpAppID
!=
""
&&
mpAppSecret
!=
""
mobileReady
:=
mobileEnabled
&&
mobileAppID
!=
""
&&
mobileAppSecret
!=
""
openReady
:=
cfg
.
OpenEnabled
&&
cfg
.
AppIDForMode
(
"open"
)
!=
""
&&
cfg
.
AppSecretForMode
(
"open"
)
!=
""
mpReady
:=
cfg
.
MPEnabled
&&
cfg
.
AppIDForMode
(
"mp"
)
!=
""
&&
cfg
.
AppSecretForMode
(
"mp"
)
!=
""
mobileReady
:=
cfg
.
MobileEnabled
&&
cfg
.
AppIDForMode
(
"mobile"
)
!=
""
&&
cfg
.
AppSecretForMode
(
"mobile"
)
!=
""
return
openReady
||
mpReady
,
openReady
,
mpReady
,
mobileReady
}
...
...
@@ -1436,6 +1482,8 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeyCustomMenuItems
:
"[]"
,
SettingKeyCustomEndpoints
:
"[]"
,
SettingKeyWeChatConnectEnabled
:
"false"
,
SettingKeyWeChatConnectAppID
:
""
,
SettingKeyWeChatConnectAppSecret
:
""
,
SettingKeyWeChatConnectOpenAppID
:
""
,
SettingKeyWeChatConnectOpenAppSecret
:
""
,
SettingKeyWeChatConnectMPAppID
:
""
,
...
...
@@ -1447,9 +1495,30 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeyWeChatConnectMobileEnabled
:
"false"
,
SettingKeyWeChatConnectMode
:
"open"
,
SettingKeyWeChatConnectScopes
:
"snsapi_login"
,
SettingKeyWeChatConnectRedirectURL
:
""
,
SettingKeyWeChatConnectFrontendRedirectURL
:
defaultWeChatConnectFrontend
,
SettingKeyOIDCConnectEnabled
:
"false"
,
SettingKeyOIDCConnectProviderName
:
"OIDC"
,
SettingKeyOIDCConnectClientID
:
""
,
SettingKeyOIDCConnectClientSecret
:
""
,
SettingKeyOIDCConnectIssuerURL
:
""
,
SettingKeyOIDCConnectDiscoveryURL
:
""
,
SettingKeyOIDCConnectAuthorizeURL
:
""
,
SettingKeyOIDCConnectTokenURL
:
""
,
SettingKeyOIDCConnectUserInfoURL
:
""
,
SettingKeyOIDCConnectJWKSURL
:
""
,
SettingKeyOIDCConnectScopes
:
"openid email profile"
,
SettingKeyOIDCConnectRedirectURL
:
""
,
SettingKeyOIDCConnectFrontendRedirectURL
:
"/auth/oidc/callback"
,
SettingKeyOIDCConnectTokenAuthMethod
:
"client_secret_post"
,
SettingKeyOIDCConnectUsePKCE
:
"true"
,
SettingKeyOIDCConnectValidateIDToken
:
"true"
,
SettingKeyOIDCConnectAllowedSigningAlgs
:
"RS256,ES256,PS256"
,
SettingKeyOIDCConnectClockSkewSeconds
:
"120"
,
SettingKeyOIDCConnectRequireEmailVerified
:
"false"
,
SettingKeyOIDCConnectUserInfoEmailPath
:
""
,
SettingKeyOIDCConnectUserInfoIDPath
:
""
,
SettingKeyOIDCConnectUserInfoUsernamePath
:
""
,
SettingKeyDefaultConcurrency
:
strconv
.
Itoa
(
s
.
cfg
.
Default
.
UserConcurrency
),
SettingKeyDefaultBalance
:
strconv
.
FormatFloat
(
s
.
cfg
.
Default
.
UserBalance
,
'f'
,
8
,
64
),
SettingKeyDefaultSubscriptions
:
"[]"
,
...
...
@@ -1737,37 +1806,30 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
}
result
.
OIDCConnectClientSecretConfigured
=
result
.
OIDCConnectClientSecret
!=
""
// WeChat Connect 设置:完全以 DB 系统设置为准。
result
.
WeChatConnectEnabled
=
settings
[
SettingKeyWeChatConnectEnabled
]
==
"true"
result
.
WeChatConnectAppID
=
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectAppID
])
result
.
WeChatConnectAppSecret
=
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectAppSecret
])
result
.
WeChatConnectAppSecretConfigured
=
result
.
WeChatConnectAppSecret
!=
""
result
.
WeChatConnectOpenAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectOpenAppID
],
result
.
WeChatConnectAppID
))
result
.
WeChatConnectOpenAppSecret
=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectOpenAppSecret
],
result
.
WeChatConnectAppSecret
))
result
.
WeChatConnectOpenAppSecretConfigured
=
result
.
WeChatConnectOpenAppSecret
!=
""
result
.
WeChatConnectMPAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMPAppID
],
result
.
WeChatConnectAppID
))
result
.
WeChatConnectMPAppSecret
=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMPAppSecret
],
result
.
WeChatConnectAppSecret
))
result
.
WeChatConnectMPAppSecretConfigured
=
result
.
WeChatConnectMPAppSecret
!=
""
result
.
WeChatConnectMobileAppID
=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMobileAppID
],
result
.
WeChatConnectAppID
))
result
.
WeChatConnectMobileAppSecret
=
strings
.
TrimSpace
(
firstNonEmpty
(
settings
[
SettingKeyWeChatConnectMobileAppSecret
],
result
.
WeChatConnectAppSecret
))
result
.
WeChatConnectMobileAppSecretConfigured
=
result
.
WeChatConnectMobileAppSecret
!=
""
result
.
WeChatConnectOpenEnabled
,
result
.
WeChatConnectMPEnabled
,
result
.
WeChatConnectMobileEnabled
=
parseWeChatConnectCapabilitySettings
(
settings
,
result
.
WeChatConnectEnabled
,
settings
[
SettingKeyWeChatConnectMode
],
)
result
.
WeChatConnectMode
=
normalizeWeChatConnectStoredMode
(
result
.
WeChatConnectOpenEnabled
,
result
.
WeChatConnectMPEnabled
,
result
.
WeChatConnectMobileEnabled
,
settings
[
SettingKeyWeChatConnectMode
],
)
result
.
WeChatConnectScopes
=
normalizeWeChatConnectScopeSetting
(
settings
[
SettingKeyWeChatConnectScopes
],
result
.
WeChatConnectMode
)
result
.
WeChatConnectRedirectURL
=
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectRedirectURL
])
result
.
WeChatConnectFrontendRedirectURL
=
strings
.
TrimSpace
(
settings
[
SettingKeyWeChatConnectFrontendRedirectURL
])
if
result
.
WeChatConnectFrontendRedirectURL
==
""
{
result
.
WeChatConnectFrontendRedirectURL
=
defaultWeChatConnectFrontend
}
// WeChat Connect 设置:
// - 优先读取 DB 系统设置
// - 缺失时回退到 config/env,保持升级兼容
weChatEffective
:=
s
.
effectiveWeChatConnectOAuthConfig
(
settings
)
result
.
WeChatConnectEnabled
=
weChatEffective
.
Enabled
result
.
WeChatConnectAppID
=
weChatEffective
.
LegacyAppID
result
.
WeChatConnectAppSecret
=
weChatEffective
.
LegacyAppSecret
result
.
WeChatConnectAppSecretConfigured
=
weChatEffective
.
LegacyAppSecret
!=
""
result
.
WeChatConnectOpenAppID
=
weChatEffective
.
OpenAppID
result
.
WeChatConnectOpenAppSecret
=
weChatEffective
.
OpenAppSecret
result
.
WeChatConnectOpenAppSecretConfigured
=
weChatEffective
.
OpenAppSecret
!=
""
result
.
WeChatConnectMPAppID
=
weChatEffective
.
MPAppID
result
.
WeChatConnectMPAppSecret
=
weChatEffective
.
MPAppSecret
result
.
WeChatConnectMPAppSecretConfigured
=
weChatEffective
.
MPAppSecret
!=
""
result
.
WeChatConnectMobileAppID
=
weChatEffective
.
MobileAppID
result
.
WeChatConnectMobileAppSecret
=
weChatEffective
.
MobileAppSecret
result
.
WeChatConnectMobileAppSecretConfigured
=
weChatEffective
.
MobileAppSecret
!=
""
result
.
WeChatConnectOpenEnabled
=
weChatEffective
.
OpenEnabled
result
.
WeChatConnectMPEnabled
=
weChatEffective
.
MPEnabled
result
.
WeChatConnectMobileEnabled
=
weChatEffective
.
MobileEnabled
result
.
WeChatConnectMode
=
weChatEffective
.
Mode
result
.
WeChatConnectScopes
=
weChatEffective
.
Scopes
result
.
WeChatConnectRedirectURL
=
weChatEffective
.
RedirectURL
result
.
WeChatConnectFrontendRedirectURL
=
weChatEffective
.
FrontendRedirectURL
// Model fallback settings
result
.
EnableModelFallback
=
settings
[
SettingKeyEnableModelFallback
]
==
"true"
...
...
backend/internal/service/setting_service_oidc_config_test.go
View file @
b2e07121
...
...
@@ -115,6 +115,22 @@ func TestSettingService_ParseSettings_PreservesOptionalOIDCCompatibilityFlags(t
require
.
False
(
t
,
got
.
OIDCConnectValidateIDToken
)
}
func
TestSettingService_ParseSettings_DefaultsOIDCSecurityFlagsToSafeConfigValues
(
t
*
testing
.
T
)
{
svc
:=
NewSettingService
(
&
settingOIDCRepoStub
{
values
:
map
[
string
]
string
{}},
&
config
.
Config
{
OIDC
:
config
.
OIDCConnectConfig
{
UsePKCE
:
true
,
ValidateIDToken
:
true
,
},
})
got
:=
svc
.
parseSettings
(
map
[
string
]
string
{
SettingKeyOIDCConnectEnabled
:
"true"
,
})
require
.
True
(
t
,
got
.
OIDCConnectUsePKCE
)
require
.
True
(
t
,
got
.
OIDCConnectValidateIDToken
)
}
func
TestGetOIDCConnectOAuthConfig_AllowsCompatibilityFlagsToDisablePKCEAndIDTokenValidation
(
t
*
testing
.
T
)
{
cfg
:=
&
config
.
Config
{
OIDC
:
config
.
OIDCConnectConfig
{
...
...
@@ -145,3 +161,37 @@ func TestGetOIDCConnectOAuthConfig_AllowsCompatibilityFlagsToDisablePKCEAndIDTok
require
.
False
(
t
,
got
.
UsePKCE
)
require
.
False
(
t
,
got
.
ValidateIDToken
)
}
func
TestGetOIDCConnectOAuthConfig_DefaultsToSecureFlagsWhenSettingsMissing
(
t
*
testing
.
T
)
{
cfg
:=
&
config
.
Config
{
OIDC
:
config
.
OIDCConnectConfig
{
Enabled
:
true
,
ProviderName
:
"OIDC"
,
ClientID
:
"oidc-client"
,
ClientSecret
:
"oidc-secret"
,
IssuerURL
:
"https://issuer.example.com"
,
AuthorizeURL
:
"https://issuer.example.com/auth"
,
TokenURL
:
"https://issuer.example.com/token"
,
UserInfoURL
:
"https://issuer.example.com/userinfo"
,
JWKSURL
:
"https://issuer.example.com/jwks"
,
RedirectURL
:
"https://example.com/api/v1/auth/oauth/oidc/callback"
,
FrontendRedirectURL
:
"/auth/oidc/callback"
,
Scopes
:
"openid email profile"
,
TokenAuthMethod
:
"client_secret_post"
,
UsePKCE
:
true
,
ValidateIDToken
:
true
,
AllowedSigningAlgs
:
"RS256"
,
ClockSkewSeconds
:
120
,
},
}
repo
:=
&
settingOIDCRepoStub
{
values
:
map
[
string
]
string
{
SettingKeyOIDCConnectEnabled
:
"true"
,
}}
svc
:=
NewSettingService
(
repo
,
cfg
)
got
,
err
:=
svc
.
GetOIDCConnectOAuthConfig
(
context
.
Background
())
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
got
.
UsePKCE
)
require
.
True
(
t
,
got
.
ValidateIDToken
)
}
backend/internal/service/setting_service_public_test.go
View file @
b2e07121
...
...
@@ -132,3 +132,22 @@ func TestSettingService_GetPublicSettings_DoesNotExposeMobileOnlyWeChatAsWebOAut
require
.
False
(
t
,
settings
.
WeChatOAuthMPEnabled
)
require
.
True
(
t
,
settings
.
WeChatOAuthMobileEnabled
)
}
func
TestSettingService_GetPublicSettings_FallsBackToConfigForWeChatOAuthCapabilities
(
t
*
testing
.
T
)
{
svc
:=
NewSettingService
(
&
settingPublicRepoStub
{
values
:
map
[
string
]
string
{}},
&
config
.
Config
{
WeChat
:
config
.
WeChatConnectConfig
{
Enabled
:
true
,
OpenEnabled
:
true
,
OpenAppID
:
"wx-open-config"
,
OpenAppSecret
:
"wx-open-secret"
,
FrontendRedirectURL
:
"/auth/wechat/config-callback"
,
},
})
settings
,
err
:=
svc
.
GetPublicSettings
(
context
.
Background
())
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
settings
.
WeChatOAuthEnabled
)
require
.
True
(
t
,
settings
.
WeChatOAuthOpenEnabled
)
require
.
False
(
t
,
settings
.
WeChatOAuthMPEnabled
)
require
.
False
(
t
,
settings
.
WeChatOAuthMobileEnabled
)
}
backend/internal/service/setting_service_wechat_config_test.go
View file @
b2e07121
...
...
@@ -79,3 +79,54 @@ func TestSettingService_GetWeChatConnectOAuthConfig_UsesDatabaseOverrides(t *tes
require
.
Equal
(
t
,
"https://api.example.com/api/v1/auth/oauth/wechat/callback"
,
got
.
RedirectURL
)
require
.
Equal
(
t
,
"/auth/wechat/callback"
,
got
.
FrontendRedirectURL
)
}
func
TestSettingService_GetWeChatConnectOAuthConfig_FallsBackToConfigWhenDatabaseEmpty
(
t
*
testing
.
T
)
{
repo
:=
&
settingWeChatRepoStub
{
values
:
map
[
string
]
string
{}}
svc
:=
NewSettingService
(
repo
,
&
config
.
Config
{
WeChat
:
config
.
WeChatConnectConfig
{
Enabled
:
true
,
OpenEnabled
:
true
,
MPEnabled
:
true
,
Mode
:
"open"
,
OpenAppID
:
"wx-open-config"
,
OpenAppSecret
:
"wx-open-secret"
,
MPAppID
:
"wx-mp-config"
,
MPAppSecret
:
"wx-mp-secret"
,
FrontendRedirectURL
:
"/auth/wechat/config-callback"
,
},
})
got
,
err
:=
svc
.
GetWeChatConnectOAuthConfig
(
context
.
Background
())
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
got
.
Enabled
)
require
.
True
(
t
,
got
.
OpenEnabled
)
require
.
True
(
t
,
got
.
MPEnabled
)
require
.
Equal
(
t
,
"wx-open-config"
,
got
.
AppIDForMode
(
"open"
))
require
.
Equal
(
t
,
"wx-open-secret"
,
got
.
AppSecretForMode
(
"open"
))
require
.
Equal
(
t
,
"wx-mp-config"
,
got
.
AppIDForMode
(
"mp"
))
require
.
Equal
(
t
,
"wx-mp-secret"
,
got
.
AppSecretForMode
(
"mp"
))
require
.
Equal
(
t
,
"/auth/wechat/config-callback"
,
got
.
FrontendRedirectURL
)
require
.
Empty
(
t
,
got
.
RedirectURL
)
}
func
TestSettingService_ParseSettings_FallsBackToConfigForWeChatAdminView
(
t
*
testing
.
T
)
{
svc
:=
NewSettingService
(
&
settingWeChatRepoStub
{
values
:
map
[
string
]
string
{}},
&
config
.
Config
{
WeChat
:
config
.
WeChatConnectConfig
{
Enabled
:
true
,
OpenEnabled
:
true
,
Mode
:
"open"
,
OpenAppID
:
"wx-open-config"
,
OpenAppSecret
:
"wx-open-secret"
,
FrontendRedirectURL
:
"/auth/wechat/config-callback"
,
},
})
got
:=
svc
.
parseSettings
(
map
[
string
]
string
{})
require
.
True
(
t
,
got
.
WeChatConnectEnabled
)
require
.
True
(
t
,
got
.
WeChatConnectOpenEnabled
)
require
.
Equal
(
t
,
"wx-open-config"
,
got
.
WeChatConnectOpenAppID
)
require
.
True
(
t
,
got
.
WeChatConnectOpenAppSecretConfigured
)
require
.
Equal
(
t
,
"/auth/wechat/config-callback"
,
got
.
WeChatConnectFrontendRedirectURL
)
require
.
Equal
(
t
,
"open"
,
got
.
WeChatConnectMode
)
require
.
Equal
(
t
,
"snsapi_login"
,
got
.
WeChatConnectScopes
)
}
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