hostbasket/actix_mvc_app/src/controllers/auth.rs
2025-05-18 09:48:28 +03:00

206 lines
6.9 KiB
Rust

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<String, jsonwebtoken::errors::Error> {
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<Claims, jsonwebtoken::errors::Error> {
let validation = Validation::new(Algorithm::HS256);
let token_data = decode::<Claims>(
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<String> {
session.get::<String>("auth_token").ok().flatten()
}
/// Extract token from cookie
pub fn extract_token_from_cookie(req: &actix_web::HttpRequest) -> Option<String> {
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<Claims> {
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<Tera>) -> Result<impl Responder> {
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<LoginCredentials>,
session: Session,
_tmpl: web::Data<Tera>
) -> Result<impl Responder> {
// 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<Tera>) -> Result<impl Responder> {
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<RegistrationData>,
session: Session,
_tmpl: web::Data<Tera>
) -> Result<impl Responder> {
// 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<impl Responder> {
// 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())
}
}