Files
tokens-reef/backend/internal/config/config_helpers.go
User a4eb4d4c3a refactor(config): split config.go into modular files
Split the monolithic config.go (~120KB) into focused modules:
- auth.go: JWT, TOTP, Turnstile, RateLimit configs
- billing.go: Billing and Pricing configs
- database.go: Database and Redis configs
- gateway.go: Gateway and Upstream configs
- gateway_sub.go: Gateway sub-configurations
- ops_and_cache.go: Ops and Cache configs
- platforms.go: Platform-specific configs
- security.go: Security-related configs
- server.go: Server configuration
- config_defaults.go: Default values
- config_defaults_detail.go: Detailed defaults
- config_helpers.go: Helper functions
- config_validate.go: Validation logic
- config_validate_gateway.go: Gateway validation

This improves:
- Code maintainability and readability
- Faster compilation (smaller files)
- Easier navigation and debugging
- Better separation of concerns
2026-04-17 07:22:55 +08:00

105 lines
3.4 KiB
Go

package config
import (
"crypto/rand"
"encoding/hex"
"fmt"
"log/slog"
"net/url"
"strings"
"github.com/spf13/viper"
)
// normalizeStringSlice normalizes a string slice by trimming empties.
func normalizeStringSlice(values []string) []string {
if len(values) == 0 { return values }
out := make([]string, 0, len(values))
for _, v := range values {
if t := strings.TrimSpace(v); t != "" { out = append(out, t) }
}
return out
}
func isWeakJWTSecret(secret string) bool {
lower := strings.ToLower(strings.TrimSpace(secret))
if lower == "" { return true }
weak := map[string]struct{}{
"change-me-in-production": {}, "changeme": {}, "secret": {}, "password": {},
"123456": {}, "12345678": {}, "admin": {}, "jwt-secret": {},
}
_, exists := weak[lower]
return exists
}
func generateJWTSecret(byteLength int) (string, error) {
if byteLength <= 0 { byteLength = 32 }
buf := make([]byte, byteLength)
if _, err := rand.Read(buf); err != nil { return "", err }
return hex.EncodeToString(buf), nil
}
// GetServerAddress returns server address before full config validation (for setup wizard).
func GetServerAddress() string {
v := viper.New()
v.SetConfigName("config")
v.SetConfigType("yaml")
v.AddConfigPath("."); v.AddConfigPath("./config"); v.AddConfigPath("/etc/sub2api")
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.SetDefault("server.host", "0.0.0.0")
v.SetDefault("server.port", 8080)
_ = v.ReadInConfig()
return fmt.Sprintf("%s:%d", v.GetString("server.host"), v.GetInt("server.port"))
}
// ValidateAbsoluteHTTPURL validates an absolute HTTP(S) URL.
func ValidateAbsoluteHTTPURL(raw string) error {
raw = strings.TrimSpace(raw)
if raw == "" { return fmt.Errorf("empty url") }
u, err := url.Parse(raw)
if err != nil { return err }
if !u.IsAbs() { return fmt.Errorf("must be absolute") }
if !isHTTPScheme(u.Scheme) { return fmt.Errorf("unsupported scheme: %s", u.Scheme) }
if strings.TrimSpace(u.Host) == "" { return fmt.Errorf("missing host") }
if u.Fragment != "" { return fmt.Errorf("must not include fragment") }
return nil
}
// ValidateFrontendRedirectURL validates frontend redirect URL (absolute http(s) or relative path).
func ValidateFrontendRedirectURL(raw string) error {
raw = strings.TrimSpace(raw)
if raw == "" { return fmt.Errorf("empty url") }
if strings.ContainsAny(raw, "\r\n") { return fmt.Errorf("contains invalid characters") }
if strings.HasPrefix(raw, "/") {
if strings.HasPrefix(raw, "//") { return fmt.Errorf("must not start with //") }
return nil
}
u, err := url.Parse(raw)
if err != nil { return err }
if !u.IsAbs() { return fmt.Errorf("must be absolute http(s) url or relative path") }
if !isHTTPScheme(u.Scheme) { return fmt.Errorf("unsupported scheme: %s", u.Scheme) }
if strings.TrimSpace(u.Host) == "" { return fmt.Errorf("missing host") }
if u.Fragment != "" { return fmt.Errorf("must not include fragment") }
return nil
}
func scopeContainsOpenID(scopes string) bool {
for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) {
if scope == "openid" { return true }
}
return false
}
func isHTTPScheme(scheme string) bool {
return strings.EqualFold(scheme, "http") || strings.EqualFold(scheme, "https")
}
func warnIfInsecureURL(field, raw string) {
u, err := url.Parse(strings.TrimSpace(raw))
if err != nil { return }
if strings.EqualFold(u.Scheme, "http") {
slog.Warn("url uses http scheme; use https in production to avoid token leakage", "field", field)
}
}