Files
quoter/tests/auth_integration_test.rs

400 lines
14 KiB
Rust
Raw Permalink Normal View History

[0.6.5] - 2025-09-21 ### 🔐 Улучшенная аутентификация для микросервисов #### ✨ Новые возможности - **Универсальная аутентификация**: Добавлена функция `authenticate_request()` для всех handlers - **Множественные источники токенов**: Поддержка Bearer, X-Session-Token, Cookie - **Redis сессии**: Интеграция с Redis для проверки активных сессий - **Безопасная валидация**: Функция `secure_token_validation()` с проверкой TTL и обновлением активности - **Извлечение токенов**: Универсальная функция `extract_token_from_request()` для всех типов запросов #### 🧪 Тестирование - **14 новых тестов**: Полное покрытие новой логики аутентификации - **Производительность**: Тесты производительности (< 1ms на операцию) - **Безопасность**: Тесты защиты от подозрительных токенов - **Граничные случаи**: Тестирование истекших токенов, неверных форматов - **Интеграция**: Тесты с мокированным Redis #### ♻️ Рефакторинг (DRY & YAGNI) - **Устранение дублирования**: Объединена логика аутентификации из upload.rs и user.rs - **Удаление устаревшего кода**: Убраны `extract_user_id_from_token`, `validate_token`, `get_user_by_token` - **Очистка констант**: Удалены неиспользуемые `MAX_TOKEN_LENGTH`, `MIN_TOKEN_LENGTH` - **Упрощение**: Заменена `extract_and_validate_token` на `authenticate_request` #### ��️ Архитектурные улучшения - **Библиотечная цель**: Добавлена `lib.rs` для тестирования модулей - **Модульность**: Четкое разделение ответственности между модулями - **Единообразие**: Все handlers теперь используют одинаковую логику аутентификации #### 📋 Совместимость - **Обратная совместимость**: Все существующие API endpoints работают без изменений - **Graceful fallback**: Работа без Redis (JWT-only режим) - **Множественные форматы**: Поддержка различных способов передачи токенов
2025-09-22 01:15:35 +03:00
use actix_web::test;
use quoter::auth::{
Author, authenticate_request, extract_token_from_request, secure_token_validation,
};
use std::time::Duration;
/// Тест извлечения токена из различных источников
#[test]
async fn test_extract_token_from_request() {
// Тест Bearer токена в Authorization header
let req = test::TestRequest::default()
.insert_header(("authorization", "Bearer test-jwt-token-123"))
.to_http_request();
let token = extract_token_from_request(&req);
assert_eq!(token, Some("test-jwt-token-123".to_string()));
// Тест кастомного заголовка X-Session-Token
let req = test::TestRequest::default()
.insert_header(("x-session-token", "custom-session-token-456"))
.to_http_request();
let token = extract_token_from_request(&req);
assert_eq!(token, Some("custom-session-token-456".to_string()));
// Тест Cookie
let req = test::TestRequest::default()
.insert_header(("cookie", "session_token=cookie-token-789; other=value"))
.to_http_request();
let token = extract_token_from_request(&req);
assert_eq!(token, Some("cookie-token-789".to_string()));
// Тест отсутствия токена
let req = test::TestRequest::default().to_http_request();
let token = extract_token_from_request(&req);
assert_eq!(token, None);
}
/// Тест валидации формата токена
#[test]
async fn test_token_format_validation() {
// Тест пустого токена
let result = secure_token_validation("", None, Duration::from_secs(5)).await;
assert!(result.is_err());
// Тест слишком короткого токена
let result = secure_token_validation("short", None, Duration::from_secs(5)).await;
assert!(result.is_err());
// Тест невалидного JWT (не 3 части)
let result = secure_token_validation("invalid.jwt", None, Duration::from_secs(5)).await;
assert!(result.is_err());
}
/// Тест создания валидного JWT токена для тестов
fn create_test_jwt_token(user_id: &str, username: Option<&str>) -> String {
use jsonwebtoken::{EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
user_id: String,
username: Option<String>,
exp: usize,
iat: usize,
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as usize;
let claims = Claims {
user_id: user_id.to_string(),
username: username.map(|s| s.to_string()),
exp: now + 3600, // Истекает через час
iat: now,
};
let secret = std::env::var("JWT_SECRET_KEY")
.or_else(|_| std::env::var("JWT_SECRET_KEY"))
.unwrap_or_else(|_| "your-secret-key".to_string());
[0.6.5] - 2025-09-21 ### 🔐 Улучшенная аутентификация для микросервисов #### ✨ Новые возможности - **Универсальная аутентификация**: Добавлена функция `authenticate_request()` для всех handlers - **Множественные источники токенов**: Поддержка Bearer, X-Session-Token, Cookie - **Redis сессии**: Интеграция с Redis для проверки активных сессий - **Безопасная валидация**: Функция `secure_token_validation()` с проверкой TTL и обновлением активности - **Извлечение токенов**: Универсальная функция `extract_token_from_request()` для всех типов запросов #### 🧪 Тестирование - **14 новых тестов**: Полное покрытие новой логики аутентификации - **Производительность**: Тесты производительности (< 1ms на операцию) - **Безопасность**: Тесты защиты от подозрительных токенов - **Граничные случаи**: Тестирование истекших токенов, неверных форматов - **Интеграция**: Тесты с мокированным Redis #### ♻️ Рефакторинг (DRY & YAGNI) - **Устранение дублирования**: Объединена логика аутентификации из upload.rs и user.rs - **Удаление устаревшего кода**: Убраны `extract_user_id_from_token`, `validate_token`, `get_user_by_token` - **Очистка констант**: Удалены неиспользуемые `MAX_TOKEN_LENGTH`, `MIN_TOKEN_LENGTH` - **Упрощение**: Заменена `extract_and_validate_token` на `authenticate_request` #### ��️ Архитектурные улучшения - **Библиотечная цель**: Добавлена `lib.rs` для тестирования модулей - **Модульность**: Четкое разделение ответственности между модулями - **Единообразие**: Все handlers теперь используют одинаковую логику аутентификации #### 📋 Совместимость - **Обратная совместимость**: Все существующие API endpoints работают без изменений - **Graceful fallback**: Работа без Redis (JWT-only режим) - **Множественные форматы**: Поддержка различных способов передачи токенов
2025-09-22 01:15:35 +03:00
let key = EncodingKey::from_secret(secret.as_ref());
encode(&Header::default(), &claims, &key).unwrap()
}
/// Тест валидации валидного JWT токена без Redis
#[tokio::test]
async fn test_valid_jwt_token_without_redis() {
let token = create_test_jwt_token("test-user-123", Some("testuser"));
let result = secure_token_validation(&token, None, Duration::from_secs(5)).await;
assert!(result.is_ok());
let author = result.unwrap();
assert_eq!(author.user_id, "test-user-123");
assert_eq!(author.username, Some("testuser".to_string()));
assert_eq!(author.token_type, Some("jwt".to_string()));
assert_eq!(author.auth_data, Some("jwt_only".to_string()));
}
/// Тест валидации истекшего JWT токена
#[tokio::test]
async fn test_expired_jwt_token() {
use jsonwebtoken::{EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
user_id: String,
username: Option<String>,
exp: usize,
iat: usize,
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as usize;
let claims = Claims {
user_id: "test-user-123".to_string(),
username: Some("testuser".to_string()),
exp: now - 3600, // Истек час назад
iat: now - 7200, // Создан 2 часа назад
};
let secret = std::env::var("JWT_SECRET_KEY")
.or_else(|_| std::env::var("JWT_SECRET_KEY"))
.unwrap_or_else(|_| "your-secret-key".to_string());
[0.6.5] - 2025-09-21 ### 🔐 Улучшенная аутентификация для микросервисов #### ✨ Новые возможности - **Универсальная аутентификация**: Добавлена функция `authenticate_request()` для всех handlers - **Множественные источники токенов**: Поддержка Bearer, X-Session-Token, Cookie - **Redis сессии**: Интеграция с Redis для проверки активных сессий - **Безопасная валидация**: Функция `secure_token_validation()` с проверкой TTL и обновлением активности - **Извлечение токенов**: Универсальная функция `extract_token_from_request()` для всех типов запросов #### 🧪 Тестирование - **14 новых тестов**: Полное покрытие новой логики аутентификации - **Производительность**: Тесты производительности (< 1ms на операцию) - **Безопасность**: Тесты защиты от подозрительных токенов - **Граничные случаи**: Тестирование истекших токенов, неверных форматов - **Интеграция**: Тесты с мокированным Redis #### ♻️ Рефакторинг (DRY & YAGNI) - **Устранение дублирования**: Объединена логика аутентификации из upload.rs и user.rs - **Удаление устаревшего кода**: Убраны `extract_user_id_from_token`, `validate_token`, `get_user_by_token` - **Очистка констант**: Удалены неиспользуемые `MAX_TOKEN_LENGTH`, `MIN_TOKEN_LENGTH` - **Упрощение**: Заменена `extract_and_validate_token` на `authenticate_request` #### ��️ Архитектурные улучшения - **Библиотечная цель**: Добавлена `lib.rs` для тестирования модулей - **Модульность**: Четкое разделение ответственности между модулями - **Единообразие**: Все handlers теперь используют одинаковую логику аутентификации #### 📋 Совместимость - **Обратная совместимость**: Все существующие API endpoints работают без изменений - **Graceful fallback**: Работа без Redis (JWT-only режим) - **Множественные форматы**: Поддержка различных способов передачи токенов
2025-09-22 01:15:35 +03:00
let key = EncodingKey::from_secret(secret.as_ref());
let token = encode(&Header::default(), &claims, &key).unwrap();
let result = secure_token_validation(&token, None, Duration::from_secs(5)).await;
assert!(result.is_err());
}
/// Тест универсальной функции аутентификации
#[tokio::test]
async fn test_authenticate_request() {
let token = create_test_jwt_token("test-user-456", Some("anotheruser"));
// Тест с Bearer токеном
let req = test::TestRequest::default()
.insert_header(("authorization", format!("Bearer {}", token)))
.to_http_request();
let result = authenticate_request(&req, None, Duration::from_secs(5)).await;
assert!(result.is_ok());
let author = result.unwrap();
assert_eq!(author.user_id, "test-user-456");
assert_eq!(author.username, Some("anotheruser".to_string()));
// Тест без токена
let req = test::TestRequest::default().to_http_request();
let result = authenticate_request(&req, None, Duration::from_secs(5)).await;
assert!(result.is_err());
}
/// Тест производительности аутентификации
#[tokio::test]
async fn test_authentication_performance() {
use std::time::Instant;
let token = create_test_jwt_token("perf-user", Some("perfuser"));
let iterations = 1000;
let start = Instant::now();
for _ in 0..iterations {
let req = test::TestRequest::default()
.insert_header(("authorization", format!("Bearer {}", token)))
.to_http_request();
let result = authenticate_request(&req, None, Duration::from_secs(1)).await;
assert!(result.is_ok());
}
let duration = start.elapsed();
let avg_time = duration.as_micros() as f64 / iterations as f64;
println!(
"Authentication performance: {} operations in {:?}, avg: {:.2} μs per auth",
iterations, duration, avg_time
);
// Проверяем, что аутентификация достаточно быстрая (< 1ms на операцию)
assert!(
avg_time < 1000.0,
"Authentication too slow: {:.2} μs per operation",
avg_time
);
}
/// Тест обработки различных заголовков
#[test]
async fn test_header_variations() {
// Тест с пробелами в Bearer токене
let req = test::TestRequest::default()
.insert_header(("authorization", "Bearer token-with-spaces "))
.to_http_request();
let token = extract_token_from_request(&req);
assert_eq!(token, Some("token-with-spaces".to_string()));
// Тест с Authorization без Bearer
let req = test::TestRequest::default()
.insert_header(("authorization", "Basic dGVzdDp0ZXN0"))
.to_http_request();
let token = extract_token_from_request(&req);
assert_eq!(token, None);
// Тест с несколькими cookies
let req = test::TestRequest::default()
.insert_header((
"cookie",
"first=value1; session_token=my-token; last=value2",
))
.to_http_request();
let token = extract_token_from_request(&req);
assert_eq!(token, Some("my-token".to_string()));
}
/// Тест граничных случаев
#[tokio::test]
async fn test_edge_cases() {
// Тест с очень длинным токеном
let long_token = "a".repeat(5000);
let result = secure_token_validation(&long_token, None, Duration::from_secs(1)).await;
assert!(result.is_err()); // Должен быть невалидным JWT
// Тест с нулевым таймаутом
let token = create_test_jwt_token("timeout-user", None);
let result = secure_token_validation(&token, None, Duration::from_secs(0)).await;
// Должен работать, так как JWT валидация не требует Redis
assert!(result.is_ok());
// Тест с очень большим таймаутом
let result = secure_token_validation(&token, None, Duration::from_secs(3600)).await;
assert!(result.is_ok());
}
/// Тест сериализации Author структуры
#[test]
async fn test_author_serialization() {
let author = Author {
user_id: "test-123".to_string(),
username: Some("testuser".to_string()),
token_type: Some("jwt".to_string()),
created_at: Some("1640995200".to_string()),
last_activity: Some("1640995260".to_string()),
auth_data: Some("jwt_only".to_string()),
device_info: None,
};
// Тест JSON сериализации
let json = serde_json::to_string(&author).unwrap();
assert!(json.contains("test-123"));
assert!(json.contains("testuser"));
assert!(json.contains("jwt"));
// Тест десериализации
let deserialized: Author = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.user_id, author.user_id);
assert_eq!(deserialized.username, author.username);
assert_eq!(deserialized.token_type, author.token_type);
}
/// Тест безопасности токенов
#[test]
async fn test_token_security() {
// Тест с подозрительными символами
let suspicious_tokens = vec![
"'; DROP TABLE users; --",
"<script>alert('xss')</script>",
"../../../etc/passwd",
"\\x00\\x01\\x02",
"SELECT * FROM users WHERE id = 1",
];
for suspicious_token in suspicious_tokens {
let req = test::TestRequest::default()
.insert_header(("authorization", format!("Bearer {}", suspicious_token)))
.to_http_request();
let token = extract_token_from_request(&req);
assert_eq!(token, Some(suspicious_token.to_string()));
// Токен должен быть отклонен при валидации
let result = secure_token_validation(suspicious_token, None, Duration::from_secs(1)).await;
assert!(
result.is_err(),
"Suspicious token should be rejected: {}",
suspicious_token
);
}
}
/// Интеграционный тест с мокированным Redis
#[tokio::test]
async fn test_integration_with_mock_redis() {
// Этот тест демонстрирует, как можно тестировать с мокированным Redis
// В реальном проекте здесь был бы мок Redis соединения
let token = create_test_jwt_token("integration-user", Some("integrationuser"));
let req = test::TestRequest::default()
.insert_header(("authorization", format!("Bearer {}", token)))
.to_http_request();
// Тестируем без Redis (None)
let result = authenticate_request(&req, None, Duration::from_secs(5)).await;
assert!(result.is_ok());
let author = result.unwrap();
assert_eq!(author.user_id, "integration-user");
assert_eq!(author.username, Some("integrationuser".to_string()));
assert_eq!(author.auth_data, Some("jwt_only".to_string()));
}
/// Тест обработки ошибок аутентификации
#[tokio::test]
async fn test_authentication_error_handling() {
// Тест с невалидным JWT
let req = test::TestRequest::default()
.insert_header(("authorization", "Bearer invalid.jwt.token"))
.to_http_request();
let result = authenticate_request(&req, None, Duration::from_secs(5)).await;
assert!(result.is_err());
// Проверяем, что ошибка имеет правильный тип
match result {
Err(e) => {
let response = e.error_response();
assert_eq!(response.status(), actix_web::http::StatusCode::UNAUTHORIZED);
}
Ok(_) => panic!("Expected error, got success"),
}
}
/// Тест множественных токенов в одном запросе
#[test]
async fn test_multiple_token_sources() {
// Если есть несколько источников токенов, должен использоваться первый найденный
let req = test::TestRequest::default()
.insert_header(("authorization", "Bearer bearer-token"))
.insert_header(("x-session-token", "session-token"))
.insert_header(("cookie", "session_token=cookie-token"))
.to_http_request();
let token = extract_token_from_request(&req);
// Должен вернуть Bearer токен (приоритет)
assert_eq!(token, Some("bearer-token".to_string()));
}
/// Тест валидации с различными алгоритмами JWT
#[tokio::test]
async fn test_jwt_algorithm_validation() {
// Тест создания токена с неправильным алгоритмом
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
user_id: String,
exp: usize,
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as usize;
let claims = Claims {
user_id: "test-user".to_string(),
exp: now + 3600,
};
// Создаем токен с RS256 вместо HS256
let header = Header {
alg: Algorithm::RS256,
..Default::default()
};
[0.6.5] - 2025-09-21 ### 🔐 Улучшенная аутентификация для микросервисов #### ✨ Новые возможности - **Универсальная аутентификация**: Добавлена функция `authenticate_request()` для всех handlers - **Множественные источники токенов**: Поддержка Bearer, X-Session-Token, Cookie - **Redis сессии**: Интеграция с Redis для проверки активных сессий - **Безопасная валидация**: Функция `secure_token_validation()` с проверкой TTL и обновлением активности - **Извлечение токенов**: Универсальная функция `extract_token_from_request()` для всех типов запросов #### 🧪 Тестирование - **14 новых тестов**: Полное покрытие новой логики аутентификации - **Производительность**: Тесты производительности (< 1ms на операцию) - **Безопасность**: Тесты защиты от подозрительных токенов - **Граничные случаи**: Тестирование истекших токенов, неверных форматов - **Интеграция**: Тесты с мокированным Redis #### ♻️ Рефакторинг (DRY & YAGNI) - **Устранение дублирования**: Объединена логика аутентификации из upload.rs и user.rs - **Удаление устаревшего кода**: Убраны `extract_user_id_from_token`, `validate_token`, `get_user_by_token` - **Очистка констант**: Удалены неиспользуемые `MAX_TOKEN_LENGTH`, `MIN_TOKEN_LENGTH` - **Упрощение**: Заменена `extract_and_validate_token` на `authenticate_request` #### ��️ Архитектурные улучшения - **Библиотечная цель**: Добавлена `lib.rs` для тестирования модулей - **Модульность**: Четкое разделение ответственности между модулями - **Единообразие**: Все handlers теперь используют одинаковую логику аутентификации #### 📋 Совместимость - **Обратная совместимость**: Все существующие API endpoints работают без изменений - **Graceful fallback**: Работа без Redis (JWT-only режим) - **Множественные форматы**: Поддержка различных способов передачи токенов
2025-09-22 01:15:35 +03:00
let secret = "wrong-secret";
let key = EncodingKey::from_secret(secret.as_ref());
// Этот токен должен быть отклонен, так как мы используем неправильный алгоритм
if let Ok(token) = encode(&header, &claims, &key) {
let result = secure_token_validation(&token, None, Duration::from_secs(5)).await;
assert!(result.is_err());
}
}