🧪 test: implement JWT secret rotation BDD tests
- Fix admin handler to handle flexible boolean parsing - Modify GenerateJWT to use latest secret for signing - Update JWT secret manager for proper expiration handling - Fix BDD test steps to use actual tokens instead of hardcoded ones - Add comprehensive debug logging for JWT operations Resolves JWT secret rotation feature implementation Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
95
pkg/user/jwt_manager.go
Normal file
95
pkg/user/jwt_manager.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// JWTSecret represents a JWT secret with metadata
|
||||
type JWTSecret struct {
|
||||
Secret string
|
||||
IsPrimary bool
|
||||
CreatedAt time.Time
|
||||
ExpiresAt *time.Time // Optional expiration time
|
||||
}
|
||||
|
||||
// JWTSecretManager manages multiple JWT secrets for rotation
|
||||
type JWTSecretManager struct {
|
||||
secrets []JWTSecret
|
||||
primarySecret string
|
||||
}
|
||||
|
||||
// NewJWTSecretManager creates a new JWT secret manager
|
||||
func NewJWTSecretManager(initialSecret string) *JWTSecretManager {
|
||||
return &JWTSecretManager{
|
||||
secrets: []JWTSecret{
|
||||
{
|
||||
Secret: initialSecret,
|
||||
IsPrimary: true,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
},
|
||||
primarySecret: initialSecret,
|
||||
}
|
||||
}
|
||||
|
||||
// AddSecret adds a new JWT secret
|
||||
func (m *JWTSecretManager) AddSecret(secret string, isPrimary bool, expiresIn time.Duration) {
|
||||
var expiresAt *time.Time
|
||||
if expiresIn > 0 {
|
||||
expirationTime := time.Now().Add(expiresIn)
|
||||
expiresAt = &expirationTime
|
||||
}
|
||||
// If expiresIn is 0 or negative, expiresAt remains nil (no expiration)
|
||||
|
||||
m.secrets = append(m.secrets, JWTSecret{
|
||||
Secret: secret,
|
||||
IsPrimary: isPrimary,
|
||||
CreatedAt: time.Now(),
|
||||
ExpiresAt: expiresAt,
|
||||
})
|
||||
|
||||
if isPrimary {
|
||||
m.primarySecret = secret
|
||||
}
|
||||
}
|
||||
|
||||
// RotateToSecret rotates to a new primary secret
|
||||
func (m *JWTSecretManager) RotateToSecret(newSecret string) {
|
||||
// Mark existing primary as non-primary
|
||||
for i, secret := range m.secrets {
|
||||
if secret.IsPrimary {
|
||||
m.secrets[i].IsPrimary = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Add new secret as primary
|
||||
m.AddSecret(newSecret, true, 0) // No expiration for primary
|
||||
}
|
||||
|
||||
// GetPrimarySecret returns the current primary secret
|
||||
func (m *JWTSecretManager) GetPrimarySecret() string {
|
||||
return m.primarySecret
|
||||
}
|
||||
|
||||
// GetAllValidSecrets returns all valid (non-expired) secrets
|
||||
func (m *JWTSecretManager) GetAllValidSecrets() []JWTSecret {
|
||||
var validSecrets []JWTSecret
|
||||
now := time.Now()
|
||||
|
||||
for _, secret := range m.secrets {
|
||||
if secret.ExpiresAt == nil || secret.ExpiresAt.After(now) {
|
||||
validSecrets = append(validSecrets, secret)
|
||||
}
|
||||
}
|
||||
|
||||
return validSecrets
|
||||
}
|
||||
|
||||
// GetSecretByIndex returns a secret by index for testing
|
||||
func (m *JWTSecretManager) GetSecretByIndex(index int) (string, bool) {
|
||||
if index < 0 || index >= len(m.secrets) {
|
||||
return "", false
|
||||
}
|
||||
return m.secrets[index].Secret, true
|
||||
}
|
||||
Reference in New Issue
Block a user