Files
oci-portal/internal/service/oauthconfig.go
T
2026-07-22 16:51:23 +08:00

188 lines
6.2 KiB
Go

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 配置的部分更新:所有字段 nil=沿用已存值,
// 只落库出现的字段;并发编辑不同 provider 因此互不回滚(2026-07-22 审查 #18)。
// secret 非 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 ""
}
// trimPtr / issuerPtr / flagPtr 把补丁字段规范化为存储值;nil 表示未出现不写。
func trimPtr(p *string) *string {
if p == nil {
return nil
}
v := strings.TrimSpace(*p)
return &v
}
func issuerPtr(p *string) *string {
if p == nil {
return nil
}
v := strings.TrimRight(strings.TrimSpace(*p), "/")
return &v
}
func flagPtr(p *bool) *string {
if p == nil {
return nil
}
v := boolFlag(*p)
return &v
}
// UpdateOAuth 部分更新 provider 配置:只落库非 nil 字段;
// issuer 规范化去尾斜杠,secret 加密落库(空串清除)。
func (s *SettingService) UpdateOAuth(ctx context.Context, in UpdateOAuthInput) error {
writes := []struct {
key string
val *string
}{
{settingOauthOidcIssuer, issuerPtr(in.OidcIssuer)},
{settingOauthOidcClientID, trimPtr(in.OidcClientID)},
{settingOauthOidcDisplayName, trimPtr(in.OidcDisplayName)},
{settingOauthOidcDisabled, flagPtr(in.OidcDisabled)},
{settingOauthGithubClientID, trimPtr(in.GithubClientID)},
{settingOauthGithubDisplayName, trimPtr(in.GithubDisplayName)},
{settingOauthGithubDisabled, flagPtr(in.GithubDisabled)},
}
for _, w := range writes {
if w.val == nil {
continue
}
if err := s.set(ctx, w.key, *w.val); 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
}