Files
vw-api/internal/config/config.go
T
2025-10-24 13:25:05 +02:00

101 lines
2.7 KiB
Go

package config
import (
"fmt"
"net/url"
"os"
"strings"
"time"
)
// Config holds all application configuration
type Config struct {
// Server settings
Port string
Environment string
ReadTimeout time.Duration
WriteTimeout time.Duration
// Security
APIKey string
VaultwardenURL string
VaultwardenToken string
VaultwardenClientID string
VaultwardenSecret string
CacheTTL time.Duration
CORSAllowedOrigins string
}
// Load reads configuration from environment variables
func Load() (*Config, error) {
cfg := &Config{
Port: getEnv("API_PORT", "8080"),
Environment: getEnv("ENVIRONMENT", "development"),
APIKey: os.Getenv("API_KEY"),
VaultwardenURL: os.Getenv("VAULTWARDEN_URL"),
VaultwardenToken: os.Getenv("VAULTWARDEN_ACCESS_TOKEN"),
VaultwardenClientID: os.Getenv("VAULTWARDEN_CLIENT_ID"),
VaultwardenSecret: os.Getenv("VAULTWARDEN_CLIENT_SECRET"),
ReadTimeout: parseDuration(getEnv("READ_TIMEOUT", "10s")),
WriteTimeout: parseDuration(getEnv("WRITE_TIMEOUT", "10s")),
CacheTTL: parseDuration(getEnv("CACHE_TTL", "5m")),
CORSAllowedOrigins: getEnv("CORS_ALLOWED_ORIGINS", "http://localhost:3000"),
}
// Validate required fields
if cfg.APIKey == "" {
return nil, fmt.Errorf("API_KEY is required")
}
if len(cfg.APIKey) < 32 {
return nil, fmt.Errorf("API_KEY must be at least 32 characters for security (run: openssl rand -base64 32)")
}
if cfg.VaultwardenURL == "" {
return nil, fmt.Errorf("VAULTWARDEN_URL is required")
}
hasAPIKeys := cfg.VaultwardenClientID != "" && cfg.VaultwardenSecret != ""
hasSessionToken := cfg.VaultwardenToken != ""
if !hasAPIKeys && !hasSessionToken {
return nil, fmt.Errorf("either VAULTWARDEN_CLIENT_ID+VAULTWARDEN_CLIENT_SECRET or VAULTWARDEN_ACCESS_TOKEN is required")
}
// Validate and normalize URL
parsedURL, err := url.Parse(cfg.VaultwardenURL)
if err != nil {
return nil, fmt.Errorf("VAULTWARDEN_URL is invalid: %w", err)
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return nil, fmt.Errorf("VAULTWARDEN_URL must use http or https scheme")
}
// Remove trailing slash for consistency
cfg.VaultwardenURL = strings.TrimSuffix(cfg.VaultwardenURL, "/")
return cfg, nil
}
// IsProd returns true if running in production
func (c *Config) IsProd() bool {
return c.Environment == "production"
}
// getEnv gets an environment variable with a fallback default value
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// parseDuration parses a duration string with a fallback
func parseDuration(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
return 10 * time.Second
}
return d
}