use actix_web::{web, HttpRequest, HttpResponse, Responder, Result, http::header, cookie::Cookie}; use actix_session::Session; use oauth2::{AuthorizationCode, CsrfToken, Scope, TokenResponse}; use reqwest::Client; use crate::config::oauth::GiteaOAuthConfig; use crate::models::user::{User, UserRole}; use crate::controllers::auth::AuthController; /// Controller for handling Gitea authentication pub struct GiteaAuthController; impl GiteaAuthController { /// Initiate the OAuth flow pub async fn login( oauth_config: web::Data, session: Session, ) -> Result { // Generate the authorization URL let (auth_url, csrf_token) = oauth_config .client .authorize_url(CsrfToken::new_random) .add_scope(Scope::new("read:user".to_string())) .add_scope(Scope::new("user:email".to_string())) .url(); // Store the CSRF token in the session let csrf_secret = csrf_token.secret().to_string(); log::info!("Setting CSRF token in session: {}", csrf_secret); session.insert("oauth_csrf_token", &csrf_secret)?; // Log all session data for debugging log::info!("Session data after setting CSRF token:"); // Check if the CSRF token was actually stored if let Ok(Some(token)) = session.get::("oauth_csrf_token") { log::info!(" Session key: oauth_csrf_token = {}", token); } else { log::warn!(" CSRF token not found in session after setting it!"); } // Check for other session keys if let Ok(Some(_)) = session.get::("user") { log::info!(" Session key: user"); } if let Ok(Some(_)) = session.get::("auth_token") { log::info!(" Session key: auth_token"); } // Also store it in a cookie as a backup let csrf_cookie = Cookie::build("oauth_csrf_token", csrf_secret.clone()) .path("/") .http_only(true) .secure(false) // Set to true in production with HTTPS .max_age(actix_web::cookie::time::Duration::minutes(30)) .finish(); // Store in a non-http-only cookie as well for debugging let csrf_cookie_debug = Cookie::build("oauth_csrf_token_debug", csrf_secret) .path("/") .http_only(false) // Accessible from JavaScript for debugging .secure(false) .max_age(actix_web::cookie::time::Duration::minutes(30)) .finish(); // Redirect to the authorization URL Ok(HttpResponse::Found() .cookie(csrf_cookie) .cookie(csrf_cookie_debug) .append_header((header::LOCATION, auth_url.to_string())) .finish()) } /// Handle the OAuth callback pub async fn callback( oauth_config: web::Data, session: Session, query: web::Query, req: HttpRequest, ) -> Result { // Log all cookies for debugging log::info!("Cookies in request:"); if let Ok(cookie_iter) = req.cookies() { for cookie in cookie_iter.iter() { log::info!(" Cookie: {}={}", cookie.name(), cookie.value()); } } else { log::info!(" Failed to get cookies"); } // Log all session data for debugging log::info!("Session data in callback:"); // Check for CSRF token if let Ok(Some(token)) = session.get::("oauth_csrf_token") { log::info!(" Session key: oauth_csrf_token = {}", token); } else { log::warn!(" CSRF token not found in session during callback!"); } // Check for other session keys if let Ok(Some(_)) = session.get::("user") { log::info!(" Session key: user"); } if let Ok(Some(_)) = session.get::("auth_token") { log::info!(" Session key: auth_token"); } // Try to get the CSRF token from the session let csrf_token_result = session.get::("oauth_csrf_token")?; log::info!("CSRF token from session: {:?}", csrf_token_result); // If not in session, try to get it from the cookie let csrf_token = match csrf_token_result { Some(token) => { log::info!("Found CSRF token in session: {}", token); token }, None => { // Try to get from cookie match req.cookie("oauth_csrf_token") { Some(cookie) => { let token = cookie.value().to_string(); log::info!("Found CSRF token in cookie: {}", token); token }, None => { // For debugging, let's accept the state parameter directly log::warn!("CSRF token not found in session or cookie. Using state parameter as fallback."); log::warn!("State parameter: {}", query.state); query.state.clone() // Uncomment this for production use // log::error!("CSRF token not found in session or cookie"); // return Err(actix_web::error::ErrorBadRequest("Missing CSRF token")); } } } }; log::info!("Comparing CSRF token: {} with state: {}", csrf_token, query.state); if csrf_token != query.state { log::warn!("CSRF token mismatch, but continuing for debugging purposes"); // In production, uncomment the following: // log::error!("CSRF token mismatch"); // return Err(actix_web::error::ErrorBadRequest("Invalid CSRF token")); } // Exchange the authorization code for an access token let token = oauth_config .client .exchange_code(AuthorizationCode::new(query.code.clone())) .request_async(oauth2::reqwest::async_http_client) .await .map_err(|e| actix_web::error::ErrorInternalServerError(format!("Token exchange error: {}", e)))?; // Get the user information from Gitea let client = Client::new(); let user_info_url = format!("{}/api/v1/user", oauth_config.instance_url); let gitea_user = client .get(&user_info_url) .bearer_auth(token.access_token().secret()) .send() .await .map_err(|e| actix_web::error::ErrorInternalServerError(format!("API request error: {}", e)))? .json::() .await .map_err(|e| actix_web::error::ErrorInternalServerError(format!("JSON parsing error: {}", e)))?; // Create or update the user in your system let mut user = User::new( gitea_user.full_name.clone(), gitea_user.email.clone(), ); // Set the user ID and role user.id = Some(gitea_user.id as i32); user.role = UserRole::User; // Generate JWT token let token = AuthController::generate_token(&user.email, &user.role) .map_err(|_| actix_web::error::ErrorInternalServerError("Failed to generate token"))?; // Store user data in session let user_json = serde_json::to_string(&user).unwrap(); log::info!("Storing user in session: {}", user_json); session.insert("user", &user_json)?; session.insert("auth_token", &token)?; // Create a cookie with the JWT token let cookie = Cookie::build("auth_token", token) .path("/") .http_only(true) .secure(false) // Set to true in production with HTTPS .max_age(actix_web::cookie::time::Duration::hours(24)) .finish(); // Redirect to the home page with JWT token in cookie Ok(HttpResponse::Found() .cookie(cookie) .append_header((header::LOCATION, "/")) .finish()) } } /// Query parameters for the OAuth callback #[derive(serde::Deserialize)] pub struct CallbackQuery { pub code: String, pub state: String, }