@@ -16,6 +16,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/imroc/req/v3"
"golang.org/x/sync/singleflight"
)
@@ -164,6 +165,8 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
SettingKeyCustomEndpoints ,
SettingKeyLinuxDoConnectEnabled ,
SettingKeyBackendModeEnabled ,
SettingKeyOIDCConnectEnabled ,
SettingKeyOIDCConnectProviderName ,
}
settings , err := s . settingRepo . GetMultiple ( ctx , keys )
@@ -177,6 +180,19 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
} else {
linuxDoEnabled = s . cfg != nil && s . cfg . LinuxDo . Enabled
}
oidcEnabled := false
if raw , ok := settings [ SettingKeyOIDCConnectEnabled ] ; ok {
oidcEnabled = raw == "true"
} else {
oidcEnabled = s . cfg != nil && s . cfg . OIDC . Enabled
}
oidcProviderName := strings . TrimSpace ( settings [ SettingKeyOIDCConnectProviderName ] )
if oidcProviderName == "" && s . cfg != nil {
oidcProviderName = strings . TrimSpace ( s . cfg . OIDC . ProviderName )
}
if oidcProviderName == "" {
oidcProviderName = "OIDC"
}
// Password reset requires email verification to be enabled
emailVerifyEnabled := settings [ SettingKeyEmailVerifyEnabled ] == "true"
@@ -209,6 +225,8 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
CustomEndpoints : settings [ SettingKeyCustomEndpoints ] ,
LinuxDoOAuthEnabled : linuxDoEnabled ,
BackendModeEnabled : settings [ SettingKeyBackendModeEnabled ] == "true" ,
OIDCOAuthEnabled : oidcEnabled ,
OIDCOAuthProviderName : oidcProviderName ,
} , nil
}
@@ -256,6 +274,8 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
CustomEndpoints json . RawMessage ` json:"custom_endpoints" `
LinuxDoOAuthEnabled bool ` json:"linuxdo_oauth_enabled" `
BackendModeEnabled bool ` json:"backend_mode_enabled" `
OIDCOAuthEnabled bool ` json:"oidc_oauth_enabled" `
OIDCOAuthProviderName string ` json:"oidc_oauth_provider_name" `
Version string ` json:"version,omitempty" `
} {
RegistrationEnabled : settings . RegistrationEnabled ,
@@ -281,6 +301,8 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
CustomEndpoints : safeRawJSONArray ( settings . CustomEndpoints ) ,
LinuxDoOAuthEnabled : settings . LinuxDoOAuthEnabled ,
BackendModeEnabled : settings . BackendModeEnabled ,
OIDCOAuthEnabled : settings . OIDCOAuthEnabled ,
OIDCOAuthProviderName : settings . OIDCOAuthProviderName ,
Version : s . version ,
} , nil
}
@@ -460,6 +482,32 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
updates [ SettingKeyLinuxDoConnectClientSecret ] = settings . LinuxDoConnectClientSecret
}
// Generic OIDC OAuth 登录
updates [ SettingKeyOIDCConnectEnabled ] = strconv . FormatBool ( settings . OIDCConnectEnabled )
updates [ SettingKeyOIDCConnectProviderName ] = settings . OIDCConnectProviderName
updates [ SettingKeyOIDCConnectClientID ] = settings . OIDCConnectClientID
updates [ SettingKeyOIDCConnectIssuerURL ] = settings . OIDCConnectIssuerURL
updates [ SettingKeyOIDCConnectDiscoveryURL ] = settings . OIDCConnectDiscoveryURL
updates [ SettingKeyOIDCConnectAuthorizeURL ] = settings . OIDCConnectAuthorizeURL
updates [ SettingKeyOIDCConnectTokenURL ] = settings . OIDCConnectTokenURL
updates [ SettingKeyOIDCConnectUserInfoURL ] = settings . OIDCConnectUserInfoURL
updates [ SettingKeyOIDCConnectJWKSURL ] = settings . OIDCConnectJWKSURL
updates [ SettingKeyOIDCConnectScopes ] = settings . OIDCConnectScopes
updates [ SettingKeyOIDCConnectRedirectURL ] = settings . OIDCConnectRedirectURL
updates [ SettingKeyOIDCConnectFrontendRedirectURL ] = settings . OIDCConnectFrontendRedirectURL
updates [ SettingKeyOIDCConnectTokenAuthMethod ] = settings . OIDCConnectTokenAuthMethod
updates [ SettingKeyOIDCConnectUsePKCE ] = strconv . FormatBool ( settings . OIDCConnectUsePKCE )
updates [ SettingKeyOIDCConnectValidateIDToken ] = strconv . FormatBool ( settings . OIDCConnectValidateIDToken )
updates [ SettingKeyOIDCConnectAllowedSigningAlgs ] = settings . OIDCConnectAllowedSigningAlgs
updates [ SettingKeyOIDCConnectClockSkewSeconds ] = strconv . Itoa ( settings . OIDCConnectClockSkewSeconds )
updates [ SettingKeyOIDCConnectRequireEmailVerified ] = strconv . FormatBool ( settings . OIDCConnectRequireEmailVerified )
updates [ SettingKeyOIDCConnectUserInfoEmailPath ] = settings . OIDCConnectUserInfoEmailPath
updates [ SettingKeyOIDCConnectUserInfoIDPath ] = settings . OIDCConnectUserInfoIDPath
updates [ SettingKeyOIDCConnectUserInfoUsernamePath ] = settings . OIDCConnectUserInfoUsernamePath
if settings . OIDCConnectClientSecret != "" {
updates [ SettingKeyOIDCConnectClientSecret ] = settings . OIDCConnectClientSecret
}
// OEM设置
updates [ SettingKeySiteName ] = settings . SiteName
updates [ SettingKeySiteLogo ] = settings . SiteLogo
@@ -826,6 +874,8 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeyPurchaseSubscriptionURL : "" ,
SettingKeyCustomMenuItems : "[]" ,
SettingKeyCustomEndpoints : "[]" ,
SettingKeyOIDCConnectEnabled : "false" ,
SettingKeyOIDCConnectProviderName : "OIDC" ,
SettingKeyDefaultConcurrency : strconv . Itoa ( s . cfg . Default . UserConcurrency ) ,
SettingKeyDefaultBalance : strconv . FormatFloat ( s . cfg . Default . UserBalance , 'f' , 8 , 64 ) ,
SettingKeyDefaultSubscriptions : "[]" ,
@@ -951,6 +1001,138 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
}
result . LinuxDoConnectClientSecretConfigured = result . LinuxDoConnectClientSecret != ""
// Generic OIDC 设置:
// - 兼容 config.yaml/env
// - 支持后台系统设置覆盖并持久化(存储于 DB)
oidcBase := config . OIDCConnectConfig { }
if s . cfg != nil {
oidcBase = s . cfg . OIDC
}
if raw , ok := settings [ SettingKeyOIDCConnectEnabled ] ; ok {
result . OIDCConnectEnabled = raw == "true"
} else {
result . OIDCConnectEnabled = oidcBase . Enabled
}
if v , ok := settings [ SettingKeyOIDCConnectProviderName ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectProviderName = strings . TrimSpace ( v )
} else {
result . OIDCConnectProviderName = strings . TrimSpace ( oidcBase . ProviderName )
}
if result . OIDCConnectProviderName == "" {
result . OIDCConnectProviderName = "OIDC"
}
if v , ok := settings [ SettingKeyOIDCConnectClientID ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectClientID = strings . TrimSpace ( v )
} else {
result . OIDCConnectClientID = strings . TrimSpace ( oidcBase . ClientID )
}
if v , ok := settings [ SettingKeyOIDCConnectIssuerURL ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectIssuerURL = strings . TrimSpace ( v )
} else {
result . OIDCConnectIssuerURL = strings . TrimSpace ( oidcBase . IssuerURL )
}
if v , ok := settings [ SettingKeyOIDCConnectDiscoveryURL ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectDiscoveryURL = strings . TrimSpace ( v )
} else {
result . OIDCConnectDiscoveryURL = strings . TrimSpace ( oidcBase . DiscoveryURL )
}
if v , ok := settings [ SettingKeyOIDCConnectAuthorizeURL ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectAuthorizeURL = strings . TrimSpace ( v )
} else {
result . OIDCConnectAuthorizeURL = strings . TrimSpace ( oidcBase . AuthorizeURL )
}
if v , ok := settings [ SettingKeyOIDCConnectTokenURL ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectTokenURL = strings . TrimSpace ( v )
} else {
result . OIDCConnectTokenURL = strings . TrimSpace ( oidcBase . TokenURL )
}
if v , ok := settings [ SettingKeyOIDCConnectUserInfoURL ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectUserInfoURL = strings . TrimSpace ( v )
} else {
result . OIDCConnectUserInfoURL = strings . TrimSpace ( oidcBase . UserInfoURL )
}
if v , ok := settings [ SettingKeyOIDCConnectJWKSURL ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectJWKSURL = strings . TrimSpace ( v )
} else {
result . OIDCConnectJWKSURL = strings . TrimSpace ( oidcBase . JWKSURL )
}
if v , ok := settings [ SettingKeyOIDCConnectScopes ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectScopes = strings . TrimSpace ( v )
} else {
result . OIDCConnectScopes = strings . TrimSpace ( oidcBase . Scopes )
}
if v , ok := settings [ SettingKeyOIDCConnectRedirectURL ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectRedirectURL = strings . TrimSpace ( v )
} else {
result . OIDCConnectRedirectURL = strings . TrimSpace ( oidcBase . RedirectURL )
}
if v , ok := settings [ SettingKeyOIDCConnectFrontendRedirectURL ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectFrontendRedirectURL = strings . TrimSpace ( v )
} else {
result . OIDCConnectFrontendRedirectURL = strings . TrimSpace ( oidcBase . FrontendRedirectURL )
}
if v , ok := settings [ SettingKeyOIDCConnectTokenAuthMethod ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectTokenAuthMethod = strings . ToLower ( strings . TrimSpace ( v ) )
} else {
result . OIDCConnectTokenAuthMethod = strings . ToLower ( strings . TrimSpace ( oidcBase . TokenAuthMethod ) )
}
if raw , ok := settings [ SettingKeyOIDCConnectUsePKCE ] ; ok {
result . OIDCConnectUsePKCE = raw == "true"
} else {
result . OIDCConnectUsePKCE = oidcBase . UsePKCE
}
if raw , ok := settings [ SettingKeyOIDCConnectValidateIDToken ] ; ok {
result . OIDCConnectValidateIDToken = raw == "true"
} else {
result . OIDCConnectValidateIDToken = oidcBase . ValidateIDToken
}
if v , ok := settings [ SettingKeyOIDCConnectAllowedSigningAlgs ] ; ok && strings . TrimSpace ( v ) != "" {
result . OIDCConnectAllowedSigningAlgs = strings . TrimSpace ( v )
} else {
result . OIDCConnectAllowedSigningAlgs = strings . TrimSpace ( oidcBase . AllowedSigningAlgs )
}
clockSkewSet := false
if raw , ok := settings [ SettingKeyOIDCConnectClockSkewSeconds ] ; ok && strings . TrimSpace ( raw ) != "" {
if parsed , err := strconv . Atoi ( strings . TrimSpace ( raw ) ) ; err == nil {
result . OIDCConnectClockSkewSeconds = parsed
clockSkewSet = true
}
}
if ! clockSkewSet {
result . OIDCConnectClockSkewSeconds = oidcBase . ClockSkewSeconds
}
if ! clockSkewSet && result . OIDCConnectClockSkewSeconds == 0 {
result . OIDCConnectClockSkewSeconds = 120
}
if raw , ok := settings [ SettingKeyOIDCConnectRequireEmailVerified ] ; ok {
result . OIDCConnectRequireEmailVerified = raw == "true"
} else {
result . OIDCConnectRequireEmailVerified = oidcBase . RequireEmailVerified
}
if v , ok := settings [ SettingKeyOIDCConnectUserInfoEmailPath ] ; ok {
result . OIDCConnectUserInfoEmailPath = strings . TrimSpace ( v )
} else {
result . OIDCConnectUserInfoEmailPath = strings . TrimSpace ( oidcBase . UserInfoEmailPath )
}
if v , ok := settings [ SettingKeyOIDCConnectUserInfoIDPath ] ; ok {
result . OIDCConnectUserInfoIDPath = strings . TrimSpace ( v )
} else {
result . OIDCConnectUserInfoIDPath = strings . TrimSpace ( oidcBase . UserInfoIDPath )
}
if v , ok := settings [ SettingKeyOIDCConnectUserInfoUsernamePath ] ; ok {
result . OIDCConnectUserInfoUsernamePath = strings . TrimSpace ( v )
} else {
result . OIDCConnectUserInfoUsernamePath = strings . TrimSpace ( oidcBase . UserInfoUsernamePath )
}
result . OIDCConnectClientSecret = strings . TrimSpace ( settings [ SettingKeyOIDCConnectClientSecret ] )
if result . OIDCConnectClientSecret == "" {
result . OIDCConnectClientSecret = strings . TrimSpace ( oidcBase . ClientSecret )
}
result . OIDCConnectClientSecretConfigured = result . OIDCConnectClientSecret != ""
// Model fallback settings
result . EnableModelFallback = settings [ SettingKeyEnableModelFallback ] == "true"
result . FallbackModelAnthropic = s . getStringOrDefault ( settings , SettingKeyFallbackModelAnthropic , "claude-3-5-sonnet-20241022" )
@@ -1323,6 +1505,282 @@ func (s *SettingService) SetOverloadCooldownSettings(ctx context.Context, settin
return s . settingRepo . Set ( ctx , SettingKeyOverloadCooldownSettings , string ( data ) )
}
// GetOIDCConnectOAuthConfig 返回用于登录的“最终生效” OIDC 配置。
//
// 优先级:
// - 若对应系统设置键存在,则覆盖 config.yaml/env 的值
// - 否则回退到 config.yaml/env 的值
func ( s * SettingService ) GetOIDCConnectOAuthConfig ( ctx context . Context ) ( config . OIDCConnectConfig , error ) {
if s == nil || s . cfg == nil {
return config . OIDCConnectConfig { } , infraerrors . ServiceUnavailable ( "CONFIG_NOT_READY" , "config not loaded" )
}
effective := s . cfg . OIDC
keys := [ ] string {
SettingKeyOIDCConnectEnabled ,
SettingKeyOIDCConnectProviderName ,
SettingKeyOIDCConnectClientID ,
SettingKeyOIDCConnectClientSecret ,
SettingKeyOIDCConnectIssuerURL ,
SettingKeyOIDCConnectDiscoveryURL ,
SettingKeyOIDCConnectAuthorizeURL ,
SettingKeyOIDCConnectTokenURL ,
SettingKeyOIDCConnectUserInfoURL ,
SettingKeyOIDCConnectJWKSURL ,
SettingKeyOIDCConnectScopes ,
SettingKeyOIDCConnectRedirectURL ,
SettingKeyOIDCConnectFrontendRedirectURL ,
SettingKeyOIDCConnectTokenAuthMethod ,
SettingKeyOIDCConnectUsePKCE ,
SettingKeyOIDCConnectValidateIDToken ,
SettingKeyOIDCConnectAllowedSigningAlgs ,
SettingKeyOIDCConnectClockSkewSeconds ,
SettingKeyOIDCConnectRequireEmailVerified ,
SettingKeyOIDCConnectUserInfoEmailPath ,
SettingKeyOIDCConnectUserInfoIDPath ,
SettingKeyOIDCConnectUserInfoUsernamePath ,
}
settings , err := s . settingRepo . GetMultiple ( ctx , keys )
if err != nil {
return config . OIDCConnectConfig { } , fmt . Errorf ( "get oidc connect settings: %w" , err )
}
if raw , ok := settings [ SettingKeyOIDCConnectEnabled ] ; ok {
effective . Enabled = raw == "true"
}
if v , ok := settings [ SettingKeyOIDCConnectProviderName ] ; ok && strings . TrimSpace ( v ) != "" {
effective . ProviderName = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectClientID ] ; ok && strings . TrimSpace ( v ) != "" {
effective . ClientID = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectClientSecret ] ; ok && strings . TrimSpace ( v ) != "" {
effective . ClientSecret = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectIssuerURL ] ; ok && strings . TrimSpace ( v ) != "" {
effective . IssuerURL = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectDiscoveryURL ] ; ok && strings . TrimSpace ( v ) != "" {
effective . DiscoveryURL = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectAuthorizeURL ] ; ok && strings . TrimSpace ( v ) != "" {
effective . AuthorizeURL = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectTokenURL ] ; ok && strings . TrimSpace ( v ) != "" {
effective . TokenURL = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectUserInfoURL ] ; ok && strings . TrimSpace ( v ) != "" {
effective . UserInfoURL = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectJWKSURL ] ; ok && strings . TrimSpace ( v ) != "" {
effective . JWKSURL = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectScopes ] ; ok && strings . TrimSpace ( v ) != "" {
effective . Scopes = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectRedirectURL ] ; ok && strings . TrimSpace ( v ) != "" {
effective . RedirectURL = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectFrontendRedirectURL ] ; ok && strings . TrimSpace ( v ) != "" {
effective . FrontendRedirectURL = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectTokenAuthMethod ] ; ok && strings . TrimSpace ( v ) != "" {
effective . TokenAuthMethod = strings . ToLower ( strings . TrimSpace ( v ) )
}
if raw , ok := settings [ SettingKeyOIDCConnectUsePKCE ] ; ok {
effective . UsePKCE = raw == "true"
}
if raw , ok := settings [ SettingKeyOIDCConnectValidateIDToken ] ; ok {
effective . ValidateIDToken = raw == "true"
}
if v , ok := settings [ SettingKeyOIDCConnectAllowedSigningAlgs ] ; ok && strings . TrimSpace ( v ) != "" {
effective . AllowedSigningAlgs = strings . TrimSpace ( v )
}
if raw , ok := settings [ SettingKeyOIDCConnectClockSkewSeconds ] ; ok && strings . TrimSpace ( raw ) != "" {
if parsed , parseErr := strconv . Atoi ( strings . TrimSpace ( raw ) ) ; parseErr == nil {
effective . ClockSkewSeconds = parsed
}
}
if raw , ok := settings [ SettingKeyOIDCConnectRequireEmailVerified ] ; ok {
effective . RequireEmailVerified = raw == "true"
}
if v , ok := settings [ SettingKeyOIDCConnectUserInfoEmailPath ] ; ok {
effective . UserInfoEmailPath = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectUserInfoIDPath ] ; ok {
effective . UserInfoIDPath = strings . TrimSpace ( v )
}
if v , ok := settings [ SettingKeyOIDCConnectUserInfoUsernamePath ] ; ok {
effective . UserInfoUsernamePath = strings . TrimSpace ( v )
}
if ! effective . Enabled {
return config . OIDCConnectConfig { } , infraerrors . NotFound ( "OAUTH_DISABLED" , "oauth login is disabled" )
}
if strings . TrimSpace ( effective . ProviderName ) == "" {
effective . ProviderName = "OIDC"
}
if strings . TrimSpace ( effective . ClientID ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth client id not configured" )
}
if strings . TrimSpace ( effective . IssuerURL ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth issuer url not configured" )
}
if strings . TrimSpace ( effective . RedirectURL ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth redirect url not configured" )
}
if strings . TrimSpace ( effective . FrontendRedirectURL ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth frontend redirect url not configured" )
}
if ! scopesContainOpenID ( effective . Scopes ) {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth scopes must contain openid" )
}
if effective . ClockSkewSeconds < 0 || effective . ClockSkewSeconds > 600 {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth clock skew must be between 0 and 600" )
}
if err := config . ValidateAbsoluteHTTPURL ( effective . IssuerURL ) ; err != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth issuer url invalid" )
}
discoveryURL := strings . TrimSpace ( effective . DiscoveryURL )
if discoveryURL == "" {
discoveryURL = oidcDefaultDiscoveryURL ( effective . IssuerURL )
effective . DiscoveryURL = discoveryURL
}
if discoveryURL != "" {
if err := config . ValidateAbsoluteHTTPURL ( discoveryURL ) ; err != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth discovery url invalid" )
}
}
needsDiscovery := strings . TrimSpace ( effective . AuthorizeURL ) == "" ||
strings . TrimSpace ( effective . TokenURL ) == "" ||
( effective . ValidateIDToken && strings . TrimSpace ( effective . JWKSURL ) == "" )
if needsDiscovery && discoveryURL != "" {
metadata , resolveErr := oidcResolveProviderMetadata ( ctx , discoveryURL )
if resolveErr != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth discovery resolve failed" ) . WithCause ( resolveErr )
}
if strings . TrimSpace ( effective . AuthorizeURL ) == "" {
effective . AuthorizeURL = strings . TrimSpace ( metadata . AuthorizationEndpoint )
}
if strings . TrimSpace ( effective . TokenURL ) == "" {
effective . TokenURL = strings . TrimSpace ( metadata . TokenEndpoint )
}
if strings . TrimSpace ( effective . UserInfoURL ) == "" {
effective . UserInfoURL = strings . TrimSpace ( metadata . UserInfoEndpoint )
}
if strings . TrimSpace ( effective . JWKSURL ) == "" {
effective . JWKSURL = strings . TrimSpace ( metadata . JWKSURI )
}
}
if strings . TrimSpace ( effective . AuthorizeURL ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth authorize url not configured" )
}
if strings . TrimSpace ( effective . TokenURL ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth token url not configured" )
}
if err := config . ValidateAbsoluteHTTPURL ( effective . AuthorizeURL ) ; err != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth authorize url invalid" )
}
if err := config . ValidateAbsoluteHTTPURL ( effective . TokenURL ) ; err != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth token url invalid" )
}
if v := strings . TrimSpace ( effective . UserInfoURL ) ; v != "" {
if err := config . ValidateAbsoluteHTTPURL ( v ) ; err != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth userinfo url invalid" )
}
}
if effective . ValidateIDToken {
if strings . TrimSpace ( effective . JWKSURL ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth jwks url not configured" )
}
if strings . TrimSpace ( effective . AllowedSigningAlgs ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth signing algs not configured" )
}
}
if v := strings . TrimSpace ( effective . JWKSURL ) ; v != "" {
if err := config . ValidateAbsoluteHTTPURL ( v ) ; err != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth jwks url invalid" )
}
}
if err := config . ValidateAbsoluteHTTPURL ( effective . RedirectURL ) ; err != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth redirect url invalid" )
}
if err := config . ValidateFrontendRedirectURL ( effective . FrontendRedirectURL ) ; err != nil {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth frontend redirect url invalid" )
}
method := strings . ToLower ( strings . TrimSpace ( effective . TokenAuthMethod ) )
switch method {
case "" , "client_secret_post" , "client_secret_basic" :
if strings . TrimSpace ( effective . ClientSecret ) == "" {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth client secret not configured" )
}
case "none" :
if ! effective . UsePKCE {
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth pkce must be enabled when token_auth_method=none" )
}
default :
return config . OIDCConnectConfig { } , infraerrors . InternalServer ( "OAUTH_CONFIG_INVALID" , "oauth token_auth_method invalid" )
}
return effective , nil
}
func scopesContainOpenID ( scopes string ) bool {
for _ , scope := range strings . Fields ( strings . ToLower ( strings . TrimSpace ( scopes ) ) ) {
if scope == "openid" {
return true
}
}
return false
}
type oidcProviderMetadata struct {
AuthorizationEndpoint string ` json:"authorization_endpoint" `
TokenEndpoint string ` json:"token_endpoint" `
UserInfoEndpoint string ` json:"userinfo_endpoint" `
JWKSURI string ` json:"jwks_uri" `
}
func oidcDefaultDiscoveryURL ( issuerURL string ) string {
issuerURL = strings . TrimSpace ( issuerURL )
if issuerURL == "" {
return ""
}
return strings . TrimRight ( issuerURL , "/" ) + "/.well-known/openid-configuration"
}
func oidcResolveProviderMetadata ( ctx context . Context , discoveryURL string ) ( * oidcProviderMetadata , error ) {
discoveryURL = strings . TrimSpace ( discoveryURL )
if discoveryURL == "" {
return nil , fmt . Errorf ( "discovery url is empty" )
}
resp , err := req . C ( ) .
SetTimeout ( 15 * time . Second ) .
R ( ) .
SetContext ( ctx ) .
SetHeader ( "Accept" , "application/json" ) .
Get ( discoveryURL )
if err != nil {
return nil , fmt . Errorf ( "request discovery document: %w" , err )
}
if ! resp . IsSuccessState ( ) {
return nil , fmt . Errorf ( "discovery request failed: status=%d" , resp . StatusCode )
}
metadata := & oidcProviderMetadata { }
if err := json . Unmarshal ( resp . Bytes ( ) , metadata ) ; err != nil {
return nil , fmt . Errorf ( "parse discovery document: %w" , err )
}
return metadata , nil
}
// GetStreamTimeoutSettings 获取流超时处理配置
func ( s * SettingService ) GetStreamTimeoutSettings ( ctx context . Context ) ( * StreamTimeoutSettings , error ) {
value , err := s . settingRepo . GetValue ( ctx , SettingKeyStreamTimeoutSettings )