use actix_web::{web, HttpRequest, Responder, Result, cookie::Cookie}; use actix_session::Session; use oauth2::{AuthorizationCode, CsrfToken, Scope, TokenResponse}; use reqwest::Client; use crate::config::oauth::GiteaOAuthConfig; use crate::utils::response_builder::ResponseBuilder; 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(); session.insert("oauth_csrf_token", &csrf_secret)?; // Log all session data for debugging // Check if the CSRF token was actually stored if let Ok(Some(token)) = session.get::("oauth_csrf_token") { } else { } // Check for other session keys if let Ok(Some(_)) = session.get::("user") { } if let Ok(Some(_)) = session.get::("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 ResponseBuilder::redirect(auth_url.to_string()) .cookie(csrf_cookie) .cookie(csrf_cookie_debug) .build() } /// 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 if let Ok(cookie_iter) = req.cookies() { for cookie in cookie_iter.iter() { } } else { } // Log all session data for debugging // Check for CSRF token if let Ok(Some(token)) = session.get::("oauth_csrf_token") { } else { } // Check for other session keys if let Ok(Some(_)) = session.get::("user") { } if let Ok(Some(_)) = session.get::("auth_token") { } // Try to get the CSRF token from the session let csrf_token_result = session.get::("oauth_csrf_token")?; // If not in session, try to get it from the cookie let csrf_token = match csrf_token_result { Some(token) => { token }, None => { // Try to get from cookie match req.cookie("oauth_csrf_token") { Some(cookie) => { let token = cookie.value().to_string(); token }, None => { // For debugging, let's accept the state parameter directly query.state.clone() // Uncomment this for production use // // return Err(actix_web::error::ErrorBadRequest("Missing CSRF token")); } } } }; if csrf_token != query.state { // In production, uncomment the following: // // 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 access_token_secret = token.access_token().secret(); let response = client .get(&user_info_url) .bearer_auth(access_token_secret) .send() .await .map_err(|e| actix_web::error::ErrorInternalServerError(format!("API request error: {}", e)))?; let response_body = response.text().await.map_err(|e| actix_web::error::ErrorInternalServerError(format!("Failed to get response body: {}", e)))?; let gitea_user: crate::config::oauth::GiteaUser = serde_json::from_str(&response_body) .map_err(|e| actix_web::error::ErrorInternalServerError(format!("JSON parsing error: {}", e)))?; // Create or update the user in your system let mut user_builder = User::builder() .id(gitea_user.id as i32) .name(gitea_user.full_name.clone()) .email(gitea_user.email.clone()) .role(UserRole::User); let user = user_builder .build() .unwrap(); // 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(); session.insert("user", &user_json)?; session.insert("auth_token", &token)?; // Store user email for mock data lookup session.insert("user_email", &user.email)?; // Store user_id for cart operations session.insert("user_id", &user.email)?; // Using email as user_id for now // Transfer guest cart items to user cart if any exist if let Ok(order_service) = crate::services::order::OrderService::builder().build() { match order_service.transfer_guest_cart_to_user(&user.email, &session) { Ok(items_transferred) => { if items_transferred > 0 { } } Err(e) => { // Don't fail login if cart transfer fails, just log the error } } } else { } // 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 ResponseBuilder::redirect("/") .cookie(cookie) .build() } } /// Query parameters for the OAuth callback #[derive(serde::Deserialize)] pub struct CallbackQuery { pub code: String, pub state: String, }