use actix_web::error::ErrorInternalServerError; use redis::{aio::MultiplexedConnection, AsyncCommands}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, env, error::Error}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use log::{info, warn}; use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; use reqwest::Client as HTTPClient; use serde_json::json; // Старые структуры для совместимости с get_id_by_token #[derive(Deserialize)] struct AuthResponse { data: Option, } #[derive(Deserialize)] struct AuthData { validate_jwt_token: Option, } #[derive(Deserialize)] struct ValidateJWTToken { is_valid: bool, claims: Option, } #[derive(Deserialize)] struct Claims { sub: Option, } // Структуры для 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, } /// Получает айди пользователя из токена в заголовке pub async fn get_id_by_token(token: &str) -> Result> { let auth_api_base = env::var("CORE_URL")?; let query_name = "validate_jwt_token"; let operation = "ValidateToken"; let mut headers = HeaderMap::new(); headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); let mut variables = HashMap::>::new(); let mut params = HashMap::::new(); params.insert("token".to_string(), token.to_string()); params.insert("token_type".to_string(), "access_token".to_string()); variables.insert("params".to_string(), params); let gql = json!({ "query": format!("query {}($params: ValidateJWTTokenInput!) {{ {}(params: $params) {{ is_valid claims }} }}", operation, query_name), "operationName": operation, "variables": variables }); let client = HTTPClient::new(); let response = client .post(&auth_api_base) .headers(headers) .json(&gql) .send() .await?; if response.status().is_success() { let auth_response: AuthResponse = response.json().await?; if let Some(auth_data) = auth_response.data { if let Some(validate_jwt_token) = auth_data.validate_jwt_token { if validate_jwt_token.is_valid { if let Some(claims) = validate_jwt_token.claims { if let Some(sub) = claims.sub { return Ok(sub); } } } } } Err(Box::new(std::io::Error::other("Invalid token response"))) } else { Err(Box::new(std::io::Error::other(format!( "Request failed with status: {}", response.status() )))) } } /// Декодирует 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(()) }