🧪 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:
@@ -3,6 +3,7 @@ package steps
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"dance-lessons-coach/pkg/bdd/testserver"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
type AuthSteps struct {
|
||||
client *testserver.Client
|
||||
lastToken string
|
||||
firstToken string // Store the first token for rotation testing
|
||||
lastUserID uint
|
||||
}
|
||||
|
||||
@@ -334,8 +336,12 @@ func (s *AuthSteps) iUseAMalformedJWTTokenForAuthentication() error {
|
||||
|
||||
// JWT Validation Steps
|
||||
func (s *AuthSteps) iValidateTheReceivedJWTToken() error {
|
||||
// Extract and parse the JWT token
|
||||
return s.iShouldReceiveAValidJWTToken()
|
||||
// Validate the received JWT token by sending it to the validation endpoint
|
||||
if s.lastToken == "" {
|
||||
return fmt.Errorf("no token to validate")
|
||||
}
|
||||
|
||||
return s.client.Request("POST", "/api/v1/auth/validate", map[string]string{"token": s.lastToken})
|
||||
}
|
||||
|
||||
func (s *AuthSteps) theTokenShouldBeValid() error {
|
||||
@@ -344,23 +350,53 @@ func (s *AuthSteps) theTokenShouldBeValid() error {
|
||||
return fmt.Errorf("expected status 200, got %d", s.client.GetLastStatusCode())
|
||||
}
|
||||
|
||||
// Check if response contains a token
|
||||
// Check if response contains validation confirmation
|
||||
body := string(s.client.GetLastBody())
|
||||
if !strings.Contains(body, "token") {
|
||||
return fmt.Errorf("expected response to contain token, got %s", body)
|
||||
if !strings.Contains(body, "valid") {
|
||||
return fmt.Errorf("expected response to contain valid token confirmation, got %s", body)
|
||||
}
|
||||
|
||||
// Extract and parse the JWT token
|
||||
if err := s.iShouldReceiveAValidJWTToken(); err != nil {
|
||||
return fmt.Errorf("failed to parse JWT token: %w", err)
|
||||
// Only try to parse a JWT token if this is an authentication response (contains "token" field)
|
||||
if strings.Contains(body, "token") {
|
||||
// Extract and parse the JWT token
|
||||
if err := s.iShouldReceiveAValidJWTToken(); err != nil {
|
||||
return fmt.Errorf("failed to parse JWT token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, the token is valid and parsed successfully
|
||||
// If we got here, the token is valid
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AuthSteps) itShouldContainTheCorrectUserID() error {
|
||||
// Verify that we have a stored user ID from the last token
|
||||
// Check if this is a token validation response (contains user_id)
|
||||
body := string(s.client.GetLastBody())
|
||||
if strings.Contains(body, "user_id") {
|
||||
// This is a token validation response, extract user_id from it
|
||||
startIdx := strings.Index(body, `"user_id":`)
|
||||
if startIdx == -1 {
|
||||
return fmt.Errorf("no user_id found in validation response: %s", body)
|
||||
}
|
||||
startIdx += 10 // Skip "user_id":
|
||||
endIdx := strings.Index(body[startIdx:], ",")
|
||||
if endIdx == -1 {
|
||||
endIdx = strings.Index(body[startIdx:], "}")
|
||||
}
|
||||
if endIdx == -1 {
|
||||
return fmt.Errorf("malformed user_id in validation response: %s", body)
|
||||
}
|
||||
userIDStr := strings.TrimSpace(body[startIdx : startIdx+endIdx])
|
||||
userID, err := strconv.Atoi(userIDStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse user_id from validation response: %s", body)
|
||||
}
|
||||
if userID <= 0 {
|
||||
return fmt.Errorf("invalid user_id in validation response: %d", userID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, verify that we have a stored user ID from the last token
|
||||
if s.lastUserID == 0 {
|
||||
return fmt.Errorf("no user ID stored from previous token")
|
||||
}
|
||||
@@ -439,7 +475,17 @@ func (s *AuthSteps) iShouldReceiveAValidJWTTokenSignedWithThePrimarySecret() err
|
||||
}
|
||||
|
||||
// Extract and store the token
|
||||
return s.iShouldReceiveAValidJWTToken()
|
||||
err := s.iShouldReceiveAValidJWTToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store this as the first token if not already set (for rotation testing)
|
||||
if s.firstToken == "" {
|
||||
s.firstToken = s.lastToken
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AuthSteps) iValidateAJWTTokenSignedWithTheSecondarySecret() error {
|
||||
@@ -516,24 +562,26 @@ func (s *AuthSteps) iUseAJWTTokenSignedWithTheExpiredSecondarySecretForAuthentic
|
||||
}
|
||||
|
||||
func (s *AuthSteps) iUseTheOldJWTTokenSignedWithPrimarySecret() error {
|
||||
// This step assumes we have stored the old token from previous authentication
|
||||
// For now, we'll simulate by using a token that would have been signed with primary secret
|
||||
oldPrimaryToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsImV4cCI6MjIwMDAwMDAwMCwiaXNzIjoiZGFuY2UtbGVzc29ucy1jb2FjaCJ9.old-primary-secret-signature"
|
||||
// Use the actual token from the first authentication (stored in firstToken)
|
||||
if s.firstToken == "" {
|
||||
return fmt.Errorf("no old token stored from first authentication")
|
||||
}
|
||||
|
||||
// Set the Authorization header with the old primary token
|
||||
req := map[string]string{"token": oldPrimaryToken}
|
||||
req := map[string]string{"token": s.firstToken}
|
||||
return s.client.RequestWithHeader("POST", "/api/v1/auth/validate", req, map[string]string{
|
||||
"Authorization": "Bearer " + oldPrimaryToken,
|
||||
"Authorization": "Bearer " + s.firstToken,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *AuthSteps) iValidateTheOldJWTTokenSignedWithPrimarySecret() error {
|
||||
// This would validate the old token signed with primary secret
|
||||
// For now, we'll simulate by validating a token
|
||||
oldPrimaryToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsImV4cCI6MjIwMDAwMDAwMCwiaXNzIjoiZGFuY2UtbGVzc29ucy1jb2FjaCJ9.old-primary-secret-signature"
|
||||
// Use the actual token from the first authentication (stored in firstToken)
|
||||
if s.firstToken == "" {
|
||||
return fmt.Errorf("no old token stored from first authentication")
|
||||
}
|
||||
|
||||
return s.client.RequestWithHeader("POST", "/api/v1/auth/validate", map[string]string{"token": oldPrimaryToken}, map[string]string{
|
||||
"Authorization": "Bearer " + oldPrimaryToken,
|
||||
return s.client.RequestWithHeader("POST", "/api/v1/auth/validate", map[string]string{"token": s.firstToken}, map[string]string{
|
||||
"Authorization": "Bearer " + s.firstToken,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user