feat: make account service domain configurable (#385)

This commit is contained in:
shenlan 2025-10-03 01:04:55 +08:00 committed by GitHub
parent 1567bc327d
commit 19b1034518
6 changed files with 152 additions and 27 deletions

View File

@ -54,6 +54,13 @@ The Next.js homepage reads `NEXT_PUBLIC_ACCOUNT_SERVICE_URL` to reach the Accoun
defaults to `https://localhost:8443`, matching the hostname used by the development TLS certificate. Override the environment
variable if your local certificates use a different host or when connecting to a remote Account Service instance.
## Account service configuration
`account/config/account.yaml` now accepts a `server.publicUrl` value such as `https://account.svc.plus:8443`. The account service
uses this URL to derive a default CORS origin and to document the externally reachable host. Set `server.allowedOrigins` when you
need to expose additional browser clients; omit it to fall back to the public URL or the local development origins
(`http://localhost:3001` and `http://127.0.0.1:3001`).
## Features
- **XCloudFlow** Multi-cloud IaC engine built with Pulumi SDK and Go. GitHub →
- **KubeGuard** Kubernetes cluster application and node-level backup system. GitHub →

View File

@ -10,6 +10,7 @@ import (
"log/slog"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
@ -72,27 +73,11 @@ var rootCmd = &cobra.Command{
slog.SetDefault(logger)
r := gin.New()
corsConfig := cors.Config{
AllowOrigins: []string{
"http://localhost:3001",
"http://127.0.0.1:3001",
},
AllowMethods: []string{
http.MethodOptions,
http.MethodPost,
},
AllowHeaders: []string{
"Authorization",
"Content-Type",
"Accept",
"Origin",
"X-Requested-With",
},
ExposeHeaders: []string{
"Content-Length",
},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
corsConfig := buildCORSConfig(logger, cfg.Server)
if corsConfig.AllowAllOrigins {
logger.Info("configured cors", "allowAllOrigins", true)
} else {
logger.Info("configured cors", "allowedOrigins", corsConfig.AllowOrigins)
}
r.Use(cors.New(corsConfig))
r.Use(gin.Recovery())
@ -305,6 +290,131 @@ func main() {
}
}
func buildCORSConfig(logger *slog.Logger, serverCfg config.Server) cors.Config {
allowOrigins, allowAll := resolveAllowedOrigins(logger, serverCfg)
cfg := cors.Config{
AllowMethods: []string{
http.MethodGet,
http.MethodHead,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
http.MethodOptions,
},
AllowHeaders: []string{
"Authorization",
"Content-Type",
"Accept",
"Origin",
"X-Requested-With",
},
ExposeHeaders: []string{
"Content-Length",
},
MaxAge: 12 * time.Hour,
}
if allowAll {
cfg.AllowAllOrigins = true
cfg.AllowCredentials = false
} else {
cfg.AllowOrigins = allowOrigins
cfg.AllowCredentials = true
}
return cfg
}
func resolveAllowedOrigins(logger *slog.Logger, serverCfg config.Server) ([]string, bool) {
rawOrigins := serverCfg.AllowedOrigins
seen := make(map[string]struct{}, len(rawOrigins))
origins := make([]string, 0, len(rawOrigins))
allowAll := false
for _, origin := range rawOrigins {
trimmed := strings.TrimSpace(origin)
if trimmed == "" {
continue
}
if trimmed == "*" {
allowAll = true
continue
}
normalized, err := parseOrigin(trimmed)
if err != nil {
logger.Warn("ignoring invalid cors origin", "origin", origin, "err", err)
continue
}
if _, exists := seen[normalized]; exists {
continue
}
seen[normalized] = struct{}{}
origins = append(origins, normalized)
}
if allowAll {
return nil, true
}
if len(origins) == 0 {
publicURL := strings.TrimSpace(serverCfg.PublicURL)
if publicURL != "" {
normalized, err := parseOrigin(publicURL)
if err != nil {
logger.Warn("invalid server public url; falling back to defaults", "publicUrl", publicURL, "err", err)
} else {
origins = append(origins, normalized)
}
}
}
if len(origins) == 0 {
origins = []string{
"http://localhost:3001",
"http://127.0.0.1:3001",
}
}
return origins, false
}
func parseOrigin(value string) (string, error) {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return "", fmt.Errorf("origin is empty")
}
normalized := trimmed
if !strings.Contains(normalized, "://") {
normalized = "https://" + normalized
}
parsed, err := url.Parse(normalized)
if err != nil {
return "", err
}
scheme := strings.ToLower(strings.TrimSpace(parsed.Scheme))
if scheme == "" {
return "", fmt.Errorf("origin must include a scheme")
}
hostname := strings.ToLower(strings.TrimSpace(parsed.Hostname()))
if hostname == "" {
return "", fmt.Errorf("origin must include a host")
}
host := hostname
if port := strings.TrimSpace(parsed.Port()); port != "" {
host = net.JoinHostPort(hostname, port)
}
return scheme + "://" + host, nil
}
func deriveRedirectAddr(addr string) string {
host, port, err := net.SplitHostPort(strings.TrimSpace(addr))
if err != nil {

View File

@ -5,6 +5,12 @@ server:
addr: ":8443"
readTimeout: 15s
writeTimeout: 15s
publicUrl: "https://account.svc.plus"
allowedOrigins:
- "https://account.svc.plus"
- "https://localhost:8443"
- "http://localhost:3001"
- "http://127.0.0.1:3001"
tls:
enabled: true
certFile: "/etc/ssl/svc.plus.pem"

View File

@ -28,10 +28,12 @@ type Config struct {
// Server defines HTTP server configuration.
type Server struct {
Addr string `yaml:"addr"`
ReadTimeout time.Duration `yaml:"readTimeout"`
WriteTimeout time.Duration `yaml:"writeTimeout"`
TLS TLS `yaml:"tls"`
Addr string `yaml:"addr"`
ReadTimeout time.Duration `yaml:"readTimeout"`
WriteTimeout time.Duration `yaml:"writeTimeout"`
TLS TLS `yaml:"tls"`
PublicURL string `yaml:"publicUrl"`
AllowedOrigins []string `yaml:"allowedOrigins"`
}
// TLS describes TLS configuration for the server listener.

View File

@ -53,7 +53,7 @@ export default function LoginContent({ children }: LoginContentProps) {
[],
)
const accountServiceBaseUrl = (process.env.NEXT_PUBLIC_ACCOUNT_SERVICE_URL || 'https://127.0.0.1:8443').replace(/\/$/, '')
const accountServiceBaseUrl = (process.env.NEXT_PUBLIC_ACCOUNT_SERVICE_URL || 'https://localhost:8443').replace(/\/$/, '')
const githubAuthUrl = process.env.NEXT_PUBLIC_GITHUB_AUTH_URL || '/api/auth/github'
const wechatAuthUrl = process.env.NEXT_PUBLIC_WECHAT_AUTH_URL || '/api/auth/wechat'
const loginUrl = process.env.NEXT_PUBLIC_LOGIN_URL || `${accountServiceBaseUrl}/api/auth/login`

View File

@ -1,4 +1,4 @@
const DEFAULT_ACCOUNT_SERVICE_URL = 'http://localhost:8080'
const DEFAULT_ACCOUNT_SERVICE_URL = 'https://localhost:8443'
const DEFAULT_SERVER_SERVICE_URL = 'http://localhost:8090'
function readEnvValue(...keys: string[]): string | undefined {