🧪 test: add JWT secret rotation BDD scenarios and step implementations (#12)
✨ merge: implement JWT secret rotation with BDD scenario isolation - Implement JWT secret rotation mechanism (closes #8) - Add per-scenario state isolation for BDD tests (closes #14) - Validate password reset workflow via BDD tests (closes #7) - Fix port conflicts in test validation - Add state tracer for debugging test execution - Document BDD isolation strategies in ADR 0025 - Fix PostgreSQL configuration environment variables Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai> Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #12.
This commit is contained in:
@@ -69,8 +69,19 @@ type APIConfig struct {
|
||||
|
||||
// AuthConfig holds authentication configuration
|
||||
type AuthConfig struct {
|
||||
JWTSecret string `mapstructure:"jwt_secret"`
|
||||
AdminMasterPassword string `mapstructure:"admin_master_password"`
|
||||
JWTSecret string `mapstructure:"jwt_secret"`
|
||||
AdminMasterPassword string `mapstructure:"admin_master_password"`
|
||||
JWT JWTConfig `mapstructure:"jwt"`
|
||||
}
|
||||
|
||||
// JWTConfig holds JWT-specific configuration
|
||||
type JWTConfig struct {
|
||||
TTL time.Duration `mapstructure:"ttl"`
|
||||
SecretRetention struct {
|
||||
RetentionFactor float64 `mapstructure:"retention_factor"`
|
||||
MaxRetention time.Duration `mapstructure:"max_retention"`
|
||||
CleanupInterval time.Duration `mapstructure:"cleanup_interval"`
|
||||
} `mapstructure:"secret_retention"`
|
||||
}
|
||||
|
||||
// DatabaseConfig holds database configuration
|
||||
@@ -111,6 +122,11 @@ type SamplerConfig struct {
|
||||
// Configuration priority: file > environment variables > defaults
|
||||
// To specify a custom config file path, set DLC_CONFIG_FILE environment variable
|
||||
func LoadConfig() (*Config, error) {
|
||||
// Check if we're in a test environment - this should NOT be called during BDD tests
|
||||
if os.Getenv("FEATURE") != "" {
|
||||
panic("ERROR: LoadConfig() was called during BDD tests! This should not happen - tests should use createTestConfig() instead.")
|
||||
}
|
||||
|
||||
v := viper.New()
|
||||
|
||||
// Set up initial console logging for config loading messages
|
||||
@@ -140,6 +156,10 @@ func LoadConfig() (*Config, error) {
|
||||
// Auth defaults
|
||||
v.SetDefault("auth.jwt_secret", "default-secret-key-please-change-in-production")
|
||||
v.SetDefault("auth.admin_master_password", "admin123")
|
||||
v.SetDefault("auth.jwt.ttl", 1*time.Hour)
|
||||
v.SetDefault("auth.jwt.secret_retention.retention_factor", 2.0)
|
||||
v.SetDefault("auth.jwt.secret_retention.max_retention", 72*time.Hour)
|
||||
v.SetDefault("auth.jwt.secret_retention.cleanup_interval", 1*time.Hour)
|
||||
|
||||
// Check for custom config file path via environment variable
|
||||
if configFile := os.Getenv("DLC_CONFIG_FILE"); configFile != "" {
|
||||
@@ -182,6 +202,10 @@ func LoadConfig() (*Config, error) {
|
||||
// Auth environment variables
|
||||
v.BindEnv("auth.jwt_secret", "DLC_AUTH_JWT_SECRET")
|
||||
v.BindEnv("auth.admin_master_password", "DLC_AUTH_ADMIN_MASTER_PASSWORD")
|
||||
v.BindEnv("auth.jwt.ttl", "DLC_AUTH_JWT_TTL")
|
||||
v.BindEnv("auth.jwt.secret_retention.retention_factor", "DLC_AUTH_JWT_SECRET_RETENTION_FACTOR")
|
||||
v.BindEnv("auth.jwt.secret_retention.max_retention", "DLC_AUTH_JWT_SECRET_MAX_RETENTION")
|
||||
v.BindEnv("auth.jwt.secret_retention.cleanup_interval", "DLC_AUTH_JWT_SECRET_CLEANUP_INTERVAL")
|
||||
v.BindEnv("telemetry.sampler.type", "DLC_TELEMETRY_SAMPLER_TYPE")
|
||||
v.BindEnv("telemetry.sampler.ratio", "DLC_TELEMETRY_SAMPLER_RATIO")
|
||||
|
||||
@@ -224,6 +248,10 @@ func LoadConfig() (*Config, error) {
|
||||
Bool("telemetry_enabled", config.Telemetry.Enabled).
|
||||
Str("telemetry_service", config.Telemetry.ServiceName).
|
||||
Bool("api_v2_enabled", config.API.V2Enabled).
|
||||
Dur("jwt_ttl", config.GetJWTTTL()).
|
||||
Float64("jwt_retention_factor", config.GetJWTSecretRetentionFactor()).
|
||||
Dur("jwt_max_retention", config.GetJWTSecretMaxRetention()).
|
||||
Dur("jwt_cleanup_interval", config.GetJWTSecretCleanupInterval()).
|
||||
Msg("Configuration loaded")
|
||||
|
||||
return &config, nil
|
||||
@@ -284,6 +312,38 @@ func (c *Config) GetAdminMasterPassword() string {
|
||||
return c.Auth.AdminMasterPassword
|
||||
}
|
||||
|
||||
// GetJWTTTL returns the JWT TTL
|
||||
func (c *Config) GetJWTTTL() time.Duration {
|
||||
if c.Auth.JWT.TTL == 0 {
|
||||
return 1 * time.Hour // Default value
|
||||
}
|
||||
return c.Auth.JWT.TTL
|
||||
}
|
||||
|
||||
// GetJWTSecretRetentionFactor returns the JWT secret retention factor
|
||||
func (c *Config) GetJWTSecretRetentionFactor() float64 {
|
||||
if c.Auth.JWT.SecretRetention.RetentionFactor == 0 {
|
||||
return 2.0 // Default value
|
||||
}
|
||||
return c.Auth.JWT.SecretRetention.RetentionFactor
|
||||
}
|
||||
|
||||
// GetJWTSecretMaxRetention returns the maximum JWT secret retention period
|
||||
func (c *Config) GetJWTSecretMaxRetention() time.Duration {
|
||||
if c.Auth.JWT.SecretRetention.MaxRetention == 0 {
|
||||
return 72 * time.Hour // Default value
|
||||
}
|
||||
return c.Auth.JWT.SecretRetention.MaxRetention
|
||||
}
|
||||
|
||||
// GetJWTSecretCleanupInterval returns the JWT secret cleanup interval
|
||||
func (c *Config) GetJWTSecretCleanupInterval() time.Duration {
|
||||
if c.Auth.JWT.SecretRetention.CleanupInterval == 0 {
|
||||
return 1 * time.Hour // Default value
|
||||
}
|
||||
return c.Auth.JWT.SecretRetention.CleanupInterval
|
||||
}
|
||||
|
||||
// GetLoggingJSON returns whether JSON logging is enabled
|
||||
func (c *Config) GetLoggingJSON() bool {
|
||||
return c.Logging.JSON
|
||||
|
||||
67
pkg/config/config_test.go
Normal file
67
pkg/config/config_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestJWTConfigurationDefaults(t *testing.T) {
|
||||
// Test that JWT configuration has proper defaults
|
||||
config, err := LoadConfig()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, config)
|
||||
|
||||
// Test JWT TTL default
|
||||
expectedTTL := 1 * time.Hour
|
||||
actualTTL := config.GetJWTTTL()
|
||||
assert.Equal(t, expectedTTL, actualTTL, "JWT TTL should default to 1 hour")
|
||||
|
||||
// Test JWT retention factor default
|
||||
expectedFactor := 2.0
|
||||
actualFactor := config.GetJWTSecretRetentionFactor()
|
||||
assert.Equal(t, expectedFactor, actualFactor, "JWT retention factor should default to 2.0")
|
||||
|
||||
// Test JWT max retention default
|
||||
expectedMaxRetention := 72 * time.Hour
|
||||
actualMaxRetention := config.GetJWTSecretMaxRetention()
|
||||
assert.Equal(t, expectedMaxRetention, actualMaxRetention, "JWT max retention should default to 72 hours")
|
||||
|
||||
// Test JWT cleanup interval default
|
||||
expectedCleanupInterval := 1 * time.Hour
|
||||
actualCleanupInterval := config.GetJWTSecretCleanupInterval()
|
||||
assert.Equal(t, expectedCleanupInterval, actualCleanupInterval, "JWT cleanup interval should default to 1 hour")
|
||||
}
|
||||
|
||||
func TestJWTConfigurationCustomValues(t *testing.T) {
|
||||
// Set custom environment variables
|
||||
t.Setenv("DLC_AUTH_JWT_TTL", "2h")
|
||||
t.Setenv("DLC_AUTH_JWT_SECRET_RETENTION_FACTOR", "3.5")
|
||||
t.Setenv("DLC_AUTH_JWT_SECRET_MAX_RETENTION", "120h")
|
||||
t.Setenv("DLC_AUTH_JWT_SECRET_CLEANUP_INTERVAL", "30m")
|
||||
|
||||
config, err := LoadConfig()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, config)
|
||||
|
||||
// Test custom JWT TTL
|
||||
expectedTTL := 2 * time.Hour
|
||||
actualTTL := config.GetJWTTTL()
|
||||
assert.Equal(t, expectedTTL, actualTTL, "JWT TTL should be 2 hours from environment variable")
|
||||
|
||||
// Test custom JWT retention factor
|
||||
expectedFactor := 3.5
|
||||
actualFactor := config.GetJWTSecretRetentionFactor()
|
||||
assert.Equal(t, expectedFactor, actualFactor, "JWT retention factor should be 3.5 from environment variable")
|
||||
|
||||
// Test custom JWT max retention
|
||||
expectedMaxRetention := 120 * time.Hour
|
||||
actualMaxRetention := config.GetJWTSecretMaxRetention()
|
||||
assert.Equal(t, expectedMaxRetention, actualMaxRetention, "JWT max retention should be 120 hours from environment variable")
|
||||
|
||||
// Test custom JWT cleanup interval
|
||||
expectedCleanupInterval := 30 * time.Minute
|
||||
actualCleanupInterval := config.GetJWTSecretCleanupInterval()
|
||||
assert.Equal(t, expectedCleanupInterval, actualCleanupInterval, "JWT cleanup interval should be 30 minutes from environment variable")
|
||||
}
|
||||
Reference in New Issue
Block a user