Commit 54dc1767 authored by IanShaw027's avatar IanShaw027
Browse files

feat(settings): support per-channel WeChat OAuth and persist payment options

parent d5819181
......@@ -753,7 +753,13 @@ func (h *AuthHandler) ensureWeChatBindOwnership(
}
for _, identity := range identities {
if identity != nil && identity.UserID != userID {
return infraerrors.Conflict("AUTH_IDENTITY_OWNERSHIP_CONFLICT", "auth identity already belongs to another user")
activeOwner, lookupErr := findActiveUserByID(ctx, client, identity.UserID)
if lookupErr != nil {
return lookupErr
}
if activeOwner != nil {
return infraerrors.Conflict("AUTH_IDENTITY_OWNERSHIP_CONFLICT", "auth identity already belongs to another user")
}
}
}
......@@ -778,7 +784,13 @@ func (h *AuthHandler) ensureWeChatBindOwnership(
}
for _, channel := range channels {
if channel != nil && channel.Edges.Identity != nil && channel.Edges.Identity.UserID != userID {
return infraerrors.Conflict("AUTH_IDENTITY_CHANNEL_OWNERSHIP_CONFLICT", "auth identity channel already belongs to another user")
activeOwner, lookupErr := findActiveUserByID(ctx, client, channel.Edges.Identity.UserID)
if lookupErr != nil {
return lookupErr
}
if activeOwner != nil {
return infraerrors.Conflict("AUTH_IDENTITY_CHANNEL_OWNERSHIP_CONFLICT", "auth identity channel already belongs to another user")
}
}
}
return nil
......@@ -960,8 +972,8 @@ func (h *AuthHandler) getWeChatOAuthConfig(ctx context.Context, rawMode string,
cfg := wechatOAuthConfig{
mode: mode,
appID: strings.TrimSpace(effective.AppID),
appSecret: strings.TrimSpace(effective.AppSecret),
appID: strings.TrimSpace(effective.AppIDForMode(mode)),
appSecret: strings.TrimSpace(effective.AppSecretForMode(mode)),
redirectURI: firstNonEmpty(strings.TrimSpace(effective.RedirectURL), resolveWeChatOAuthAbsoluteURL(apiBaseURL, c, "/api/v1/auth/oauth/wechat/callback")),
frontendCallback: firstNonEmpty(strings.TrimSpace(effective.FrontendRedirectURL), wechatOAuthDefaultFrontendCB),
scope: effective.ScopeForMode(mode),
......
......@@ -54,8 +54,15 @@ type SystemSettings struct {
WeChatConnectEnabled bool `json:"wechat_connect_enabled"`
WeChatConnectAppID string `json:"wechat_connect_app_id"`
WeChatConnectAppSecretConfigured bool `json:"wechat_connect_app_secret_configured"`
WeChatConnectOpenAppID string `json:"wechat_connect_open_app_id"`
WeChatConnectOpenAppSecretConfigured bool `json:"wechat_connect_open_app_secret_configured"`
WeChatConnectMPAppID string `json:"wechat_connect_mp_app_id"`
WeChatConnectMPAppSecretConfigured bool `json:"wechat_connect_mp_app_secret_configured"`
WeChatConnectMobileAppID string `json:"wechat_connect_mobile_app_id"`
WeChatConnectMobileAppSecretConfigured bool `json:"wechat_connect_mobile_app_secret_configured"`
WeChatConnectOpenEnabled bool `json:"wechat_connect_open_enabled"`
WeChatConnectMPEnabled bool `json:"wechat_connect_mp_enabled"`
WeChatConnectMobileEnabled bool `json:"wechat_connect_mobile_enabled"`
WeChatConnectMode string `json:"wechat_connect_mode"`
WeChatConnectScopes string `json:"wechat_connect_scopes"`
WeChatConnectRedirectURL string `json:"wechat_connect_redirect_url"`
......@@ -212,6 +219,7 @@ type PublicSettings struct {
WeChatOAuthEnabled bool `json:"wechat_oauth_enabled"`
WeChatOAuthOpenEnabled bool `json:"wechat_oauth_open_enabled"`
WeChatOAuthMPEnabled bool `json:"wechat_oauth_mp_enabled"`
WeChatOAuthMobileEnabled bool `json:"wechat_oauth_mobile_enabled"`
OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"`
OIDCOAuthProviderName string `json:"oidc_oauth_provider_name"`
SoraClientEnabled bool `json:"sora_client_enabled"`
......
......@@ -60,6 +60,7 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
WeChatOAuthEnabled: settings.WeChatOAuthEnabled,
WeChatOAuthOpenEnabled: settings.WeChatOAuthOpenEnabled,
WeChatOAuthMPEnabled: settings.WeChatOAuthMPEnabled,
WeChatOAuthMobileEnabled: settings.WeChatOAuthMobileEnabled,
OIDCOAuthEnabled: settings.OIDCOAuthEnabled,
OIDCOAuthProviderName: settings.OIDCOAuthProviderName,
BackendModeEnabled: settings.BackendModeEnabled,
......
......@@ -115,8 +115,15 @@ const (
SettingKeyWeChatConnectEnabled = "wechat_connect_enabled"
SettingKeyWeChatConnectAppID = "wechat_connect_app_id"
SettingKeyWeChatConnectAppSecret = "wechat_connect_app_secret"
SettingKeyWeChatConnectOpenAppID = "wechat_connect_open_app_id"
SettingKeyWeChatConnectOpenAppSecret = "wechat_connect_open_app_secret"
SettingKeyWeChatConnectMPAppID = "wechat_connect_mp_app_id"
SettingKeyWeChatConnectMPAppSecret = "wechat_connect_mp_app_secret"
SettingKeyWeChatConnectMobileAppID = "wechat_connect_mobile_app_id"
SettingKeyWeChatConnectMobileAppSecret = "wechat_connect_mobile_app_secret"
SettingKeyWeChatConnectOpenEnabled = "wechat_connect_open_enabled"
SettingKeyWeChatConnectMPEnabled = "wechat_connect_mp_enabled"
SettingKeyWeChatConnectMobileEnabled = "wechat_connect_mobile_enabled"
SettingKeyWeChatConnectMode = "wechat_connect_mode"
SettingKeyWeChatConnectScopes = "wechat_connect_scopes"
SettingKeyWeChatConnectRedirectURL = "wechat_connect_redirect_url"
......
......@@ -519,13 +519,15 @@ func (s *PaymentService) getWeChatPaymentOAuthCredential(ctx context.Context) (s
)
}
cfg, err := (&SettingService{settingRepo: s.configService.settingRepo}).GetWeChatConnectOAuthConfig(ctx)
if err != nil || !cfg.SupportsMode("mp") || strings.TrimSpace(cfg.AppID) == "" || strings.TrimSpace(cfg.AppSecret) == "" {
appID := strings.TrimSpace(cfg.AppIDForMode("mp"))
appSecret := strings.TrimSpace(cfg.AppSecretForMode("mp"))
if err != nil || !cfg.SupportsMode("mp") || appID == "" || appSecret == "" {
return "", "", infraerrors.ServiceUnavailable(
"WECHAT_PAYMENT_MP_NOT_CONFIGURED",
"wechat in-app payment requires a complete WeChat MP OAuth credential",
)
}
return strings.TrimSpace(cfg.AppID), strings.TrimSpace(cfg.AppSecret), nil
return appID, appSecret, nil
}
func classifyCreatePaymentError(req CreateOrderRequest, providerKey string, err error) error {
......
......@@ -181,14 +181,19 @@ func normalizeWeChatConnectModeSetting(raw string) string {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "mp":
return "mp"
case "mobile":
return "mobile"
default:
return "open"
}
}
func defaultWeChatConnectScopeForMode(mode string) string {
if normalizeWeChatConnectModeSetting(mode) == "mp" {
switch normalizeWeChatConnectModeSetting(mode) {
case "mp":
return "snsapi_userinfo"
case "mobile":
return ""
}
return defaultWeChatConnectScopes
}
......@@ -204,37 +209,47 @@ func normalizeWeChatConnectScopeSetting(raw, mode string) string {
default:
return defaultWeChatConnectScopeForMode(mode)
}
case "mobile":
return ""
default:
return defaultWeChatConnectScopes
}
}
func parseWeChatConnectCapabilitySettings(settings map[string]string, enabled bool, mode string) (bool, bool) {
func parseWeChatConnectCapabilitySettings(settings map[string]string, enabled bool, mode string) (bool, bool, bool) {
mode = normalizeWeChatConnectModeSetting(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 {
if openConfigured || mpConfigured || mobileConfigured {
openEnabled := strings.TrimSpace(rawOpen) == "true"
mpEnabled := strings.TrimSpace(rawMP) == "true"
return openEnabled, mpEnabled
mobileEnabled := strings.TrimSpace(rawMobile) == "true"
return openEnabled, mpEnabled, mobileEnabled
}
if !enabled {
return false, false
return false, false, false
}
if mode == "mp" {
return false, true
return false, true, false
}
if mode == "mobile" {
return false, false, true
}
return true, false
return true, false, false
}
func normalizeWeChatConnectStoredMode(openEnabled, mpEnabled bool, mode string) string {
func normalizeWeChatConnectStoredMode(openEnabled, mpEnabled, mobileEnabled bool, mode string) string {
switch {
case mpEnabled:
return "mp"
case mobileEnabled:
return "mobile"
case openEnabled:
return "open"
default:
......@@ -310,8 +325,15 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
SettingKeyWeChatConnectEnabled,
SettingKeyWeChatConnectAppID,
SettingKeyWeChatConnectAppSecret,
SettingKeyWeChatConnectOpenAppID,
SettingKeyWeChatConnectOpenAppSecret,
SettingKeyWeChatConnectMPAppID,
SettingKeyWeChatConnectMPAppSecret,
SettingKeyWeChatConnectMobileAppID,
SettingKeyWeChatConnectMobileAppSecret,
SettingKeyWeChatConnectOpenEnabled,
SettingKeyWeChatConnectMPEnabled,
SettingKeyWeChatConnectMobileEnabled,
SettingKeyWeChatConnectMode,
SettingKeyWeChatConnectScopes,
SettingKeyWeChatConnectRedirectURL,
......@@ -350,7 +372,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
if oidcProviderName == "" {
oidcProviderName = "OIDC"
}
weChatEnabled, weChatOpenEnabled, weChatMPEnabled := s.weChatOAuthCapabilitiesFromSettings(settings)
weChatEnabled, weChatOpenEnabled, weChatMPEnabled, weChatMobileEnabled := s.weChatOAuthCapabilitiesFromSettings(settings)
// Password reset requires email verification to be enabled
emailVerifyEnabled := settings[SettingKeyEmailVerifyEnabled] == "true"
......@@ -397,6 +419,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
WeChatOAuthEnabled: weChatEnabled,
WeChatOAuthOpenEnabled: weChatOpenEnabled,
WeChatOAuthMPEnabled: weChatMPEnabled,
WeChatOAuthMobileEnabled: weChatMobileEnabled,
BackendModeEnabled: settings[SettingKeyBackendModeEnabled] == "true",
PaymentEnabled: settings[SettingPaymentEnabled] == "true",
OIDCOAuthEnabled: oidcEnabled,
......@@ -456,6 +479,7 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
WeChatOAuthEnabled bool `json:"wechat_oauth_enabled"`
WeChatOAuthOpenEnabled bool `json:"wechat_oauth_open_enabled"`
WeChatOAuthMPEnabled bool `json:"wechat_oauth_mp_enabled"`
WeChatOAuthMobileEnabled bool `json:"wechat_oauth_mobile_enabled"`
BackendModeEnabled bool `json:"backend_mode_enabled"`
PaymentEnabled bool `json:"payment_enabled"`
OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"`
......@@ -493,6 +517,7 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
WeChatOAuthEnabled: settings.WeChatOAuthEnabled,
WeChatOAuthOpenEnabled: settings.WeChatOAuthOpenEnabled,
WeChatOAuthMPEnabled: settings.WeChatOAuthMPEnabled,
WeChatOAuthMobileEnabled: settings.WeChatOAuthMobileEnabled,
BackendModeEnabled: settings.BackendModeEnabled,
PaymentEnabled: settings.PaymentEnabled,
OIDCOAuthEnabled: settings.OIDCOAuthEnabled,
......@@ -512,15 +537,22 @@ 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 := parseWeChatConnectCapabilitySettings(settings, enabled, mode)
mode = normalizeWeChatConnectStoredMode(openEnabled, mpEnabled, mode)
openEnabled, mpEnabled, mobileEnabled := parseWeChatConnectCapabilitySettings(settings, enabled, mode)
mode = normalizeWeChatConnectStoredMode(openEnabled, mpEnabled, mobileEnabled, mode)
cfg := WeChatConnectOAuthConfig{
Enabled: enabled,
AppID: strings.TrimSpace(settings[SettingKeyWeChatConnectAppID]),
AppSecret: strings.TrimSpace(settings[SettingKeyWeChatConnectAppSecret]),
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]),
......@@ -533,11 +565,29 @@ func (s *SettingService) parseWeChatConnectOAuthConfig(settings map[string]strin
if !cfg.Enabled || (!cfg.OpenEnabled && !cfg.MPEnabled) {
return WeChatConnectOAuthConfig{}, infraerrors.NotFound("OAUTH_DISABLED", "wechat oauth is disabled")
}
if cfg.AppID == "" {
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth app id not configured")
if cfg.OpenEnabled {
if cfg.AppIDForMode("open") == "" {
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth pc app id not configured")
}
if cfg.AppSecretForMode("open") == "" {
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth pc app secret not configured")
}
}
if cfg.MPEnabled {
if cfg.AppIDForMode("mp") == "" {
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth official account app id not configured")
}
if cfg.AppSecretForMode("mp") == "" {
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth official account app secret not configured")
}
}
if cfg.AppSecret == "" {
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth app secret not configured")
if cfg.MobileEnabled {
if cfg.AppIDForMode("mobile") == "" {
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth mobile app id not configured")
}
if cfg.AppSecretForMode("mobile") == "" {
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")
......@@ -554,12 +604,34 @@ func (s *SettingService) parseWeChatConnectOAuthConfig(settings map[string]strin
return cfg, nil
}
func (s *SettingService) weChatOAuthCapabilitiesFromSettings(settings map[string]string) (bool, bool, bool) {
cfg, err := s.parseWeChatConnectOAuthConfig(settings)
if err != nil {
return false, false, false
func (s *SettingService) weChatOAuthCapabilitiesFromSettings(settings map[string]string) (bool, bool, bool, bool) {
if settings[SettingKeyWeChatConnectEnabled] != "true" {
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
}
return true, cfg.OpenEnabled, cfg.MPEnabled
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 != ""
return openReady || mpReady || mobileReady, openReady, mpReady, mobileReady
}
// filterUserVisibleMenuItems filters out admin-only menu items from a raw JSON
......@@ -744,9 +816,16 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
settings.PaymentVisibleMethodWxpaySource = wxpaySource
settings.WeChatConnectAppID = strings.TrimSpace(settings.WeChatConnectAppID)
settings.WeChatConnectAppSecret = strings.TrimSpace(settings.WeChatConnectAppSecret)
settings.WeChatConnectOpenAppID = strings.TrimSpace(firstNonEmpty(settings.WeChatConnectOpenAppID, settings.WeChatConnectAppID))
settings.WeChatConnectOpenAppSecret = strings.TrimSpace(firstNonEmpty(settings.WeChatConnectOpenAppSecret, settings.WeChatConnectAppSecret))
settings.WeChatConnectMPAppID = strings.TrimSpace(firstNonEmpty(settings.WeChatConnectMPAppID, settings.WeChatConnectAppID))
settings.WeChatConnectMPAppSecret = strings.TrimSpace(firstNonEmpty(settings.WeChatConnectMPAppSecret, settings.WeChatConnectAppSecret))
settings.WeChatConnectMobileAppID = strings.TrimSpace(firstNonEmpty(settings.WeChatConnectMobileAppID, settings.WeChatConnectAppID))
settings.WeChatConnectMobileAppSecret = strings.TrimSpace(firstNonEmpty(settings.WeChatConnectMobileAppSecret, settings.WeChatConnectAppSecret))
settings.WeChatConnectMode = normalizeWeChatConnectStoredMode(
settings.WeChatConnectOpenEnabled,
settings.WeChatConnectMPEnabled,
settings.WeChatConnectMobileEnabled,
settings.WeChatConnectMode,
)
settings.WeChatConnectScopes = normalizeWeChatConnectScopeSetting(settings.WeChatConnectScopes, settings.WeChatConnectMode)
......@@ -827,8 +906,12 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
// WeChat Connect OAuth 登录
updates[SettingKeyWeChatConnectEnabled] = strconv.FormatBool(settings.WeChatConnectEnabled)
updates[SettingKeyWeChatConnectAppID] = settings.WeChatConnectAppID
updates[SettingKeyWeChatConnectOpenAppID] = settings.WeChatConnectOpenAppID
updates[SettingKeyWeChatConnectMPAppID] = settings.WeChatConnectMPAppID
updates[SettingKeyWeChatConnectMobileAppID] = settings.WeChatConnectMobileAppID
updates[SettingKeyWeChatConnectOpenEnabled] = strconv.FormatBool(settings.WeChatConnectOpenEnabled)
updates[SettingKeyWeChatConnectMPEnabled] = strconv.FormatBool(settings.WeChatConnectMPEnabled)
updates[SettingKeyWeChatConnectMobileEnabled] = strconv.FormatBool(settings.WeChatConnectMobileEnabled)
updates[SettingKeyWeChatConnectMode] = settings.WeChatConnectMode
updates[SettingKeyWeChatConnectScopes] = settings.WeChatConnectScopes
updates[SettingKeyWeChatConnectRedirectURL] = settings.WeChatConnectRedirectURL
......@@ -836,6 +919,15 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
if settings.WeChatConnectAppSecret != "" {
updates[SettingKeyWeChatConnectAppSecret] = settings.WeChatConnectAppSecret
}
if settings.WeChatConnectOpenAppSecret != "" {
updates[SettingKeyWeChatConnectOpenAppSecret] = settings.WeChatConnectOpenAppSecret
}
if settings.WeChatConnectMPAppSecret != "" {
updates[SettingKeyWeChatConnectMPAppSecret] = settings.WeChatConnectMPAppSecret
}
if settings.WeChatConnectMobileAppSecret != "" {
updates[SettingKeyWeChatConnectMobileAppSecret] = settings.WeChatConnectMobileAppSecret
}
// OEM设置
updates[SettingKeySiteName] = settings.SiteName
......@@ -1344,8 +1436,15 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeyCustomMenuItems: "[]",
SettingKeyCustomEndpoints: "[]",
SettingKeyWeChatConnectEnabled: "false",
SettingKeyWeChatConnectOpenAppID: "",
SettingKeyWeChatConnectOpenAppSecret: "",
SettingKeyWeChatConnectMPAppID: "",
SettingKeyWeChatConnectMPAppSecret: "",
SettingKeyWeChatConnectMobileAppID: "",
SettingKeyWeChatConnectMobileAppSecret: "",
SettingKeyWeChatConnectOpenEnabled: "false",
SettingKeyWeChatConnectMPEnabled: "false",
SettingKeyWeChatConnectMobileEnabled: "false",
SettingKeyWeChatConnectMode: "open",
SettingKeyWeChatConnectScopes: "snsapi_login",
SettingKeyWeChatConnectFrontendRedirectURL: defaultWeChatConnectFrontend,
......@@ -1645,7 +1744,16 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
result.WeChatConnectAppID = strings.TrimSpace(settings[SettingKeyWeChatConnectAppID])
result.WeChatConnectAppSecret = strings.TrimSpace(settings[SettingKeyWeChatConnectAppSecret])
result.WeChatConnectAppSecretConfigured = result.WeChatConnectAppSecret != ""
result.WeChatConnectOpenEnabled, result.WeChatConnectMPEnabled = parseWeChatConnectCapabilitySettings(
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],
......@@ -1653,6 +1761,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
result.WeChatConnectMode = normalizeWeChatConnectStoredMode(
result.WeChatConnectOpenEnabled,
result.WeChatConnectMPEnabled,
result.WeChatConnectMobileEnabled,
settings[SettingKeyWeChatConnectMode],
)
result.WeChatConnectScopes = normalizeWeChatConnectScopeSetting(settings[SettingKeyWeChatConnectScopes], result.WeChatConnectMode)
......@@ -2151,8 +2260,15 @@ func (s *SettingService) GetWeChatConnectOAuthConfig(ctx context.Context) (WeCha
SettingKeyWeChatConnectEnabled,
SettingKeyWeChatConnectAppID,
SettingKeyWeChatConnectAppSecret,
SettingKeyWeChatConnectOpenAppID,
SettingKeyWeChatConnectOpenAppSecret,
SettingKeyWeChatConnectMPAppID,
SettingKeyWeChatConnectMPAppSecret,
SettingKeyWeChatConnectMobileAppID,
SettingKeyWeChatConnectMobileAppSecret,
SettingKeyWeChatConnectOpenEnabled,
SettingKeyWeChatConnectMPEnabled,
SettingKeyWeChatConnectMobileEnabled,
SettingKeyWeChatConnectMode,
SettingKeyWeChatConnectScopes,
SettingKeyWeChatConnectRedirectURL,
......
package service
import "strings"
func firstNonEmpty(values ...string) string {
for _, value := range values {
if trimmed := strings.TrimSpace(value); trimmed != "" {
return trimmed
}
}
return ""
}
type SystemSettings struct {
RegistrationEnabled bool
EmailVerifyEnabled bool
......@@ -32,16 +43,26 @@ type SystemSettings struct {
LinuxDoConnectRedirectURL string
// WeChat Connect OAuth 登录
WeChatConnectEnabled bool
WeChatConnectAppID string
WeChatConnectAppSecret string
WeChatConnectAppSecretConfigured bool
WeChatConnectOpenEnabled bool
WeChatConnectMPEnabled bool
WeChatConnectMode string
WeChatConnectScopes string
WeChatConnectRedirectURL string
WeChatConnectFrontendRedirectURL string
WeChatConnectEnabled bool
WeChatConnectAppID string
WeChatConnectAppSecret string
WeChatConnectAppSecretConfigured bool
WeChatConnectOpenAppID string
WeChatConnectOpenAppSecret string
WeChatConnectOpenAppSecretConfigured bool
WeChatConnectMPAppID string
WeChatConnectMPAppSecret string
WeChatConnectMPAppSecretConfigured bool
WeChatConnectMobileAppID string
WeChatConnectMobileAppSecret string
WeChatConnectMobileAppSecretConfigured bool
WeChatConnectOpenEnabled bool
WeChatConnectMPEnabled bool
WeChatConnectMobileEnabled bool
WeChatConnectMode string
WeChatConnectScopes string
WeChatConnectRedirectURL string
WeChatConnectFrontendRedirectURL string
// Generic OIDC OAuth 登录
OIDCConnectEnabled bool
......@@ -173,15 +194,16 @@ type PublicSettings struct {
CustomMenuItems string // JSON array of custom menu items
CustomEndpoints string // JSON array of custom endpoints
LinuxDoOAuthEnabled bool
WeChatOAuthEnabled bool
WeChatOAuthOpenEnabled bool
WeChatOAuthMPEnabled bool
BackendModeEnabled bool
PaymentEnabled bool
OIDCOAuthEnabled bool
OIDCOAuthProviderName string
Version string
LinuxDoOAuthEnabled bool
WeChatOAuthEnabled bool
WeChatOAuthOpenEnabled bool
WeChatOAuthMPEnabled bool
WeChatOAuthMobileEnabled bool
BackendModeEnabled bool
PaymentEnabled bool
OIDCOAuthEnabled bool
OIDCOAuthProviderName string
Version string
BalanceLowNotifyEnabled bool
AccountQuotaNotifyEnabled bool
......@@ -191,10 +213,17 @@ type PublicSettings struct {
type WeChatConnectOAuthConfig struct {
Enabled bool
AppID string
AppSecret string
LegacyAppID string
LegacyAppSecret string
OpenAppID string
OpenAppSecret string
MPAppID string
MPAppSecret string
MobileAppID string
MobileAppSecret string
OpenEnabled bool
MPEnabled bool
MobileEnabled bool
Mode string
Scopes string
RedirectURL string
......@@ -205,18 +234,43 @@ func (cfg WeChatConnectOAuthConfig) SupportsMode(mode string) bool {
switch normalizeWeChatConnectModeSetting(mode) {
case "mp":
return cfg.MPEnabled
case "mobile":
return cfg.MobileEnabled
default:
return cfg.OpenEnabled
}
}
func (cfg WeChatConnectOAuthConfig) ScopeForMode(mode string) string {
if normalizeWeChatConnectModeSetting(mode) == "mp" {
switch normalizeWeChatConnectModeSetting(mode) {
case "mp":
return normalizeWeChatConnectScopeSetting(cfg.Scopes, "mp")
case "mobile":
return ""
}
return defaultWeChatConnectScopeForMode("open")
}
func (cfg WeChatConnectOAuthConfig) AppIDForMode(mode string) string {
switch normalizeWeChatConnectModeSetting(mode) {
case "mp":
return strings.TrimSpace(firstNonEmpty(cfg.MPAppID, cfg.LegacyAppID))
case "mobile":
return strings.TrimSpace(firstNonEmpty(cfg.MobileAppID, cfg.LegacyAppID))
}
return strings.TrimSpace(firstNonEmpty(cfg.OpenAppID, cfg.LegacyAppID))
}
func (cfg WeChatConnectOAuthConfig) AppSecretForMode(mode string) string {
switch normalizeWeChatConnectModeSetting(mode) {
case "mp":
return strings.TrimSpace(firstNonEmpty(cfg.MPAppSecret, cfg.LegacyAppSecret))
case "mobile":
return strings.TrimSpace(firstNonEmpty(cfg.MobileAppSecret, cfg.LegacyAppSecret))
}
return strings.TrimSpace(firstNonEmpty(cfg.OpenAppSecret, cfg.LegacyAppSecret))
}
// StreamTimeoutSettings 流超时处理配置(仅控制超时后的处理方式,超时判定由网关配置控制)
type StreamTimeoutSettings struct {
// Enabled 是否启用流超时处理
......
......@@ -32,7 +32,7 @@ export type PaymentVisibleMethodSource =
| "easypay_alipay"
| "official_wxpay"
| "easypay_wxpay";
export type WeChatConnectMode = "open" | "mp";
export type WeChatConnectMode = "open" | "mp" | "mobile";
export interface PaymentVisibleMethodSourceOption {
value: PaymentVisibleMethodSource;
......@@ -108,11 +108,16 @@ const PAYMENT_VISIBLE_METHOD_SOURCE_ALIASES: Record<
},
};
const WECHAT_CONNECT_MODE_OPTIONS: WeChatConnectModeOption[] = [
{ value: "open", labelZh: "微信开放平台", labelEn: "WeChat Open Platform" },
{ value: "open", labelZh: "PC 应用", labelEn: "PC App" },
{
value: "mp",
labelZh: "微信公众号 / 小程序",
labelEn: "WeChat Official Account / Mini Program",
labelZh: "公众号",
labelEn: "Official Account",
},
{
value: "mobile",
labelZh: "移动应用",
labelEn: "Mobile App",
},
];
const WECHAT_CONNECT_MODE_ALIASES: Record<string, WeChatConnectMode> = {
......@@ -124,6 +129,9 @@ const WECHAT_CONNECT_MODE_ALIASES: Record<string, WeChatConnectMode> = {
official_account: "mp",
wechat_mp: "mp",
mini_program: "mp",
mobile: "mobile",
mobile_app: "mobile",
native_app: "mobile",
};
export function normalizeDefaultSubscriptionSettings(
......@@ -234,34 +242,52 @@ export function normalizeWeChatConnectMode(source: unknown): WeChatConnectMode {
}
export function defaultWeChatConnectScopesForMode(mode: unknown): string {
return normalizeWeChatConnectMode(mode) === "mp"
? "snsapi_userinfo"
: "snsapi_login";
switch (normalizeWeChatConnectMode(mode)) {
case "mp":
return "snsapi_userinfo";
case "mobile":
return "";
default:
return "snsapi_login";
}
}
export function resolveWeChatConnectModeCapabilities(
openEnabled: unknown,
mpEnabled: unknown,
mobileEnabled: unknown,
legacyMode: unknown,
): { openEnabled: boolean; mpEnabled: boolean } {
if (typeof openEnabled === "boolean" || typeof mpEnabled === "boolean") {
): { openEnabled: boolean; mpEnabled: boolean; mobileEnabled: boolean } {
if (
typeof openEnabled === "boolean" ||
typeof mpEnabled === "boolean" ||
typeof mobileEnabled === "boolean"
) {
return {
openEnabled: openEnabled === true,
mpEnabled: mpEnabled === true,
mobileEnabled: mobileEnabled === true,
};
}
return normalizeWeChatConnectMode(legacyMode) === "mp"
? { openEnabled: false, mpEnabled: true }
: { openEnabled: true, mpEnabled: false };
switch (normalizeWeChatConnectMode(legacyMode)) {
case "mp":
return { openEnabled: false, mpEnabled: true, mobileEnabled: false };
case "mobile":
return { openEnabled: false, mpEnabled: false, mobileEnabled: true };
default:
return { openEnabled: true, mpEnabled: false, mobileEnabled: false };
}
}
export function deriveWeChatConnectStoredMode(
openEnabled: boolean,
mpEnabled: boolean,
mobileEnabled: boolean,
legacyMode: unknown,
): WeChatConnectMode {
if (mpEnabled) return "mp";
if (mobileEnabled) return "mobile";
if (openEnabled) return "open";
return normalizeWeChatConnectMode(legacyMode);
}
......@@ -342,8 +368,15 @@ export interface SystemSettings {
wechat_connect_enabled: boolean;
wechat_connect_app_id: string;
wechat_connect_app_secret_configured: boolean;
wechat_connect_open_app_id?: string;
wechat_connect_open_app_secret_configured?: boolean;
wechat_connect_mp_app_id?: string;
wechat_connect_mp_app_secret_configured?: boolean;
wechat_connect_mobile_app_id?: string;
wechat_connect_mobile_app_secret_configured?: boolean;
wechat_connect_open_enabled?: boolean;
wechat_connect_mp_enabled?: boolean;
wechat_connect_mobile_enabled?: boolean;
wechat_connect_mode: string;
wechat_connect_scopes: string;
wechat_connect_redirect_url: string;
......@@ -501,8 +534,15 @@ export interface UpdateSettingsRequest {
wechat_connect_enabled?: boolean;
wechat_connect_app_id?: string;
wechat_connect_app_secret?: string;
wechat_connect_open_app_id?: string;
wechat_connect_open_app_secret?: string;
wechat_connect_mp_app_id?: string;
wechat_connect_mp_app_secret?: string;
wechat_connect_mobile_app_id?: string;
wechat_connect_mobile_app_secret?: string;
wechat_connect_open_enabled?: boolean;
wechat_connect_mp_enabled?: boolean;
wechat_connect_mobile_enabled?: boolean;
wechat_connect_mode?: string;
wechat_connect_scopes?: string;
wechat_connect_redirect_url?: string;
......
......@@ -57,6 +57,11 @@ const disabledHint = computed(() => {
return t('auth.oauthFlow.wechatSystemBrowserOnly')
case 'wechat_browser_required':
return t('auth.oauthFlow.wechatBrowserOnly')
case 'native_app_required':
return localizeWeChatHint(
'当前仅配置微信移动应用登录,需要在原生 App 中通过微信 SDK 发起授权。',
'This site only has WeChat mobile app login configured. Continue from the native app through the WeChat SDK.',
)
case 'not_configured':
return t('auth.oauthFlow.wechatNotConfigured')
default:
......
......@@ -344,6 +344,7 @@ export const useAppStore = defineStore('app', () => {
wechat_oauth_enabled: false,
wechat_oauth_open_enabled: false,
wechat_oauth_mp_enabled: false,
wechat_oauth_mobile_enabled: false,
oidc_oauth_enabled: false,
oidc_oauth_provider_name: 'OIDC',
backend_mode_enabled: false,
......
......@@ -168,6 +168,7 @@ export interface PublicSettings {
wechat_oauth_enabled: boolean
wechat_oauth_open_enabled?: boolean
wechat_oauth_mp_enabled?: boolean
wechat_oauth_mobile_enabled?: boolean
oidc_oauth_enabled: boolean
oidc_oauth_provider_name: string
backend_mode_enabled: boolean
......
This diff is collapsed.
......@@ -184,7 +184,7 @@ import TotpLoginModal from '@/components/auth/TotpLoginModal.vue'
import Icon from '@/components/icons/Icon.vue'
import TurnstileWidget from '@/components/TurnstileWidget.vue'
import { useAuthStore, useAppStore } from '@/stores'
import { getPublicSettings, isTotp2FARequired } from '@/api/auth'
import { getPublicSettings, isTotp2FARequired, isWeChatWebOAuthEnabled } from '@/api/auth'
import type { TotpLoginResponse } from '@/types'
const { t } = useI18n()
......@@ -258,7 +258,7 @@ onMounted(async () => {
turnstileEnabled.value = settings.turnstile_enabled
turnstileSiteKey.value = settings.turnstile_site_key || ''
linuxdoOAuthEnabled.value = settings.linuxdo_oauth_enabled
wechatOAuthEnabled.value = settings.wechat_oauth_enabled
wechatOAuthEnabled.value = isWeChatWebOAuthEnabled(settings)
backendModeEnabled.value = settings.backend_mode_enabled
oidcOAuthEnabled.value = settings.oidc_oauth_enabled
oidcOAuthProviderName.value = settings.oidc_oauth_provider_name || 'OIDC'
......
......@@ -282,7 +282,12 @@ import WechatOAuthSection from '@/components/auth/WechatOAuthSection.vue'
import Icon from '@/components/icons/Icon.vue'
import TurnstileWidget from '@/components/TurnstileWidget.vue'
import { useAuthStore, useAppStore } from '@/stores'
import { getPublicSettings, validatePromoCode, validateInvitationCode } from '@/api/auth'
import {
getPublicSettings,
isWeChatWebOAuthEnabled,
validatePromoCode,
validateInvitationCode
} from '@/api/auth'
import { buildAuthErrorMessage } from '@/utils/authError'
import {
isRegistrationEmailSuffixAllowed,
......@@ -385,7 +390,7 @@ onMounted(async () => {
turnstileSiteKey.value = settings.turnstile_site_key || ''
siteName.value = settings.site_name || 'Sub2API'
linuxdoOAuthEnabled.value = settings.linuxdo_oauth_enabled
wechatOAuthEnabled.value = settings.wechat_oauth_enabled
wechatOAuthEnabled.value = isWeChatWebOAuthEnabled(settings)
oidcOAuthEnabled.value = settings.oidc_oauth_enabled
oidcOAuthProviderName.value = settings.oidc_oauth_provider_name || 'OIDC'
registrationEmailSuffixWhitelist.value = normalizeRegistrationEmailSuffixWhitelist(
......
......@@ -504,6 +504,8 @@ function resolveWeChatOAuthUnavailableMessage(): string {
return t('auth.oauthFlow.wechatSystemBrowserOnly')
case 'wechat_browser_required':
return t('auth.oauthFlow.wechatBrowserOnly')
case 'native_app_required':
return 'This WeChat sign-in flow is only available from the native mobile app.'
case 'not_configured':
return t('auth.oauthFlow.wechatNotConfigured')
default:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment