use actix_web::error::ErrorInternalServerError; use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode}; use log::{info, warn}; use redis::{AsyncCommands, aio::MultiplexedConnection}; use serde::{Deserialize, Serialize}; use std::error::Error; // Структуры для JWT токенов #[derive(Debug, Deserialize)] struct TokenClaims { user_id: String, username: Option, exp: Option, iat: Option, } // Структура для данных пользователя из Redis сессии #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Author { pub user_id: String, pub username: Option, pub token_type: Option, pub created_at: Option, pub last_activity: Option, pub auth_data: Option, pub device_info: Option, } /// Декодирует JWT токен и извлекает claims с проверкой истечения fn decode_jwt_token(token: &str) -> Result> { // В реальном приложении здесь должен быть настоящий секретный ключ let secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "your-secret-key".to_string()); let key = DecodingKey::from_secret(secret.as_ref()); let mut validation = Validation::new(Algorithm::HS256); validation.validate_exp = true; // Включаем проверку истечения срока действия match decode::(token, &key, &validation) { Ok(token_data) => { let claims = token_data.claims; // Дополнительная проверка exp если поле присутствует if let Some(exp) = claims.exp { let current_time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() as usize; if exp < current_time { warn!("JWT token expired: exp={}, current={}", exp, current_time); return Err(Box::new(std::io::Error::other("Token expired"))); } info!("JWT token valid until: {} (current: {})", exp, current_time); } info!( "Successfully decoded and validated JWT token for user: {}", claims.user_id ); Ok(claims) } Err(e) => { warn!("Failed to decode JWT token: {}", e); Err(Box::new(e)) } } } /// Быстро извлекает user_id из JWT токена для работы с квотами pub fn extract_user_id_from_token(token: &str) -> Result> { let claims = decode_jwt_token(token)?; Ok(claims.user_id) } /// Проверяет валидность JWT токена (включая истечение срока действия) pub fn validate_token(token: &str) -> Result> { match decode_jwt_token(token) { Ok(_) => Ok(true), Err(e) => { warn!("Token validation failed: {}", e); Ok(false) } } } /// Получает user_id из JWT токена и базовые данные пользователя pub async fn get_user_by_token( token: &str, redis: &mut MultiplexedConnection, ) -> Result> { // Декодируем JWT токен для получения user_id let claims = decode_jwt_token(token)?; let user_id = &claims.user_id; info!("Extracted user_id from JWT token: {}", user_id); // Проверяем валидность токена через сессию в Redis (опционально) let token_key = format!("session:{}:{}", user_id, token); let session_exists: bool = redis .exists(&token_key) .await .map_err(|e| { warn!("Failed to check session existence in Redis: {}", e); // Не критичная ошибка, продолжаем с базовыми данными }) .unwrap_or(false); if session_exists { // Обновляем last_activity если сессия существует let current_time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); let _: () = redis .hset(&token_key, "last_activity", current_time.to_string()) .await .map_err(|e| { warn!("Failed to update last_activity: {}", e); }) .unwrap_or(()); info!("Updated last_activity for session: {}", token_key); } else { info!("Session not found in Redis, proceeding with JWT-only data"); } // Создаем базовый объект Author с данными из JWT let author = Author { user_id: user_id.clone(), username: claims.username.clone(), token_type: Some("jwt".to_string()), created_at: claims.iat.map(|ts| ts.to_string()), last_activity: Some( std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() .to_string(), ), auth_data: None, device_info: None, }; info!("Successfully created author data for user_id: {}", user_id); Ok(author) } /// Сохраняет имя файла в Redis для пользователя pub async fn user_added_file( redis: &mut MultiplexedConnection, user_id: &str, filename: &str, ) -> Result<(), actix_web::Error> { redis .sadd::<&str, &str, ()>(user_id, filename) .await .map_err(|_| ErrorInternalServerError(format!("Failed to save {} in Redis", filename)))?; // Добавляем имя файла в набор пользователя Ok(()) }