307 lines
8.6 KiB
Go
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"
|
|
}
|