package config import ( "log" "os" "strconv" "strings" "time" ) type Config struct { Server ServerConfig Auth AuthConfig RateLimit RateLimitConfig CORS CORSConfig Services ServicesConfig Security SecurityConfig Logging LoggingConfig Metrics MetricsConfig } type ServerConfig struct { Port string ReadTimeout time.Duration WriteTimeout time.Duration IdleTimeout time.Duration } type AuthConfig struct { ZitadelURL string JWTSigningKey string TokenExpiration time.Duration RefreshThreshold time.Duration RequiredScopes []string AdminRoles []string } type RateLimitConfig struct { Enabled bool RequestsPerMinute int BurstSize int WindowSize time.Duration KeyGenerator string Storage StorageConfig } type CORSConfig struct { AllowedOrigins string AllowedMethods string AllowedHeaders string AllowCredentials bool MaxAge int } type ServicesConfig struct { GameService ServiceConfig QuestionService ServiceConfig UserService ServiceConfig LeaderboardService ServiceConfig SessionService ServiceConfig AdminService ServiceConfig HealthCheckInterval time.Duration CircuitBreaker CircuitBreakerConfig } type ServiceConfig struct { URL string Timeout time.Duration RetryAttempts int RetryDelay time.Duration HealthEndpoint string Priority int LoadBalancing LoadBalancingConfig } type LoadBalancingConfig struct { Strategy string Instances []string HealthCheck HealthCheckConfig } type HealthCheckConfig struct { Enabled bool Interval time.Duration Timeout time.Duration Path string } type CircuitBreakerConfig struct { Enabled bool Threshold int Timeout time.Duration MaxRequests int RecoveryTimeout time.Duration } type SecurityConfig struct { IPWhitelist []string IPBlacklist []string RequestSizeLimit int64 EnableCSRFProtection bool CSRFTokenHeader string EnableXSSProtection bool ContentTypeValidation bool AllowedContentTypes []string SecurityHeaders map[string]string } type StorageConfig struct { Type string RedisURL string Options map[string]interface{} } type LoggingConfig struct { Level string Format string Output string AccessLog bool ErrorLog bool RequestID bool Structured bool } type MetricsConfig struct { Enabled bool Endpoint string Namespace string Subsystem string CollectGo bool CollectProcess bool } func Load() *Config { return &Config{ Server: ServerConfig{ Port: getEnv("GATEWAY_PORT", ":8086"), ReadTimeout: getDurationEnv("GATEWAY_READ_TIMEOUT", 30*time.Second), WriteTimeout: getDurationEnv("GATEWAY_WRITE_TIMEOUT", 30*time.Second), IdleTimeout: getDurationEnv("GATEWAY_IDLE_TIMEOUT", 120*time.Second), }, Auth: AuthConfig{ ZitadelURL: getEnv("ZITADEL_URL", "http://localhost:8080"), JWTSigningKey: getEnv("JWT_SIGNING_KEY", ""), TokenExpiration: getDurationEnv("TOKEN_EXPIRATION", 24*time.Hour), RefreshThreshold: getDurationEnv("REFRESH_THRESHOLD", 5*time.Minute), RequiredScopes: getSliceEnv("REQUIRED_SCOPES", []string{"openid", "profile", "email"}), AdminRoles: getSliceEnv("ADMIN_ROLES", []string{"admin", "super_admin", "moderator"}), }, RateLimit: RateLimitConfig{ Enabled: getBoolEnv("RATE_LIMIT_ENABLED", true), RequestsPerMinute: getIntEnv("RATE_LIMIT_RPM", 60), BurstSize: getIntEnv("RATE_LIMIT_BURST", 10), WindowSize: getDurationEnv("RATE_LIMIT_WINDOW", time.Minute), KeyGenerator: getEnv("RATE_LIMIT_KEY_GENERATOR", "ip"), Storage: StorageConfig{ Type: getEnv("RATE_LIMIT_STORAGE", "memory"), RedisURL: getEnv("REDIS_URL", "redis://localhost:6379"), }, }, CORS: CORSConfig{ AllowedOrigins: getEnv("CORS_ALLOWED_ORIGINS", "*"), AllowedMethods: getEnv("CORS_ALLOWED_METHODS", "GET,POST,PUT,DELETE,OPTIONS,PATCH"), AllowedHeaders: getEnv("CORS_ALLOWED_HEADERS", "Origin,Content-Type,Accept,Authorization,X-Requested-With"), AllowCredentials: getBoolEnv("CORS_ALLOW_CREDENTIALS", true), MaxAge: getIntEnv("CORS_MAX_AGE", 86400), }, Services: ServicesConfig{ GameService: ServiceConfig{ URL: getEnv("GAME_SERVICE_URL", "http://localhost:8080"), Timeout: getDurationEnv("GAME_SERVICE_TIMEOUT", 10*time.Second), RetryAttempts: getIntEnv("GAME_SERVICE_RETRY_ATTEMPTS", 3), RetryDelay: getDurationEnv("GAME_SERVICE_RETRY_DELAY", 1*time.Second), HealthEndpoint: "/health", Priority: 1, }, QuestionService: ServiceConfig{ URL: getEnv("QUESTION_SERVICE_URL", "http://localhost:8081"), Timeout: getDurationEnv("QUESTION_SERVICE_TIMEOUT", 10*time.Second), RetryAttempts: getIntEnv("QUESTION_SERVICE_RETRY_ATTEMPTS", 3), RetryDelay: getDurationEnv("QUESTION_SERVICE_RETRY_DELAY", 1*time.Second), HealthEndpoint: "/health", Priority: 2, }, UserService: ServiceConfig{ URL: getEnv("USER_SERVICE_URL", "http://localhost:8082"), Timeout: getDurationEnv("USER_SERVICE_TIMEOUT", 10*time.Second), RetryAttempts: getIntEnv("USER_SERVICE_RETRY_ATTEMPTS", 3), RetryDelay: getDurationEnv("USER_SERVICE_RETRY_DELAY", 1*time.Second), HealthEndpoint: "/health", Priority: 3, }, LeaderboardService: ServiceConfig{ URL: getEnv("LEADERBOARD_SERVICE_URL", "http://localhost:8083"), Timeout: getDurationEnv("LEADERBOARD_SERVICE_TIMEOUT", 10*time.Second), RetryAttempts: getIntEnv("LEADERBOARD_SERVICE_RETRY_ATTEMPTS", 3), RetryDelay: getDurationEnv("LEADERBOARD_SERVICE_RETRY_DELAY", 1*time.Second), HealthEndpoint: "/health", Priority: 4, }, SessionService: ServiceConfig{ URL: getEnv("SESSION_SERVICE_URL", "http://localhost:8084"), Timeout: getDurationEnv("SESSION_SERVICE_TIMEOUT", 10*time.Second), RetryAttempts: getIntEnv("SESSION_SERVICE_RETRY_ATTEMPTS", 3), RetryDelay: getDurationEnv("SESSION_SERVICE_RETRY_DELAY", 1*time.Second), HealthEndpoint: "/health", Priority: 5, }, AdminService: ServiceConfig{ URL: getEnv("ADMIN_SERVICE_URL", "http://localhost:8085"), Timeout: getDurationEnv("ADMIN_SERVICE_TIMEOUT", 10*time.Second), RetryAttempts: getIntEnv("ADMIN_SERVICE_RETRY_ATTEMPTS", 3), RetryDelay: getDurationEnv("ADMIN_SERVICE_RETRY_DELAY", 1*time.Second), HealthEndpoint: "/health", Priority: 6, }, HealthCheckInterval: getDurationEnv("HEALTH_CHECK_INTERVAL", 30*time.Second), CircuitBreaker: CircuitBreakerConfig{ Enabled: getBoolEnv("CIRCUIT_BREAKER_ENABLED", true), Threshold: getIntEnv("CIRCUIT_BREAKER_THRESHOLD", 5), Timeout: getDurationEnv("CIRCUIT_BREAKER_TIMEOUT", 60*time.Second), MaxRequests: getIntEnv("CIRCUIT_BREAKER_MAX_REQUESTS", 3), RecoveryTimeout: getDurationEnv("CIRCUIT_BREAKER_RECOVERY_TIMEOUT", 30*time.Second), }, }, Security: SecurityConfig{ IPWhitelist: getSliceEnv("IP_WHITELIST", []string{}), IPBlacklist: getSliceEnv("IP_BLACKLIST", []string{}), RequestSizeLimit: getInt64Env("REQUEST_SIZE_LIMIT", 10*1024*1024), EnableCSRFProtection: getBoolEnv("ENABLE_CSRF_PROTECTION", true), CSRFTokenHeader: getEnv("CSRF_TOKEN_HEADER", "X-CSRF-Token"), EnableXSSProtection: getBoolEnv("ENABLE_XSS_PROTECTION", true), ContentTypeValidation: getBoolEnv("CONTENT_TYPE_VALIDATION", true), AllowedContentTypes: getSliceEnv("ALLOWED_CONTENT_TYPES", []string{"application/json", "application/x-www-form-urlencoded", "multipart/form-data"}), SecurityHeaders: map[string]string{ "X-Frame-Options": "DENY", "X-Content-Type-Options": "nosniff", "X-XSS-Protection": "1; mode=block", "Strict-Transport-Security": "max-age=31536000; includeSubDomains", "Referrer-Policy": "strict-origin-when-cross-origin", }, }, Logging: LoggingConfig{ Level: getEnv("LOG_LEVEL", "info"), Format: getEnv("LOG_FORMAT", "json"), Output: getEnv("LOG_OUTPUT", "stdout"), AccessLog: getBoolEnv("ACCESS_LOG", true), ErrorLog: getBoolEnv("ERROR_LOG", true), RequestID: getBoolEnv("LOG_REQUEST_ID", true), Structured: getBoolEnv("STRUCTURED_LOGGING", true), }, Metrics: MetricsConfig{ Enabled: getBoolEnv("METRICS_ENABLED", true), Endpoint: getEnv("METRICS_ENDPOINT", "/metrics"), Namespace: getEnv("METRICS_NAMESPACE", "knowfoolery"), Subsystem: getEnv("METRICS_SUBSYSTEM", "gateway"), CollectGo: getBoolEnv("METRICS_COLLECT_GO", true), CollectProcess: getBoolEnv("METRICS_COLLECT_PROCESS", true), }, } } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func getIntEnv(key string, defaultValue int) int { if value := os.Getenv(key); value != "" { if parsed, err := strconv.Atoi(value); err == nil { return parsed } log.Printf("Invalid integer value for %s: %s, using default: %d", key, value, defaultValue) } return defaultValue } func getInt64Env(key string, defaultValue int64) int64 { if value := os.Getenv(key); value != "" { if parsed, err := strconv.ParseInt(value, 10, 64); err == nil { return parsed } log.Printf("Invalid int64 value for %s: %s, using default: %d", key, value, defaultValue) } return defaultValue } func getBoolEnv(key string, defaultValue bool) bool { if value := os.Getenv(key); value != "" { if parsed, err := strconv.ParseBool(value); err == nil { return parsed } log.Printf("Invalid boolean value for %s: %s, using default: %t", key, value, defaultValue) } return defaultValue } func getDurationEnv(key string, defaultValue time.Duration) time.Duration { if value := os.Getenv(key); value != "" { if parsed, err := time.ParseDuration(value); err == nil { return parsed } log.Printf("Invalid duration value for %s: %s, using default: %s", key, value, defaultValue) } return defaultValue } func getSliceEnv(key string, defaultValue []string) []string { if value := os.Getenv(key); value != "" { return strings.Split(value, ",") } return defaultValue }