help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
rust-web-server
/
src
RSK World
rust-web-server
Rust Web Server - High-Performance Async Web Server + WebSocket Support + JWT Authentication + File Upload + Memory Safety + Educational Design
src
  • auth.rs15.7 KB
  • config.rs2.9 KB
  • error.rs5.2 KB
  • file_upload.rs19 KB
  • handlers.rs12.8 KB
  • lib.rs1.8 KB
  • main.rs6 KB
  • middleware.rs6.2 KB
  • static_files.rs9.9 KB
  • utils.rs9.6 KB
  • websocket.rs15.3 KB
features.mdauth.rs
src/auth.rs
Raw Download
Find: Go to:
/*
 * Authentication Module - Rust Web Server
 * 
 * Created by RSK World (https://rskworld.in)
 * Founder: Molla Samser
 * Designer & Tester: Rima Khatun
 * 
 * Contact:
 * - Email: hello@rskworld.in, support@rskworld.in
 * - Phone: +91 93305 39277
 * - Address: Nutanhat, Mongolkote, Purba Burdwan, West Bengal, India, 713147
 * 
 * © 2026 RSK World. All rights reserved.
 * Content used for educational purposes only.
 */

use bcrypt::{hash, verify, DEFAULT_COST};
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};
use uuid::Uuid;

use crate::error::{ServerError, ServerResult};

/// User role types
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum UserRole {
    Admin,
    User,
    Guest,
}

/// User information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: String,
    pub username: String,
    pub email: String,
    pub password_hash: String,
    pub role: UserRole,
    pub created_at: String,
    pub last_login: Option<String>,
    pub is_active: bool,
}

/// Login request
#[derive(Debug, Deserialize)]
pub struct LoginRequest {
    pub username: String,
    pub password: String,
}

/// Register request
#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
    pub username: String,
    pub email: String,
    pub password: String,
    pub confirm_password: String,
}

/// Authentication response
#[derive(Debug, Serialize)]
pub struct AuthResponse {
    pub token: String,
    pub user: UserInfo,
    pub expires_in: i64,
}

/// Public user information
#[derive(Debug, Serialize)]
pub struct UserInfo {
    pub id: String,
    pub username: String,
    pub email: String,
    pub role: UserRole,
    pub created_at: String,
}

/// JWT Claims
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String, // Subject (user ID)
    pub username: String,
    pub email: String,
    pub role: UserRole,
    pub exp: i64, // Expiration time
    pub iat: i64, // Issued at
    pub jti: String, // JWT ID
}

/// Session information
#[derive(Debug, Clone)]
pub struct Session {
    pub user_id: String,
    pub token: String,
    pub expires_at: chrono::DateTime<Utc>,
    pub created_at: chrono::DateTime<Utc>,
}

/// Authentication manager
pub struct AuthManager {
    /// In-memory user storage (in production, use a database)
    users: Arc<RwLock<HashMap<String, User>>>,
    /// Active sessions
    sessions: Arc<RwLock<HashMap<String, Session>>>,
    /// JWT secret key
    jwt_secret: String,
    /// Token expiration time (hours)
    token_expiration_hours: i64,
}

impl AuthManager {
    /// Create a new authentication manager
    pub fn new(jwt_secret: String, token_expiration_hours: i64) -> Self {
        Self {
            users: Arc::new(RwLock::new(HashMap::new())),
            sessions: Arc::new(RwLock::new(HashMap::new())),
            jwt_secret,
            token_expiration_hours,
        }
    }

    /// Register a new user
    pub async fn register(&self, request: RegisterRequest) -> ServerResult<AuthResponse> {
        // Validate request
        if request.password != request.confirm_password {
            return Err(ServerError::BadRequest("Passwords do not match".to_string()));
        }

        if request.password.len() < 8 {
            return Err(ServerError::BadRequest("Password must be at least 8 characters".to_string()));
        }

        let mut users = self.users.write().await;

        // Check if username already exists
        if users.values().any(|u| u.username == request.username) {
            return Err(ServerError::BadRequest("Username already exists".to_string()));
        }

        // Check if email already exists
        if users.values().any(|u| u.email == request.email) {
            return Err(ServerError::BadRequest("Email already exists".to_string()));
        }

        // Hash password
        let password_hash = hash(&request.password, DEFAULT_COST)
            .map_err(|e| ServerError::Internal(format!("Failed to hash password: {}", e)))?;

        // Create user
        let user = User {
            id: Uuid::new_v4().to_string(),
            username: request.username.clone(),
            email: request.email.clone(),
            password_hash,
            role: UserRole::User,
            created_at: Utc::now().to_rfc3339(),
            last_login: None,
            is_active: true,
        };

        users.insert(user.id.clone(), user.clone());
        drop(users);

        info!("User registered: {}", request.username);

        // Generate token
        let token = self.generate_token(&user)?;
        let expires_in = self.token_expiration_hours * 3600; // Convert to seconds

        // Store session
        let session = Session {
            user_id: user.id.clone(),
            token: token.clone(),
            expires_at: Utc::now() + Duration::hours(self.token_expiration_hours),
            created_at: Utc::now(),
        };

        let mut sessions = self.sessions.write().await;
        sessions.insert(token.clone(), session);
        drop(sessions);

        Ok(AuthResponse {
            token,
            user: UserInfo {
                id: user.id,
                username: user.username,
                email: user.email,
                role: user.role,
                created_at: user.created_at,
            },
            expires_in,
        })
    }

