Files
skybridge/internal/config/config.go
2025-08-22 18:57:40 -04:00

307 lines
8.6 KiB
Go

package config
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/joho/godotenv"
)
// ConfigProvider defines the interface for configuration operations
type ConfigProvider interface {
// GetString retrieves a string configuration value
GetString(key string) string
// GetInt retrieves an integer configuration value
GetInt(key string) int
// GetBool retrieves a boolean configuration value
GetBool(key string) bool
// GetDuration retrieves a duration configuration value
GetDuration(key string) time.Duration
// GetStringSlice retrieves a string slice configuration value
GetStringSlice(key string) []string
// IsSet checks if a configuration key is set
IsSet(key string) bool
// Validate validates all required configuration values
Validate() error
// GetDatabaseDSN constructs and returns the database connection string
GetDatabaseDSN() string
// GetServerAddress returns the server address in host:port format
GetServerAddress() string
// GetMetricsAddress returns the metrics server address in host:port format
GetMetricsAddress() string
// GetJWTSecret returns the JWT signing secret
GetJWTSecret() string
// IsDevelopment returns true if the environment is development
IsDevelopment() bool
// IsProduction returns true if the environment is production
IsProduction() bool
}
// Config implements the ConfigProvider interface
type Config struct {
values map[string]string
}
// NewConfig creates a new configuration provider
func NewConfig() ConfigProvider {
// Load .env file if it exists
_ = godotenv.Load()
c := &Config{
values: make(map[string]string),
}
// Load environment variables
for _, env := range os.Environ() {
pair := strings.SplitN(env, "=", 2)
if len(pair) == 2 {
c.values[pair[0]] = pair[1]
}
}
// Set defaults
c.setDefaults()
return c
}
func (c *Config) setDefaults() {
defaults := map[string]string{
"APP_NAME": "api-key-service",
"APP_VERSION": "1.0.0",
"SERVER_HOST": "0.0.0.0",
"SERVER_PORT": "8080",
"SERVER_READ_TIMEOUT": "30s",
"SERVER_WRITE_TIMEOUT": "30s",
"SERVER_IDLE_TIMEOUT": "120s",
"DB_HOST": "localhost",
"DB_PORT": "5432",
"DB_NAME": "kms",
"DB_USER": "postgres",
"DB_PASSWORD": "postgres",
"DB_SSLMODE": "disable",
"DB_MAX_OPEN_CONNS": "25",
"DB_MAX_IDLE_CONNS": "25",
"DB_CONN_MAX_LIFETIME": "5m",
"MIGRATION_PATH": "./migrations",
"LOG_LEVEL": "info",
"LOG_FORMAT": "json",
"RATE_LIMIT_ENABLED": "true",
"RATE_LIMIT_RPS": "100",
"RATE_LIMIT_BURST": "200",
"CACHE_ENABLED": "false",
"CACHE_TTL": "1h",
"JWT_ISSUER": "api-key-service",
"JWT_SECRET": "bootstrap-jwt-secret-change-in-production",
"AUTH_PROVIDER": "header", // header or sso
"AUTH_HEADER_USER_EMAIL": "X-User-Email",
"SSO_PROVIDER_URL": "",
"SSO_CLIENT_ID": "",
"SSO_CLIENT_SECRET": "",
"INTERNAL_APP_ID": "internal.api-key-service",
"INTERNAL_HMAC_KEY": "bootstrap-hmac-key-change-in-production",
"METRICS_ENABLED": "false",
"METRICS_PORT": "9090",
"REDIS_ENABLED": "false",
"REDIS_ADDR": "localhost:6379",
"REDIS_PASSWORD": "",
"REDIS_DB": "0",
"REDIS_POOL_SIZE": "10",
"REDIS_MIN_IDLE_CONNS": "5",
"REDIS_MAX_RETRIES": "3",
"REDIS_DIAL_TIMEOUT": "5s",
"REDIS_READ_TIMEOUT": "3s",
"REDIS_WRITE_TIMEOUT": "3s",
"MAX_AUTH_FAILURES": "5",
"AUTH_FAILURE_WINDOW": "15m",
"IP_BLOCK_DURATION": "1h",
"REQUEST_MAX_AGE": "5m",
"IP_WHITELIST": "",
"SAML_ENABLED": "false",
"SAML_IDP_METADATA_URL": "",
"SAML_SP_ENTITY_ID": "",
"SAML_SP_ACS_URL": "",
"SAML_SP_PRIVATE_KEY": "",
"SAML_SP_CERTIFICATE": "",
}
for key, value := range defaults {
if _, exists := c.values[key]; !exists {
c.values[key] = value
}
}
}
// GetString retrieves a string configuration value
func (c *Config) GetString(key string) string {
return c.values[key]
}
// GetInt retrieves an integer configuration value
func (c *Config) GetInt(key string) int {
if value, exists := c.values[key]; exists {
if intVal, err := strconv.Atoi(value); err == nil {
return intVal
}
}
return 0
}
// GetBool retrieves a boolean configuration value
func (c *Config) GetBool(key string) bool {
if value, exists := c.values[key]; exists {
if boolVal, err := strconv.ParseBool(value); err == nil {
return boolVal
}
}
return false
}
// GetDuration retrieves a duration configuration value
func (c *Config) GetDuration(key string) time.Duration {
if value, exists := c.values[key]; exists {
if duration, err := time.ParseDuration(value); err == nil {
return duration
}
}
return 0
}
// GetStringSlice retrieves a string slice configuration value
func (c *Config) GetStringSlice(key string) []string {
if value, exists := c.values[key]; exists {
if value == "" {
return []string{}
}
return strings.Split(value, ",")
}
return []string{}
}
// IsSet checks if a configuration key is set
func (c *Config) IsSet(key string) bool {
_, exists := c.values[key]
return exists
}
// Validate validates all required configuration values
func (c *Config) Validate() error {
required := []string{
"DB_HOST",
"DB_PORT",
"DB_NAME",
"DB_USER",
"DB_PASSWORD",
"SERVER_HOST",
"SERVER_PORT",
"INTERNAL_APP_ID",
"INTERNAL_HMAC_KEY",
"JWT_SECRET",
}
var missing []string
for _, key := range required {
if !c.IsSet(key) || c.GetString(key) == "" {
missing = append(missing, key)
}
}
if len(missing) > 0 {
return fmt.Errorf("missing required configuration keys: %s", strings.Join(missing, ", "))
}
// Validate specific values
if c.GetInt("DB_PORT") <= 0 || c.GetInt("DB_PORT") > 65535 {
return fmt.Errorf("DB_PORT must be a valid port number")
}
if c.GetInt("SERVER_PORT") <= 0 || c.GetInt("SERVER_PORT") > 65535 {
return fmt.Errorf("SERVER_PORT must be a valid port number")
}
if c.GetDuration("SERVER_READ_TIMEOUT") <= 0 {
return fmt.Errorf("SERVER_READ_TIMEOUT must be a positive duration")
}
if c.GetDuration("SERVER_WRITE_TIMEOUT") <= 0 {
return fmt.Errorf("SERVER_WRITE_TIMEOUT must be a positive duration")
}
if c.GetDuration("DB_CONN_MAX_LIFETIME") <= 0 {
return fmt.Errorf("DB_CONN_MAX_LIFETIME must be a positive duration")
}
authProvider := c.GetString("AUTH_PROVIDER")
if authProvider != "header" && authProvider != "sso" {
return fmt.Errorf("AUTH_PROVIDER must be either 'header' or 'sso'")
}
if authProvider == "sso" {
if c.GetString("SSO_PROVIDER_URL") == "" {
return fmt.Errorf("SSO_PROVIDER_URL is required when AUTH_PROVIDER is 'sso'")
}
if c.GetString("SSO_CLIENT_ID") == "" {
return fmt.Errorf("SSO_CLIENT_ID is required when AUTH_PROVIDER is 'sso'")
}
if c.GetString("SSO_CLIENT_SECRET") == "" {
return fmt.Errorf("SSO_CLIENT_SECRET is required when AUTH_PROVIDER is 'sso'")
}
}
return nil
}
// GetDatabaseDSN constructs and returns the database connection string
func (c *Config) GetDatabaseDSN() string {
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
c.GetString("DB_HOST"),
c.GetInt("DB_PORT"),
c.GetString("DB_USER"),
c.GetString("DB_PASSWORD"),
c.GetString("DB_NAME"),
c.GetString("DB_SSLMODE"),
)
}
// GetServerAddress returns the server address in host:port format
func (c *Config) GetServerAddress() string {
return fmt.Sprintf("%s:%d", c.GetString("SERVER_HOST"), c.GetInt("SERVER_PORT"))
}
// GetMetricsAddress returns the metrics server address in host:port format
func (c *Config) GetMetricsAddress() string {
return fmt.Sprintf("%s:%d", c.GetString("SERVER_HOST"), c.GetInt("METRICS_PORT"))
}
// GetJWTSecret returns the JWT signing secret
func (c *Config) GetJWTSecret() string {
return c.GetString("JWT_SECRET")
}
// IsDevelopment returns true if the environment is development
func (c *Config) IsDevelopment() bool {
env := c.GetString("APP_ENV")
return env == "development" || env == "dev" || env == ""
}
// IsProduction returns true if the environment is production
func (c *Config) IsProduction() bool {
env := c.GetString("APP_ENV")
return env == "production" || env == "prod"
}