Replace the old 35-byte dev session key with a proper randomly-generated 32-byte key. Also ensure dev mode actually falls back to DevSessionKey when SESSION_KEY is not set in the environment, rather than leaving SessionKey empty and failing at session creation. Update tests to remove the old key references.
170 lines
5.1 KiB
Go
170 lines
5.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"go.uber.org/fx"
|
|
"sneak.berlin/go/webhooker/internal/globals"
|
|
"sneak.berlin/go/webhooker/internal/logger"
|
|
pkgconfig "sneak.berlin/go/webhooker/pkg/config"
|
|
|
|
// Populates the environment from a ./.env file automatically for
|
|
// development configuration. Kept in one place only (here).
|
|
_ "github.com/joho/godotenv/autoload"
|
|
)
|
|
|
|
const (
|
|
// EnvironmentDev represents development environment
|
|
EnvironmentDev = "dev"
|
|
// EnvironmentProd represents production environment
|
|
EnvironmentProd = "prod"
|
|
// DevSessionKey is an insecure default 32-byte session key for development.
|
|
// NEVER use this key in production. It exists solely so that `make dev`
|
|
// works without requiring SESSION_KEY to be set.
|
|
DevSessionKey = "0oaEeAhFe7aXn9DkZ/oiSN+QbAxXxcoxAnGX9TADkp8="
|
|
)
|
|
|
|
// nolint:revive // ConfigParams is a standard fx naming convention
|
|
type ConfigParams struct {
|
|
fx.In
|
|
Globals *globals.Globals
|
|
Logger *logger.Logger
|
|
}
|
|
|
|
type Config struct {
|
|
DBURL string
|
|
Debug bool
|
|
MaintenanceMode bool
|
|
DevelopmentMode bool
|
|
DevAdminUsername string
|
|
DevAdminPassword string
|
|
Environment string
|
|
MetricsPassword string
|
|
MetricsUsername string
|
|
Port int
|
|
SentryDSN string
|
|
SessionKey string
|
|
params *ConfigParams
|
|
log *slog.Logger
|
|
}
|
|
|
|
// IsDev returns true if running in development environment
|
|
func (c *Config) IsDev() bool {
|
|
return c.Environment == EnvironmentDev
|
|
}
|
|
|
|
// IsProd returns true if running in production environment
|
|
func (c *Config) IsProd() bool {
|
|
return c.Environment == EnvironmentProd
|
|
}
|
|
|
|
// envString returns the env var value if set, otherwise falls back to pkgconfig.
|
|
func envString(envKey, configKey string) string {
|
|
if v := os.Getenv(envKey); v != "" {
|
|
return v
|
|
}
|
|
return pkgconfig.GetString(configKey)
|
|
}
|
|
|
|
// envSecretString returns the env var value if set, otherwise falls back to pkgconfig secrets.
|
|
func envSecretString(envKey, configKey string) string {
|
|
if v := os.Getenv(envKey); v != "" {
|
|
return v
|
|
}
|
|
return pkgconfig.GetSecretString(configKey)
|
|
}
|
|
|
|
// envBool returns the env var value parsed as bool, otherwise falls back to pkgconfig.
|
|
func envBool(envKey, configKey string) bool {
|
|
if v := os.Getenv(envKey); v != "" {
|
|
return strings.EqualFold(v, "true") || v == "1"
|
|
}
|
|
return pkgconfig.GetBool(configKey)
|
|
}
|
|
|
|
// envInt returns the env var value parsed as int, otherwise falls back to pkgconfig.
|
|
func envInt(envKey, configKey string, defaultValue ...int) int {
|
|
if v := os.Getenv(envKey); v != "" {
|
|
if i, err := strconv.Atoi(v); err == nil {
|
|
return i
|
|
}
|
|
}
|
|
return pkgconfig.GetInt(configKey, defaultValue...)
|
|
}
|
|
|
|
// nolint:revive // lc parameter is required by fx even if unused
|
|
func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
|
|
log := params.Logger.Get()
|
|
|
|
// Determine environment from WEBHOOKER_ENVIRONMENT env var, default to dev
|
|
environment := os.Getenv("WEBHOOKER_ENVIRONMENT")
|
|
if environment == "" {
|
|
environment = EnvironmentDev
|
|
}
|
|
|
|
// Validate environment
|
|
if environment != EnvironmentDev && environment != EnvironmentProd {
|
|
return nil, fmt.Errorf("WEBHOOKER_ENVIRONMENT must be either '%s' or '%s', got '%s'",
|
|
EnvironmentDev, EnvironmentProd, environment)
|
|
}
|
|
|
|
// Set the environment in the config package (for fallback resolution)
|
|
pkgconfig.SetEnvironment(environment)
|
|
|
|
// Load configuration values — env vars take precedence over config.yaml
|
|
s := &Config{
|
|
DBURL: envString("DBURL", "dburl"),
|
|
Debug: envBool("DEBUG", "debug"),
|
|
MaintenanceMode: envBool("MAINTENANCE_MODE", "maintenanceMode"),
|
|
DevelopmentMode: envBool("DEVELOPMENT_MODE", "developmentMode"),
|
|
DevAdminUsername: envString("DEV_ADMIN_USERNAME", "devAdminUsername"),
|
|
DevAdminPassword: envString("DEV_ADMIN_PASSWORD", "devAdminPassword"),
|
|
Environment: environment,
|
|
MetricsUsername: envString("METRICS_USERNAME", "metricsUsername"),
|
|
MetricsPassword: envString("METRICS_PASSWORD", "metricsPassword"),
|
|
Port: envInt("PORT", "port", 8080),
|
|
SentryDSN: envSecretString("SENTRY_DSN", "sentryDSN"),
|
|
SessionKey: envSecretString("SESSION_KEY", "sessionKey"),
|
|
log: log,
|
|
params: ¶ms,
|
|
}
|
|
|
|
// Validate database URL
|
|
if s.DBURL == "" {
|
|
return nil, fmt.Errorf("database URL (DBURL) is required")
|
|
}
|
|
|
|
// In production, require session key
|
|
if s.IsProd() && s.SessionKey == "" {
|
|
return nil, fmt.Errorf("SESSION_KEY is required in production environment")
|
|
}
|
|
|
|
// In development mode, fall back to the insecure default key
|
|
if s.IsDev() && s.SessionKey == "" {
|
|
s.SessionKey = DevSessionKey
|
|
log.Warn("Using insecure default session key for development mode")
|
|
}
|
|
|
|
if s.Debug {
|
|
params.Logger.EnableDebugLogging()
|
|
}
|
|
|
|
// Log configuration summary (without secrets)
|
|
log.Info("Configuration loaded",
|
|
"environment", s.Environment,
|
|
"port", s.Port,
|
|
"debug", s.Debug,
|
|
"maintenanceMode", s.MaintenanceMode,
|
|
"developmentMode", s.DevelopmentMode,
|
|
"hasSessionKey", s.SessionKey != "",
|
|
"hasSentryDSN", s.SentryDSN != "",
|
|
"hasMetricsAuth", s.MetricsUsername != "" && s.MetricsPassword != "",
|
|
)
|
|
|
|
return s, nil
|
|
}
|