feat: make account service domain configurable (#385)
This commit is contained in:
parent
1567bc327d
commit
19b1034518
@ -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 →
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user