210 lines
5.9 KiB
Go
210 lines
5.9 KiB
Go
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
|
|
}
|
|
|