use actix_web::{web, HttpResponse, Responder, Result, http::header, cookie::Cookie}; use actix_session::Session; use tera::Tera; use crate::models::user::{User, LoginCredentials, RegistrationData, UserRole}; use crate::utils::render_template; use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey}; use serde::{Deserialize, Serialize}; use chrono::{Utc, Duration}; use lazy_static::lazy_static; // JWT Claims structure #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { pub sub: String, // Subject (email) pub exp: usize, // Expiration time pub iat: usize, // Issued at pub role: String, // User role } // JWT Secret key lazy_static! { static ref JWT_SECRET: String = std::env::var("JWT_SECRET").unwrap_or_else(|_| "your_jwt_secret_key".to_string()); } /// Controller for handling authentication-related routes pub struct AuthController; #[allow(dead_code)] impl AuthController { /// Generate a JWT token for a user fn generate_token(email: &str, role: &UserRole) -> Result { let role_str = match role { UserRole::Admin => "admin", UserRole::User => "user", }; let expiration = Utc::now() .checked_add_signed(Duration::hours(24)) .expect("valid timestamp") .timestamp() as usize; let claims = Claims { sub: email.to_owned(), exp: expiration, iat: Utc::now().timestamp() as usize, role: role_str.to_string(), }; encode( &Header::default(), &claims, &EncodingKey::from_secret(JWT_SECRET.as_bytes()) ) } /// Validate a JWT token pub fn validate_token(token: &str) -> Result { let validation = Validation::new(Algorithm::HS256); let token_data = decode::( token, &DecodingKey::from_secret(JWT_SECRET.as_bytes()), &validation )?; Ok(token_data.claims) } /// Extract token from session pub fn extract_token_from_session(session: &Session) -> Option { session.get::("auth_token").ok().flatten() } /// Extract token from cookie pub fn extract_token_from_cookie(req: &actix_web::HttpRequest) -> Option { req.cookie("auth_token").map(|c| c.value().to_string()) } /// Check if user is authenticated from session pub async fn is_authenticated(session: &Session) -> Option { if let Some(token) = Self::extract_token_from_session(session) { match Self::validate_token(&token) { Ok(claims) => Some(claims), Err(_) => None, } } else { None } } /// Renders the login page pub async fn login_page(tmpl: web::Data) -> Result { let mut ctx = tera::Context::new(); ctx.insert("active_page", "login"); render_template(&tmpl, "auth/login.html", &ctx) } /// Handles user login pub async fn login( form: web::Form, session: Session, _tmpl: web::Data ) -> Result { // For simplicity, always log in the user without checking credentials // Create a user object with admin role let mut test_user = User::new( "Admin User".to_string(), form.email.clone() ); // Set the ID and admin role test_user.id = Some(1); test_user.role = UserRole::Admin; // Generate JWT token let token = Self::generate_token(&test_user.email, &test_user.role) .map_err(|_| actix_web::error::ErrorInternalServerError("Failed to generate token"))?; // Store user data in session let user_json = serde_json::to_string(&test_user).unwrap(); 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()) } /// Renders the registration page pub async fn register_page(tmpl: web::Data) -> Result { let mut ctx = tera::Context::new(); ctx.insert("active_page", "register"); render_template(&tmpl, "auth/register.html", &ctx) } /// Handles user registration pub async fn register( form: web::Form, session: Session, _tmpl: web::Data ) -> Result { // Skip validation and always create an admin user let mut user = User::new( form.name.clone(), form.email.clone() ); // Set the ID and admin role user.id = Some(1); user.role = UserRole::Admin; // Generate JWT token let token = Self::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)?; // 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()) } /// Handles user logout pub async fn logout(session: Session) -> Result { // Clear the session session.purge(); // Create an expired cookie to remove the JWT token let cookie = Cookie::build("auth_token", "") .path("/") .http_only(true) .max_age(actix_web::cookie::time::Duration::seconds(0)) .finish(); // Redirect to the home page and clear the auth token cookie Ok(HttpResponse::Found() .cookie(cookie) .append_header((header::LOCATION, "/")) .finish()) } }