package service import ( "context" "fmt" "strings" ) // OAuth provider 配置键;client secret 以 AES-GCM 密文落库。 const ( settingOauthOidcIssuer = "oauth_oidc_issuer" settingOauthOidcClientID = "oauth_oidc_client_id" settingOauthOidcClientSecret = "oauth_oidc_client_secret" settingOauthOidcDisplayName = "oauth_oidc_display_name" settingOauthOidcDisabled = "oauth_oidc_disabled" settingOauthGithubClientID = "oauth_github_client_id" settingOauthGithubClientSecret = "oauth_github_client_secret" settingOauthGithubDisplayName = "oauth_github_display_name" settingOauthGithubDisabled = "oauth_github_disabled" ) // OAuthProvidersView 是 OAuth provider 配置视图;绝不返回 secret 明文。 type OAuthProvidersView struct { OidcIssuer string `json:"oidcIssuer"` OidcClientID string `json:"oidcClientId"` OidcSecretSet bool `json:"oidcSecretSet"` OidcDisplayName string `json:"oidcDisplayName"` OidcDisabled bool `json:"oidcDisabled"` GithubClientID string `json:"githubClientId"` GithubSecretSet bool `json:"githubSecretSet"` GithubDisplayName string `json:"githubDisplayName"` GithubDisabled bool `json:"githubDisabled"` } // UpdateOAuthInput 是保存 provider 配置的输入; // secret 为 nil 沿用已存值,非 nil 覆盖(空串清除)。 type UpdateOAuthInput struct { OidcIssuer string `json:"oidcIssuer"` OidcClientID string `json:"oidcClientId"` OidcClientSecret *string `json:"oidcClientSecret"` OidcDisplayName string `json:"oidcDisplayName"` OidcDisabled bool `json:"oidcDisabled"` GithubClientID string `json:"githubClientId"` GithubClientSecret *string `json:"githubClientSecret"` GithubDisplayName string `json:"githubDisplayName"` GithubDisabled bool `json:"githubDisabled"` } // OAuthView 返回脱敏后的 provider 配置。 func (s *SettingService) OAuthView(ctx context.Context) (OAuthProvidersView, error) { var view OAuthProvidersView vals, err := s.getMany(ctx, settingOauthOidcIssuer, settingOauthOidcClientID, settingOauthOidcClientSecret, settingOauthOidcDisplayName, settingOauthOidcDisabled, settingOauthGithubClientID, settingOauthGithubClientSecret, settingOauthGithubDisplayName, settingOauthGithubDisabled) if err != nil { return view, err } view.OidcIssuer = vals[settingOauthOidcIssuer] view.OidcClientID = vals[settingOauthOidcClientID] view.OidcSecretSet = vals[settingOauthOidcClientSecret] != "" view.OidcDisplayName = vals[settingOauthOidcDisplayName] view.OidcDisabled = vals[settingOauthOidcDisabled] == "1" view.GithubClientID = vals[settingOauthGithubClientID] view.GithubSecretSet = vals[settingOauthGithubClientSecret] != "" view.GithubDisplayName = vals[settingOauthGithubDisplayName] view.GithubDisabled = vals[settingOauthGithubDisabled] == "1" return view, nil } // boolFlag 把开关序列化为 settings 存储值。 func boolFlag(on bool) string { if on { return "1" } return "" } // UpdateOAuth 保存 provider 配置;issuer 规范化去尾斜杠,secret 加密落库。 func (s *SettingService) UpdateOAuth(ctx context.Context, in UpdateOAuthInput) error { plain := map[string]string{ settingOauthOidcIssuer: strings.TrimRight(strings.TrimSpace(in.OidcIssuer), "/"), settingOauthOidcClientID: strings.TrimSpace(in.OidcClientID), settingOauthOidcDisplayName: strings.TrimSpace(in.OidcDisplayName), settingOauthOidcDisabled: boolFlag(in.OidcDisabled), settingOauthGithubClientID: strings.TrimSpace(in.GithubClientID), settingOauthGithubDisplayName: strings.TrimSpace(in.GithubDisplayName), settingOauthGithubDisabled: boolFlag(in.GithubDisabled), } for key, value := range plain { if err := s.set(ctx, key, value); err != nil { return err } } if err := s.saveOAuthSecret(ctx, settingOauthOidcClientSecret, in.OidcClientSecret); err != nil { return err } return s.saveOAuthSecret(ctx, settingOauthGithubClientSecret, in.GithubClientSecret) } // saveOAuthSecret 加密保存 secret;nil 沿用,空串清除。 func (s *SettingService) saveOAuthSecret(ctx context.Context, key string, secret *string) error { if secret == nil { return nil } if *secret == "" { return s.set(ctx, key, "") } enc, err := s.cipher.EncryptString(*secret) if err != nil { return fmt.Errorf("encrypt oauth secret: %w", err) } return s.set(ctx, key, enc) } // oauthClient 返回 provider 的 clientID/明文 secret/issuer(仅 oidc);未配置时 clientID 为空。 func (s *SettingService) oauthClient(ctx context.Context, provider string) (clientID, secret, issuer string, err error) { idKey, secKey := settingOauthGithubClientID, settingOauthGithubClientSecret if provider == "oidc" { idKey, secKey = settingOauthOidcClientID, settingOauthOidcClientSecret if issuer, err = s.get(ctx, settingOauthOidcIssuer); err != nil { return } } if clientID, err = s.get(ctx, idKey); err != nil { return } enc, err := s.get(ctx, secKey) if err != nil || enc == "" { return } if secret, err = s.cipher.DecryptString(enc); err != nil { err = fmt.Errorf("decrypt oauth secret: %w", err) } return } // oauthProviderMeta 返回 provider 的展示名(空则给默认名)与禁用态。 func (s *SettingService) oauthProviderMeta(ctx context.Context, provider string) (display string, disabled bool, err error) { nameKey, offKey, def := settingOauthGithubDisplayName, settingOauthGithubDisabled, "GitHub" if provider == "oidc" { nameKey, offKey, def = settingOauthOidcDisplayName, settingOauthOidcDisabled, "OIDC 单点登录" } if display, err = s.get(ctx, nameKey); err != nil { return } if display == "" { display = def } off, err := s.get(ctx, offKey) disabled = off == "1" return }