    /// Login user
    pub async fn login(&self, request: LoginRequest) -> ServerResult<AuthResponse> {
        let users = self.users.read().await;

        // Find user by username
        let user = users
            .values()
            .find(|u| u.username == request.username && u.is_active)
            .ok_or_else(|| ServerError::Unauthorized("Invalid credentials".to_string()))?;

        // Verify password
        if !verify(&request.password, &user.password_hash).unwrap_or(false) {
            return Err(ServerError::Unauthorized("Invalid credentials".to_string()));
        }

        drop(users);

        info!("User logged in: {}", request.username);

        // Generate token
        let token = self.generate_token(user)?;
        let expires_in = self.token_expiration_hours * 3600; // Convert to seconds

        // Store session
        let session = Session {
            user_id: user.id.clone(),
            token: token.clone(),
            expires_at: Utc::now() + Duration::hours(self.token_expiration_hours),
            created_at: Utc::now(),
        };

        let mut sessions = self.sessions.write().await;
        sessions.insert(token.clone(), session);
        drop(sessions);

        // Update last login
        let mut users = self.users.write().await;
        if let Some(user_mut) = users.get_mut(&user.id) {
            user_mut.last_login = Some(Utc::now().to_rfc3339());
        }

        Ok(AuthResponse {
            token,
            user: UserInfo {
                id: user.id,
                username: user.username.clone(),
                email: user.email,
                role: user.role,
                created_at: user.created_at,
            },
            expires_in,
        })
    }

    /// Logout user
    pub async fn logout(&self, token: &str) -> ServerResult<()> {
        let mut sessions = self.sessions.write().await;
        
        if let Some(session) = sessions.remove(token) {
            info!("User logged out: {}", session.user_id);
            Ok(())
        } else {
            Err(ServerError::BadRequest("Invalid session".to_string()))
        }
    }

    /// Validate token and return user
    pub async fn validate_token(&self, token: &str) -> ServerResult<User> {
        // Check if session exists and is not expired
        let sessions = self.sessions.read().await;
        
        if let Some(session) = sessions.get(token) {
            if session.expires_at < Utc::now() {
                return Err(ServerError::Unauthorized("Token expired".to_string()));
            }
        } else {
            return Err(ServerError::Unauthorized("Invalid token".to_string()));
        }
        drop(sessions);

        // Decode JWT
        let token_data = decode::<Claims>(
            token,
            &DecodingKey::from_secret(self.jwt_secret.as_ref()),
            &Validation::default(),
        )
        .map_err(|e| ServerError::Unauthorized(format!("Invalid token: {}", e)))?;

        // Get user
        let users = self.users.read().await;
        let user = users
            .get(&token_data.claims.sub)
            .filter(|u| u.is_active)
            .ok_or_else(|| ServerError::Unauthorized("User not found or inactive".to_string()))?;

        Ok(user.clone())
    }

    /// Generate JWT token
    fn generate_token(&self, user: &User) -> ServerResult<String> {
        let now = Utc::now();
        let exp = now + Duration::hours(self.token_expiration_hours);

        let claims = Claims {
            sub: user.id.clone(),
            username: user.username.clone(),
            email: user.email.clone(),
            role: user.role.clone(),
            exp: exp.timestamp(),
            iat: now.timestamp(),
            jti: Uuid::new_v4().to_string(),
        };

        encode(
            &Header::default(),
            &claims,
            &EncodingKey::from_secret(self.jwt_secret.as_ref()),
        )
        .map_err(|e| ServerError::Internal(format!("Failed to generate token: {}", e)))
    }

