package config import ( "fmt" "os" "time" "gopkg.in/yaml.v3" ) // Config represents the application configuration type Config struct { Server ServerConfig `yaml:"server"` Database DatabaseConfig `yaml:"database"` Auth AuthConfig `yaml:"auth"` Logging LoggingConfig `yaml:"logging"` Security SecurityConfig `yaml:"security"` } // ServerConfig holds HTTP server configuration type ServerConfig struct { Port int `yaml:"port"` Host string `yaml:"host"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` IdleTimeout time.Duration `yaml:"idle_timeout"` Cache CacheConfig `yaml:"cache"` } // CacheConfig holds response caching configuration type CacheConfig struct { Enabled bool `yaml:"enabled"` DefaultTTL time.Duration `yaml:"default_ttl"` MaxAge int `yaml:"max_age"` // seconds for Cache-Control header } // DatabaseConfig holds PostgreSQL connection configuration type DatabaseConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` User string `yaml:"user"` Password string `yaml:"password"` Database string `yaml:"database"` SSLMode string `yaml:"ssl_mode"` MaxConnections int `yaml:"max_connections"` MaxIdleConns int `yaml:"max_idle_conns"` ConnMaxLifetime time.Duration `yaml:"conn_max_lifetime"` } // AuthConfig holds authentication configuration type AuthConfig struct { JWTSecret string `yaml:"jwt_secret"` TokenLifetime time.Duration `yaml:"token_lifetime"` Argon2Params Argon2Params `yaml:"argon2"` } // Argon2Params holds Argon2id password hashing parameters type Argon2Params struct { Memory uint32 `yaml:"memory"` Iterations uint32 `yaml:"iterations"` Parallelism uint8 `yaml:"parallelism"` SaltLength uint32 `yaml:"salt_length"` KeyLength uint32 `yaml:"key_length"` } // LoggingConfig holds logging configuration type LoggingConfig struct { Level string `yaml:"level"` Format string `yaml:"format"` // json or text } // SecurityConfig holds security-related configuration type SecurityConfig struct { CORS CORSConfig `yaml:"cors"` RateLimit RateLimitConfig `yaml:"rate_limit"` SecurityHeaders SecurityHeadersConfig `yaml:"security_headers"` } // CORSConfig holds CORS configuration type CORSConfig struct { AllowedOrigins []string `yaml:"allowed_origins"` AllowedMethods []string `yaml:"allowed_methods"` AllowedHeaders []string `yaml:"allowed_headers"` AllowCredentials bool `yaml:"allow_credentials"` } // RateLimitConfig holds rate limiting configuration type RateLimitConfig struct { Enabled bool `yaml:"enabled"` RequestsPerSecond float64 `yaml:"requests_per_second"` BurstSize int `yaml:"burst_size"` } // SecurityHeadersConfig holds security headers configuration type SecurityHeadersConfig struct { Enabled bool `yaml:"enabled"` } // Load reads configuration from file and environment variables func Load(path string) (*Config, error) { cfg := DefaultConfig() // Read from file if it exists if _, err := os.Stat(path); err == nil { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read config file: %w", err) } if err := yaml.Unmarshal(data, cfg); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) } } // Override with environment variables overrideFromEnv(cfg) return cfg, nil } // DefaultConfig returns a configuration with sensible defaults func DefaultConfig() *Config { return &Config{ Server: ServerConfig{ Port: 8080, Host: "0.0.0.0", ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, }, Database: DatabaseConfig{ Host: getEnv("CALYPSO_DB_HOST", "localhost"), Port: getEnvInt("CALYPSO_DB_PORT", 5432), User: getEnv("CALYPSO_DB_USER", "calypso"), Password: getEnv("CALYPSO_DB_PASSWORD", ""), Database: getEnv("CALYPSO_DB_NAME", "calypso"), SSLMode: getEnv("CALYPSO_DB_SSLMODE", "disable"), MaxConnections: 25, MaxIdleConns: 5, ConnMaxLifetime: 5 * time.Minute, }, Auth: AuthConfig{ JWTSecret: getEnv("CALYPSO_JWT_SECRET", "change-me-in-production"), TokenLifetime: 24 * time.Hour, Argon2Params: Argon2Params{ Memory: 64 * 1024, // 64 MB Iterations: 3, Parallelism: 4, SaltLength: 16, KeyLength: 32, }, }, Logging: LoggingConfig{ Level: getEnv("CALYPSO_LOG_LEVEL", "info"), Format: getEnv("CALYPSO_LOG_FORMAT", "json"), }, Security: SecurityConfig{ CORS: CORSConfig{ AllowedOrigins: []string{"*"}, // Default: allow all (should be restricted in production) AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"}, AllowedHeaders: []string{"Content-Type", "Authorization", "Accept", "Origin"}, AllowCredentials: true, }, RateLimit: RateLimitConfig{ Enabled: true, RequestsPerSecond: 100.0, BurstSize: 50, }, SecurityHeaders: SecurityHeadersConfig{ Enabled: true, }, }, } } // overrideFromEnv applies environment variable overrides func overrideFromEnv(cfg *Config) { if v := os.Getenv("CALYPSO_SERVER_PORT"); v != "" { cfg.Server.Port = getEnvInt("CALYPSO_SERVER_PORT", cfg.Server.Port) } if v := os.Getenv("CALYPSO_DB_HOST"); v != "" { cfg.Database.Host = v } if v := os.Getenv("CALYPSO_DB_PASSWORD"); v != "" { cfg.Database.Password = v } if v := os.Getenv("CALYPSO_JWT_SECRET"); v != "" { cfg.Auth.JWTSecret = v } } // Helper functions func getEnv(key, defaultValue string) string { if v := os.Getenv(key); v != "" { return v } return defaultValue } func getEnvInt(key string, defaultValue int) int { if v := os.Getenv(key); v != "" { var result int if _, err := fmt.Sscanf(v, "%d", &result); err == nil { return result } } return defaultValue }