🔒 fix(security): redact JWT tokens and HMAC secrets in trace logs (auth_service.go) (#88)
Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #88.
This commit is contained in:
@@ -106,7 +106,7 @@ func (s *userServiceImpl) GenerateJWT(ctx context.Context, user *User) (string,
|
|||||||
// Use the most recently added secret (last in the list)
|
// Use the most recently added secret (last in the list)
|
||||||
// This ensures new tokens are signed with the latest secret
|
// This ensures new tokens are signed with the latest secret
|
||||||
signingSecret := validSecrets[len(validSecrets)-1].Secret
|
signingSecret := validSecrets[len(validSecrets)-1].Secret
|
||||||
log.Trace().Ctx(ctx).Str("signing_secret", signingSecret).Bool("is_primary", validSecrets[len(validSecrets)-1].IsPrimary).Msg("Generating JWT with latest secret")
|
log.Trace().Ctx(ctx).Str("signing_secret_fp", tokenFingerprint(signingSecret)).Bool("is_primary", validSecrets[len(validSecrets)-1].IsPrimary).Msg("Generating JWT with latest secret")
|
||||||
|
|
||||||
// Sign and get the complete encoded token as a string
|
// Sign and get the complete encoded token as a string
|
||||||
tokenString, err := token.SignedString([]byte(signingSecret))
|
tokenString, err := token.SignedString([]byte(signingSecret))
|
||||||
@@ -114,20 +114,20 @@ func (s *userServiceImpl) GenerateJWT(ctx context.Context, user *User) (string,
|
|||||||
return "", fmt.Errorf("failed to sign JWT: %w", err)
|
return "", fmt.Errorf("failed to sign JWT: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().Ctx(ctx).Str("token", tokenString).Msg("Generated JWT token")
|
log.Trace().Ctx(ctx).Str("token_fp", tokenFingerprint(tokenString)).Int("token_len", len(tokenString)).Msg("Generated JWT token")
|
||||||
return tokenString, nil
|
return tokenString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateJWT validates a JWT token and returns the user
|
// ValidateJWT validates a JWT token and returns the user
|
||||||
func (s *userServiceImpl) ValidateJWT(ctx context.Context, tokenString string) (*User, error) {
|
func (s *userServiceImpl) ValidateJWT(ctx context.Context, tokenString string) (*User, error) {
|
||||||
log.Trace().Ctx(ctx).Str("token", tokenString).Msg("Validating JWT token")
|
log.Trace().Ctx(ctx).Str("token_fp", tokenFingerprint(tokenString)).Int("token_len", len(tokenString)).Msg("Validating JWT token")
|
||||||
|
|
||||||
// Get all valid secrets for validation
|
// Get all valid secrets for validation
|
||||||
validSecrets := s.secretManager.GetAllValidSecrets()
|
validSecrets := s.secretManager.GetAllValidSecrets()
|
||||||
|
|
||||||
log.Trace().Ctx(ctx).Int("num_secrets", len(validSecrets)).Msg("Validating JWT with multiple secrets")
|
log.Trace().Ctx(ctx).Int("num_secrets", len(validSecrets)).Msg("Validating JWT with multiple secrets")
|
||||||
for i, secret := range validSecrets {
|
for i, secret := range validSecrets {
|
||||||
log.Trace().Ctx(ctx).Int("secret_index", i).Str("secret", secret.Secret).Bool("is_primary", secret.IsPrimary).Msg("Trying secret")
|
log.Trace().Ctx(ctx).Int("secret_index", i).Str("secret_fp", tokenFingerprint(secret.Secret)).Bool("is_primary", secret.IsPrimary).Msg("Trying secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try each valid secret until we find one that works
|
// Try each valid secret until we find one that works
|
||||||
@@ -146,7 +146,7 @@ func (s *userServiceImpl) ValidateJWT(ctx context.Context, tokenString string) (
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err == nil && token.Valid {
|
if err == nil && token.Valid {
|
||||||
log.Trace().Ctx(ctx).Int("secret_index", i).Str("secret", secret.Secret).Msg("JWT validation successful")
|
log.Trace().Ctx(ctx).Int("secret_index", i).Str("secret_fp", tokenFingerprint(secret.Secret)).Msg("JWT validation successful")
|
||||||
parsedToken = token
|
parsedToken = token
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ func (s *userServiceImpl) ValidateJWT(ctx context.Context, tokenString string) (
|
|||||||
// Store the last error for reporting
|
// Store the last error for reporting
|
||||||
validationError = err
|
validationError = err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace().Ctx(ctx).Int("secret_index", i).Str("secret", secret.Secret).Err(err).Msg("JWT validation failed")
|
log.Trace().Ctx(ctx).Int("secret_index", i).Str("secret_fp", tokenFingerprint(secret.Secret)).Err(err).Msg("JWT validation failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,3 +351,13 @@ func (s *PasswordResetServiceImpl) CompletePasswordReset(ctx context.Context, us
|
|||||||
// Complete the password reset
|
// Complete the password reset
|
||||||
return s.repo.CompletePasswordReset(ctx, username, hashedPassword)
|
return s.repo.CompletePasswordReset(ctx, username, hashedPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tokenFingerprint returns the first 16 hex chars of SHA-256 hash of a token/secret.
|
||||||
|
// Used for safe logging correlation without leaking sensitive values.
|
||||||
|
func tokenFingerprint(tok string) string {
|
||||||
|
if tok == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
sum := sha256.Sum256([]byte(tok))
|
||||||
|
return hex.EncodeToString(sum[:8]) // 16 hex chars = 8 bytes
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user