    /// Create admin user (for initial setup)
    pub async fn create_admin_user(&self, username: &str, email: &str, password: &str) -> ServerResult<()> {
        let mut users = self.users.write().await;

        // Check if admin already exists
        if users.values().any(|u| u.role == UserRole::Admin) {
            warn!("Admin user already exists");
            return Ok(());
        }

        // Hash password
        let password_hash = hash(password, DEFAULT_COST)
            .map_err(|e| ServerError::Internal(format!("Failed to hash password: {}", e)))?;

        // Create admin user
        let admin = User {
            id: Uuid::new_v4().to_string(),
            username: username.to_string(),
            email: email.to_string(),
            password_hash,
            role: UserRole::Admin,
            created_at: Utc::now().to_rfc3339(),
            last_login: None,
            is_active: true,
        };

        users.insert(admin.id.clone(), admin);
        drop(users);

        info!("Admin user created: {}", username);
        Ok(())
    }

    /// Get user by ID
    pub async fn get_user(&self, user_id: &str) -> ServerResult<User> {
        let users = self.users.read().await;
        users
            .get(user_id)
            .filter(|u| u.is_active)
            .cloned()
            .ok_or_else(|| ServerError::NotFound("User not found".to_string()))
    }

    /// Update user role (admin only)
    pub async fn update_user_role(&self, user_id: &str, new_role: UserRole) -> ServerResult<()> {
        let mut users = self.users.write().await;
        
        if let Some(user) = users.get_mut(user_id) {
            user.role = new_role;
            info!("User role updated: {} -> {:?}", user_id, new_role);
            Ok(())
        } else {
            Err(ServerError::NotFound("User not found".to_string()))
        }
    }

    /// Clean up expired sessions
    pub async fn cleanup_expired_sessions(&self) {
        let mut sessions = self.sessions.write().await;
        let now = Utc::now();
        
        let expired_tokens: Vec<String> = sessions
            .iter()
            .filter(|(_, session)| session.expires_at < now)
            .map(|(token, _)| token.clone())
            .collect();

        for token in expired_tokens {
            sessions.remove(&token);
        }

        if !sessions.is_empty() {
            debug!("Cleaned up expired sessions");
        }
    }

    /// Get active sessions count
    pub async fn get_active_sessions_count(&self) -> usize {
        self.sessions.read().await.len()
    }

    /// Get total users count
    pub async fn get_total_users_count(&self) -> usize {
        self.users.read().await.len()
    }
}

/// Middleware for authentication
pub async fn auth_middleware(
    auth_manager: Arc<AuthManager>,
    token: Option<String>,
) -> ServerResult<User> {
    let token = token
        .or_else(|| {
            // Try to extract from Authorization header
            // This would need to be implemented based on how the request is structured
            None
        })
        .ok_or_else(|| ServerError::Unauthorized("No token provided".to_string()))?;

    auth_manager.validate_token(&token).await
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_user_registration() {
        let auth = AuthManager::new("test_secret".to_string(), 24);
        
        let request = RegisterRequest {
            username: "testuser".to_string(),
            email: "test@example.com".to_string(),
            password: "password123".to_string(),
            confirm_password: "password123".to_string(),
        };

        let result = auth.register(request).await;
        assert!(result.is_ok());
        
        let response = result.unwrap();
        assert!(!response.token.is_empty());
        assert_eq!(response.user.username, "testuser");
        assert_eq!(response.user.role, UserRole::User);
    }

    #[tokio::test]
    async fn test_user_login() {
        let auth = AuthManager::new("test_secret".to_string(), 24);
        
        // Register user first
        let register_request = RegisterRequest {
            username: "testuser".to_string(),
            email: "test@example.com".to_string(),
            password: "password123".to_string(),
            confirm_password: "password123".to_string(),
        };
        auth.register(register_request).await.unwrap();

        // Login
        let login_request = LoginRequest {
            username: "testuser".to_string(),
            password: "password123".to_string(),
        };

        let result = auth.login(login_request).await;
        assert!(result.is_ok());
        
        let response = result.unwrap();
        assert!(!response.token.is_empty());
        assert_eq!(response.user.username, "testuser");
    }

    #[tokio::test]
    async fn test_invalid_login() {
        let auth = AuthManager::new("test_secret".to_string(), 24);
        
        let login_request = LoginRequest {
            username: "nonexistent".to_string(),
            password: "wrongpassword".to_string(),
        };

        let result = auth.login(login_request).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_token_validation() {
        let auth = AuthManager::new("test_secret".to_string(), 24);
        
        // Register and login user
        let register_request = RegisterRequest {
            username: "testuser".to_string(),
            email: "test@example.com".to_string(),
            password: "password123".to_string(),
            confirm_password: "password123".to_string(),
        };
        let auth_response = auth.register(register_request).await.unwrap();

        // Validate token
        let result = auth.validate_token(&auth_response.token).await;
        assert!(result.is_ok());
        
        let user = result.unwrap();
        assert_eq!(user.username, "testuser");
    }
}
510 lines•15.7 KB
rust

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer