Explore the GuruConnect server codebase to understand the current authentication setup:

1. Look at server/src/main.rs for route definitions and any existing auth middleware
2. Check server/src/auth/ or similar for authentication code
3. Look at the login.html and dashboard.html for how auth is handled on frontend
4. Check the database schema in server/src/db/ for any existing user tables
5. Look for JWT handling, session management, or API key auth

Focus on understanding:
- How is the dashboard currently protected (if at all)?
- Is there any user table already?
- What auth patterns are used for agents vs dashboard?
- Where are credentials/tokens stored?

Codebase is at: C:/Users/MikeSwanson/Claude/guru-connect/


I'll explore the GuruConnect server codebase to understand the current authentication setup. Let me start by examining the directory structure and key files.


Bash

ls -la "C:\Users\MikeSwanson\Claude\guru-connect\" | head -20

List root directory structure


find "C:\Users\MikeSwanson\Claude\guru-connect" -name "*.rs" -o -name "*.html" -o -name "*.json" | head -30

Find key file types in codebase


ls: cannot access 'C:\Users\MikeSwanson\Claude\guru-connect"': No such file or directory


Good! Now let me explore the key authentication files. Let me check the main server file and authentication module:


     1→//! GuruConnect Server - WebSocket Relay Server
     2→//!
     3→//! Handles connections from both agents and dashboard viewers,
     4→//! relaying video frames and input events between them.
     5→
     6→mod config;
     7→mod relay;
     8→mod session;
     9→mod auth;
    10→mod api;
    11→mod db;
    12→mod support_codes;
    13→
    14→pub mod proto {
    15→    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
    16→}
    17→
    18→use anyhow::Result;
    19→use axum::{
    20→    Router,
    21→    routing::{get, post},
    22→    extract::{Path, State, Json},
    23→    response::{Html, IntoResponse},
    24→    http::StatusCode,
    25→};
    26→use std::net::SocketAddr;
    27→use tower_http::cors::{Any, CorsLayer};
    28→use tower_http::trace::TraceLayer;
    29→use tower_http::services::ServeDir;
    30→use tracing::{info, Level};
    31→use tracing_subscriber::FmtSubscriber;
    32→use serde::Deserialize;
    33→
    34→use support_codes::{SupportCodeManager, CreateCodeRequest, SupportCode, CodeValidation};
    35→
    36→/// Application state
    37→#[derive(Clone)]
    38→pub struct AppState {
    39→    sessions: session::SessionManager,
    40→    support_codes: SupportCodeManager,
    41→    db: Option<db::Database>,
    42→}
    43→
    44→#[tokio::main]
    45→async fn main() -> Result<()> {
    46→    // Initialize logging
    47→    let _subscriber = FmtSubscriber::builder()
    48→        .with_max_level(Level::INFO)
    49→        .with_target(true)
    50→        .init();
    51→
    52→    info!("GuruConnect Server v{}", env!("CARGO_PKG_VERSION"));
    53→
    54→    // Load configuration
    55→    let config = config::Config::load()?;
    56→
    57→    // Use port 3002 for GuruConnect
    58→    let listen_addr = std::env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:3002".to_string());
    59→    info!("Loaded configuration, listening on {}", listen_addr);
    60→
    61→    // Initialize database if configured
    62→    let database = if let Some(ref db_url) = config.database_url {
    63→        match db::Database::connect(db_url, config.database_max_connections).await {
    64→            Ok(db) => {
    65→                // Run migrations
    66→                if let Err(e) = db.migrate().await {
    67→                    tracing::error!("Failed to run migrations: {}", e);
    68→                    return Err(e);
    69→                }
    70→                Some(db)
    71→            }
    72→            Err(e) => {
    73→                tracing::warn!("Failed to connect to database: {}. Running without persistence.", e);
    74→                None
    75→            }
    76→        }
    77→    } else {
    78→        info!("No DATABASE_URL set, running without persistence");
    79→        None
    80→    };
    81→
    82→    // Create session manager
    83→    let sessions = session::SessionManager::new();
    84→
    85→    // Restore persistent machines from database
    86→    if let Some(ref db) = database {
    87→        match db::machines::get_all_machines(db.pool()).await {
    88→            Ok(machines) => {
    89→                info!("Restoring {} persistent machines from database", machines.len());
    90→                for machine in machines {
    91→                    sessions.restore_offline_machine(&machine.agent_id, &machine.hostname).await;
    92→                }
    93→            }
    94→            Err(e) => {
    95→                tracing::warn!("Failed to restore machines: {}", e);
    96→            }
    97→        }
    98→    }
    99→
   100→    // Create application state
   101→    let state = AppState {
   102→        sessions,
   103→        support_codes: SupportCodeManager::new(),
   104→        db: database,
   105→    };
   106→
   107→    // Build router
   108→    let app = Router::new()
   109→        // Health check
   110→        .route("/health", get(health))
   111→
   112→        // Portal API - Support codes
   113→        .route("/api/codes", post(create_code))
   114→        .route("/api/codes", get(list_codes))
   115→        .route("/api/codes/:code/validate", get(validate_code))
   116→        .route("/api/codes/:code/cancel", post(cancel_code))
   117→
   118→        // WebSocket endpoints
   119→        .route("/ws/agent", get(relay::agent_ws_handler))
   120→        .route("/ws/viewer", get(relay::viewer_ws_handler))
   121→
   122→        // REST API - Sessions
   123→        .route("/api/sessions", get(list_sessions))
   124→        .route("/api/sessions/:id", get(get_session))
   125→        .route("/api/sessions/:id", axum::routing::delete(disconnect_session))
   126→
   127→        // HTML page routes (clean URLs)
   128→        .route("/login", get(serve_login))
   129→        .route("/dashboard", get(serve_dashboard))
   130→
   131→        // State
   132→        .with_state(state)
   133→
   134→        // Serve static files for portal (fallback)
   135→        .fallback_service(ServeDir::new("static").append_index_html_on_directories(true))
   136→
   137→        // Middleware
   138→        .layer(TraceLayer::new_for_http())
   139→        .layer(
   140→            CorsLayer::new()
   141→                .allow_origin(Any)
   142→                .allow_methods(Any)
   143→                .allow_headers(Any),
   144→        );
   145→
   146→    // Start server
   147→    let addr: SocketAddr = listen_addr.parse()?;
   148→    let listener = tokio::net::TcpListener::bind(addr).await?;
   149→
   150→    info!("Server listening on {}", addr);
   151→
   152→    axum::serve(listener, app).await?;
   153→
   154→    Ok(())
   155→}
   156→
   157→async fn health() -> &'static str {
   158→    "OK"
   159→}
   160→
   161→// Support code API handlers
   162→
   163→async fn create_code(
   164→    State(state): State<AppState>,
   165→    Json(request): Json<CreateCodeRequest>,
   166→) -> Json<SupportCode> {
   167→    let code = state.support_codes.create_code(request).await;
   168→    info!("Created support code: {}", code.code);
   169→    Json(code)
   170→}
   171→
   172→async fn list_codes(
   173→    State(state): State<AppState>,
   174→) -> Json<Vec<SupportCode>> {
   175→    Json(state.support_codes.list_active_codes().await)
   176→}
   177→
   178→#[derive(Deserialize)]
   179→struct ValidateParams {
   180→    code: String,
   181→}
   182→
   183→async fn validate_code(
   184→    State(state): State<AppState>,
   185→    Path(code): Path<String>,
   186→) -> Json<CodeValidation> {
   187→    Json(state.support_codes.validate_code(&code).await)
   188→}
   189→
   190→async fn cancel_code(
   191→    State(state): State<AppState>,
   192→    Path(code): Path<String>,
   193→) -> impl IntoResponse {
   194→    if state.support_codes.cancel_code(&code).await {
   195→        (StatusCode::OK, "Code cancelled")
   196→    } else {
   197→        (StatusCode::BAD_REQUEST, "Cannot cancel code")
   198→    }
   199→}
   200→
   201→// Session API handlers (updated to use AppState)
   202→
   203→async fn list_sessions(
   204→    State(state): State<AppState>,
   205→) -> Json<Vec<api::SessionInfo>> {
   206→    let sessions = state.sessions.list_sessions().await;
   207→    Json(sessions.into_iter().map(api::SessionInfo::from).collect())
   208→}
   209→
   210→async fn get_session(
   211→    State(state): State<AppState>,
   212→    Path(id): Path<String>,
   213→) -> Result<Json<api::SessionInfo>, (StatusCode, &'static str)> {
   214→    let session_id = uuid::Uuid::parse_str(&id)
   215→        .map_err(|_| (StatusCode::BAD_REQUEST, "Invalid session ID"))?;
   216→
   217→    let session = state.sessions.get_session(session_id).await
   218→        .ok_or((StatusCode::NOT_FOUND, "Session not found"))?;
   219→
   220→    Ok(Json(api::SessionInfo::from(session)))
   221→}
   222→
   223→async fn disconnect_session(
   224→    State(state): State<AppState>,
   225→    Path(id): Path<String>,
   226→) -> impl IntoResponse {
   227→    let session_id = match uuid::Uuid::parse_str(&id) {
   228→        Ok(id) => id,
   229→        Err(_) => return (StatusCode::BAD_REQUEST, "Invalid session ID"),
   230→    };
   231→
   232→    if state.sessions.disconnect_session(session_id, "Disconnected by administrator").await {
   233→        info!("Session {} disconnected by admin", session_id);
   234→        (StatusCode::OK, "Session disconnected")
   235→    } else {
   236→        (StatusCode::NOT_FOUND, "Session not found")
   237→    }
   238→}
   239→
   240→// Static page handlers
   241→async fn serve_login() -> impl IntoResponse {
   242→    match tokio::fs::read_to_string("static/login.html").await {
   243→        Ok(content) => Html(content).into_response(),
   244→        Err(_) => (StatusCode::NOT_FOUND, "Page not found").into_response(),
   245→    }
   246→}
   247→
   248→async fn serve_dashboard() -> impl IntoResponse {
   249→    match tokio::fs::read_to_string("static/dashboard.html").await {
   250→        Ok(content) => Html(content).into_response(),
   251→        Err(_) => (StatusCode::NOT_FOUND, "Page not found").into_response(),
   252→    }
   253→}
   254→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! GuruConnect Server - WebSocket Relay Server
//!
//! Handles connections from both agents and dashboard viewers,
//! relaying video frames and input events between them.

mod config;
mod relay;
mod session;
mod auth;
mod api;
mod db;
mod support_codes;

pub mod proto {
    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
}

use anyhow::Result;
use axum::{
    Router,
    routing::{get, post},
    extract::{Path, State, Json},
    response::{Html, IntoResponse},
    http::StatusCode,
};
use std::net::SocketAddr;
use tower_http::cors::{Any, CorsLayer};
use tower_http::trace::TraceLayer;
use tower_http::services::ServeDir;
use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;
use serde::Deserialize;

use support_codes::{SupportCodeManager, CreateCodeRequest, SupportCode, CodeValidation};

/// Application state
#[derive(Clone)]
pub struct AppState {
    sessions: session::SessionManager,
    support_codes: SupportCodeManager,
    db: Option<db::Database>,
}

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    let _subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .with_target(true)
        .init();

    info!("GuruConnect Server v{}", env!("CARGO_PKG_VERSION"));

    // Load configuration
    let config = config::Config::load()?;

    // Use port 3002 for GuruConnect
    let listen_addr = std::env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:3002".to_string());
    info!("Loaded configuration, listening on {}", listen_addr);

    // Initialize database if configured
    let database = if let Some(ref db_url) = config.database_url {
        match db::Database::connect(db_url, config.database_max_connections).await {
            Ok(db) => {
                // Run migrations
                if let Err(e) = db.migrate().await {
                    tracing::error!("Failed to run migrations: {}", e);
                    return Err(e);
                }
                Some(db)
            }
            Err(e) => {
                tracing::warn!("Failed to connect to database: {}. Running without persistence.", e);
                None
            }
        }
    } else {
        info!("No DATABASE_URL set, running without persistence");
        None
    };

    // Create session manager
    let sessions = session::SessionManager::new();

    // Restore persistent machines from database
    if let Some(ref db) = database {
        match db::machines::get_all_machines(db.pool()).await {
            Ok(machines) => {
                info!("Restoring {} persistent machines from database", machines.len());
                for machine in machines {
                    sessions.restore_offline_machine(&machine.agent_id, &machine.hostname).await;
                }
            }
            Err(e) => {
                tracing::warn!("Failed to restore machines: {}", e);
            }
        }
    }

    // Create application state
    let state = AppState {
        sessions,
        support_codes: SupportCodeManager::new(),
        db: database,
    };

    // Build router
    let app = Router::new()
        // Health check
        .route("/health", get(health))

        // Portal API - Support codes
        .route("/api/codes", post(create_code))
        .route("/api/codes", get(list_codes))
        .route("/api/codes/:code/validate", get(validate_code))
        .route("/api/codes/:code/cancel", post(cancel_code))

        // WebSocket endpoints
        .route("/ws/agent", get(relay::agent_ws_handler))
        .route("/ws/viewer", get(relay::viewer_ws_handler))

        // REST API - Sessions
        .route("/api/sessions", get(list_sessions))
        .route("/api/sessions/:id", get(get_session))
        .route("/api/sessions/:id", axum::routing::delete(disconnect_session))

        // HTML page routes (clean URLs)
        .route("/login", get(serve_login))
        .route("/dashboard", get(serve_dashboard))

        // State
        .with_state(state)

        // Serve static files for portal (fallback)
        .fallback_service(ServeDir::new("static").append_index_html_on_directories(true))

        // Middleware
        .layer(TraceLayer::new_for_http())
        .layer(
            CorsLayer::new()
                .allow_origin(Any)
                .allow_methods(Any)
                .allow_headers(Any),
        );

    // Start server
    let addr: SocketAddr = listen_addr.parse()?;
    let listener = tokio::net::TcpListener::bind(addr).await?;

    info!("Server listening on {}", addr);

    axum::serve(listener, app).await?;

    Ok(())
}

async fn health() -> &'static str {
    "OK"
}

// Support code API handlers

async fn create_code(
    State(state): State<AppState>,
    Json(request): Json<CreateCodeRequest>,
) -> Json<SupportCode> {
    let code = state.support_codes.create_code(request).await;
    info!("Created support code: {}", code.code);
    Json(code)
}

async fn list_codes(
    State(state): State<AppState>,
) -> Json<Vec<SupportCode>> {
    Json(state.support_codes.list_active_codes().await)
}

#[derive(Deserialize)]
struct ValidateParams {
    code: String,
}

async fn validate_code(
    State(state): State<AppState>,
    Path(code): Path<String>,
) -> Json<CodeValidation> {
    Json(state.support_codes.validate_code(&code).await)
}

async fn cancel_code(
    State(state): State<AppState>,
    Path(code): Path<String>,
) -> impl IntoResponse {
    if state.support_codes.cancel_code(&code).await {
        (StatusCode::OK, "Code cancelled")
    } else {
        (StatusCode::BAD_REQUEST, "Cannot cancel code")
    }
}

// Session API handlers (updated to use AppState)

async fn list_sessions(
    State(state): State<AppState>,
) -> Json<Vec<api::SessionInfo>> {
    let sessions = state.sessions.list_sessions().await;
    Json(sessions.into_iter().map(api::SessionInfo::from).collect())
}

async fn get_session(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> Result<Json<api::SessionInfo>, (StatusCode, &'static str)> {
    let session_id = uuid::Uuid::parse_str(&id)
        .map_err(|_| (StatusCode::BAD_REQUEST, "Invalid session ID"))?;

    let session = state.sessions.get_session(session_id).await
        .ok_or((StatusCode::NOT_FOUND, "Session not found"))?;

    Ok(Json(api::SessionInfo::from(session)))
}

async fn disconnect_session(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> impl IntoResponse {
    let session_id = match uuid::Uuid::parse_str(&id) {
        Ok(id) => id,
        Err(_) => return (StatusCode::BAD_REQUEST, "Invalid session ID"),
    };

    if state.sessions.disconnect_session(session_id, "Disconnected by administrator").await {
        info!("Session {} disconnected by admin", session_id);
        (StatusCode::OK, "Session disconnected")
    } else {
        (StatusCode::NOT_FOUND, "Session not found")
    }
}

// Static page handlers
async fn serve_login() -> impl IntoResponse {
    match tokio::fs::read_to_string("static/login.html").await {
        Ok(content) => Html(content).into_response(),
        Err(_) => (StatusCode::NOT_FOUND, "Page not found").into_response(),
    }
}

async fn serve_dashboard() -> impl IntoResponse {
    match tokio::fs::read_to_string("static/dashboard.html").await {
        Ok(content) => Html(content).into_response(),
        Err(_) => (StatusCode::NOT_FOUND, "Page not found").into_response(),
    }
}


     1→//! Authentication module
     2→//!
     3→//! Handles JWT validation for dashboard users and API key
     4→//! validation for agents.
     5→
     6→use axum::{
     7→    extract::FromRequestParts,
     8→    http::{request::Parts, StatusCode},
     9→};
    10→
    11→/// Authenticated user from JWT
    12→#[derive(Debug, Clone)]
    13→pub struct AuthenticatedUser {
    14→    pub user_id: String,
    15→    pub email: String,
    16→    pub roles: Vec<String>,
    17→}
    18→
    19→/// Authenticated agent from API key
    20→#[derive(Debug, Clone)]
    21→pub struct AuthenticatedAgent {
    22→    pub agent_id: String,
    23→    pub org_id: String,
    24→}
    25→
    26→/// Extract authenticated user from request (placeholder for MVP)
    27→#[axum::async_trait]
    28→impl<S> FromRequestParts<S> for AuthenticatedUser
    29→where
    30→    S: Send + Sync,
    31→{
    32→    type Rejection = (StatusCode, &'static str);
    33→
    34→    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
    35→        // TODO: Implement JWT validation
    36→        // For MVP, accept any request
    37→
    38→        // Look for Authorization header
    39→        let _auth_header = parts
    40→            .headers
    41→            .get("Authorization")
    42→            .and_then(|v| v.to_str().ok());
    43→
    44→        // Placeholder - in production, validate JWT
    45→        Ok(AuthenticatedUser {
    46→            user_id: "mvp-user".to_string(),
    47→            email: "mvp@example.com".to_string(),
    48→            roles: vec!["admin".to_string()],
    49→        })
    50→    }
    51→}
    52→
    53→/// Validate an agent API key (placeholder for MVP)
    54→pub fn validate_agent_key(_api_key: &str) -> Option<AuthenticatedAgent> {
    55→    // TODO: Implement actual API key validation
    56→    // For MVP, accept any key
    57→    Some(AuthenticatedAgent {
    58→        agent_id: "mvp-agent".to_string(),
    59→        org_id: "mvp-org".to_string(),
    60→    })
    61→}
    62→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Authentication module
//!
//! Handles JWT validation for dashboard users and API key
//! validation for agents.

use axum::{
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
};

/// Authenticated user from JWT
#[derive(Debug, Clone)]
pub struct AuthenticatedUser {
    pub user_id: String,
    pub email: String,
    pub roles: Vec<String>,
}

/// Authenticated agent from API key
#[derive(Debug, Clone)]
pub struct AuthenticatedAgent {
    pub agent_id: String,
    pub org_id: String,
}

/// Extract authenticated user from request (placeholder for MVP)
#[axum::async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // TODO: Implement JWT validation
        // For MVP, accept any request

        // Look for Authorization header
        let _auth_header = parts
            .headers
            .get("Authorization")
            .and_then(|v| v.to_str().ok());

        // Placeholder - in production, validate JWT
        Ok(AuthenticatedUser {
            user_id: "mvp-user".to_string(),
            email: "mvp@example.com".to_string(),
            roles: vec!["admin".to_string()],
        })
    }
}

/// Validate an agent API key (placeholder for MVP)
pub fn validate_agent_key(_api_key: &str) -> Option<AuthenticatedAgent> {
    // TODO: Implement actual API key validation
    // For MVP, accept any key
    Some(AuthenticatedAgent {
        agent_id: "mvp-agent".to_string(),
        org_id: "mvp-org".to_string(),
    })
}


Now let me check the database schema and see if there's any user table setup:


find "C:/Users/MikeSwanson/Claude/guru-connect/server" -name "*.sql" -o -name "migrations*"

Search for SQL migrations


     1→//! Database module for GuruConnect
     2→//!
     3→//! Handles persistence for machines, sessions, and audit logging.
     4→//! Optional - server works without database if DATABASE_URL not set.
     5→
     6→pub mod machines;
     7→pub mod sessions;
     8→pub mod events;
     9→pub mod support_codes;
    10→
    11→use anyhow::Result;
    12→use sqlx::postgres::PgPoolOptions;
    13→use sqlx::PgPool;
    14→use tracing::info;
    15→
    16→pub use machines::*;
    17→pub use sessions::*;
    18→pub use events::*;
    19→pub use support_codes::*;
    20→
    21→/// Database connection pool wrapper
    22→#[derive(Clone)]
    23→pub struct Database {
    24→    pool: PgPool,
    25→}
    26→
    27→impl Database {
    28→    /// Initialize database connection pool
    29→    pub async fn connect(database_url: &str, max_connections: u32) -> Result<Self> {
    30→        info!("Connecting to database...");
    31→        let pool = PgPoolOptions::new()
    32→            .max_connections(max_connections)
    33→            .connect(database_url)
    34→            .await?;
    35→
    36→        info!("Database connection established");
    37→        Ok(Self { pool })
    38→    }
    39→
    40→    /// Run database migrations
    41→    pub async fn migrate(&self) -> Result<()> {
    42→        info!("Running database migrations...");
    43→        sqlx::migrate!("./migrations").run(&self.pool).await?;
    44→        info!("Migrations complete");
    45→        Ok(())
    46→    }
    47→
    48→    /// Get reference to the connection pool
    49→    pub fn pool(&self) -> &PgPool {
    50→        &self.pool
    51→    }
    52→}
    53→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Database module for GuruConnect
//!
//! Handles persistence for machines, sessions, and audit logging.
//! Optional - server works without database if DATABASE_URL not set.

pub mod machines;
pub mod sessions;
pub mod events;
pub mod support_codes;

use anyhow::Result;
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use tracing::info;

pub use machines::*;
pub use sessions::*;
pub use events::*;
pub use support_codes::*;

/// Database connection pool wrapper
#[derive(Clone)]
pub struct Database {
    pool: PgPool,
}

impl Database {
    /// Initialize database connection pool
    pub async fn connect(database_url: &str, max_connections: u32) -> Result<Self> {
        info!("Connecting to database...");
        let pool = PgPoolOptions::new()
            .max_connections(max_connections)
            .connect(database_url)
            .await?;

        info!("Database connection established");
        Ok(Self { pool })
    }

    /// Run database migrations
    pub async fn migrate(&self) -> Result<()> {
        info!("Running database migrations...");
        sqlx::migrate!("./migrations").run(&self.pool).await?;
        info!("Migrations complete");
        Ok(())
    }

    /// Get reference to the connection pool
    pub fn pool(&self) -> &PgPool {
        &self.pool
    }
}


C:/Users/MikeSwanson/Claude/guru-connect/server/migrations
C:/Users/MikeSwanson/Claude/guru-connect/server/migrations/001_initial_schema.sql


Perfect! Let me read the migration file:


     1→-- GuruConnect Initial Schema
     2→-- Machine persistence, session audit logging, and support codes
     3→
     4→-- Enable UUID generation
     5→CREATE EXTENSION IF NOT EXISTS "pgcrypto";
     6→
     7→-- Machines table - persistent agent records that survive server restarts
     8→CREATE TABLE connect_machines (
     9→    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    10→    agent_id VARCHAR(255) UNIQUE NOT NULL,
    11→    hostname VARCHAR(255) NOT NULL,
    12→    os_version VARCHAR(255),
    13→    is_elevated BOOLEAN DEFAULT FALSE,
    14→    is_persistent BOOLEAN DEFAULT TRUE,
    15→    first_seen TIMESTAMPTZ DEFAULT NOW(),
    16→    last_seen TIMESTAMPTZ DEFAULT NOW(),
    17→    last_session_id UUID,
    18→    status VARCHAR(20) DEFAULT 'offline',
    19→    created_at TIMESTAMPTZ DEFAULT NOW(),
    20→    updated_at TIMESTAMPTZ DEFAULT NOW()
    21→);
    22→
    23→CREATE INDEX idx_connect_machines_agent_id ON connect_machines(agent_id);
    24→CREATE INDEX idx_connect_machines_status ON connect_machines(status);
    25→
    26→-- Sessions table - connection history
    27→CREATE TABLE connect_sessions (
    28→    id UUID PRIMARY KEY,
    29→    machine_id UUID REFERENCES connect_machines(id) ON DELETE CASCADE,
    30→    started_at TIMESTAMPTZ DEFAULT NOW(),
    31→    ended_at TIMESTAMPTZ,
    32→    duration_secs INTEGER,
    33→    is_support_session BOOLEAN DEFAULT FALSE,
    34→    support_code VARCHAR(10),
    35→    status VARCHAR(20) DEFAULT 'active'
    36→);
    37→
    38→CREATE INDEX idx_connect_sessions_machine ON connect_sessions(machine_id);
    39→CREATE INDEX idx_connect_sessions_started ON connect_sessions(started_at DESC);
    40→CREATE INDEX idx_connect_sessions_support_code ON connect_sessions(support_code);
    41→
    42→-- Session events - comprehensive audit log
    43→CREATE TABLE connect_session_events (
    44→    id BIGSERIAL PRIMARY KEY,
    45→    session_id UUID REFERENCES connect_sessions(id) ON DELETE CASCADE,
    46→    event_type VARCHAR(50) NOT NULL,
    47→    timestamp TIMESTAMPTZ DEFAULT NOW(),
    48→    viewer_id VARCHAR(255),
    49→    viewer_name VARCHAR(255),
    50→    details JSONB,
    51→    ip_address INET
    52→);
    53→
    54→CREATE INDEX idx_connect_events_session ON connect_session_events(session_id);
    55→CREATE INDEX idx_connect_events_time ON connect_session_events(timestamp DESC);
    56→CREATE INDEX idx_connect_events_type ON connect_session_events(event_type);
    57→
    58→-- Support codes - persistent across restarts
    59→CREATE TABLE connect_support_codes (
    60→    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    61→    code VARCHAR(10) UNIQUE NOT NULL,
    62→    session_id UUID,
    63→    created_by VARCHAR(255) NOT NULL,
    64→    created_at TIMESTAMPTZ DEFAULT NOW(),
    65→    expires_at TIMESTAMPTZ,
    66→    status VARCHAR(20) DEFAULT 'pending',
    67→    client_name VARCHAR(255),
    68→    client_machine VARCHAR(255),
    69→    connected_at TIMESTAMPTZ
    70→);
    71→
    72→CREATE INDEX idx_support_codes_code ON connect_support_codes(code);
    73→CREATE INDEX idx_support_codes_status ON connect_support_codes(status);
    74→CREATE INDEX idx_support_codes_session ON connect_support_codes(session_id);
    75→
    76→-- Trigger to auto-update updated_at on machines
    77→CREATE OR REPLACE FUNCTION update_connect_updated_at()
    78→RETURNS TRIGGER AS $$
    79→BEGIN
    80→    NEW.updated_at = NOW();
    81→    RETURN NEW;
    82→END;
    83→$$ LANGUAGE plpgsql;
    84→
    85→CREATE TRIGGER update_connect_machines_updated_at
    86→    BEFORE UPDATE ON connect_machines
    87→    FOR EACH ROW
    88→    EXECUTE FUNCTION update_connect_updated_at();
    89→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

-- GuruConnect Initial Schema
-- Machine persistence, session audit logging, and support codes

-- Enable UUID generation
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- Machines table - persistent agent records that survive server restarts
CREATE TABLE connect_machines (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    agent_id VARCHAR(255) UNIQUE NOT NULL,
    hostname VARCHAR(255) NOT NULL,
    os_version VARCHAR(255),
    is_elevated BOOLEAN DEFAULT FALSE,
    is_persistent BOOLEAN DEFAULT TRUE,
    first_seen TIMESTAMPTZ DEFAULT NOW(),
    last_seen TIMESTAMPTZ DEFAULT NOW(),
    last_session_id UUID,
    status VARCHAR(20) DEFAULT 'offline',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_connect_machines_agent_id ON connect_machines(agent_id);
CREATE INDEX idx_connect_machines_status ON connect_machines(status);

-- Sessions table - connection history
CREATE TABLE connect_sessions (
    id UUID PRIMARY KEY,
    machine_id UUID REFERENCES connect_machines(id) ON DELETE CASCADE,
    started_at TIMESTAMPTZ DEFAULT NOW(),
    ended_at TIMESTAMPTZ,
    duration_secs INTEGER,
    is_support_session BOOLEAN DEFAULT FALSE,
    support_code VARCHAR(10),
    status VARCHAR(20) DEFAULT 'active'
);

CREATE INDEX idx_connect_sessions_machine ON connect_sessions(machine_id);
CREATE INDEX idx_connect_sessions_started ON connect_sessions(started_at DESC);
CREATE INDEX idx_connect_sessions_support_code ON connect_sessions(support_code);

-- Session events - comprehensive audit log
CREATE TABLE connect_session_events (
    id BIGSERIAL PRIMARY KEY,
    session_id UUID REFERENCES connect_sessions(id) ON DELETE CASCADE,
    event_type VARCHAR(50) NOT NULL,
    timestamp TIMESTAMPTZ DEFAULT NOW(),
    viewer_id VARCHAR(255),
    viewer_name VARCHAR(255),
    details JSONB,
    ip_address INET
);

CREATE INDEX idx_connect_events_session ON connect_session_events(session_id);
CREATE INDEX idx_connect_events_time ON connect_session_events(timestamp DESC);
CREATE INDEX idx_connect_events_type ON connect_session_events(event_type);

-- Support codes - persistent across restarts
CREATE TABLE connect_support_codes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(10) UNIQUE NOT NULL,
    session_id UUID,
    created_by VARCHAR(255) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    expires_at TIMESTAMPTZ,
    status VARCHAR(20) DEFAULT 'pending',
    client_name VARCHAR(255),
    client_machine VARCHAR(255),
    connected_at TIMESTAMPTZ
);

CREATE INDEX idx_support_codes_code ON connect_support_codes(code);
CREATE INDEX idx_support_codes_status ON connect_support_codes(status);
CREATE INDEX idx_support_codes_session ON connect_support_codes(session_id);

-- Trigger to auto-update updated_at on machines
CREATE OR REPLACE FUNCTION update_connect_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_connect_machines_updated_at
    BEFORE UPDATE ON connect_machines
    FOR EACH ROW
    EXECUTE FUNCTION update_connect_updated_at();


     1→//! Machine/Agent database operations
     2→
     3→use chrono::{DateTime, Utc};
     4→use serde::{Deserialize, Serialize};
     5→use sqlx::PgPool;
     6→use uuid::Uuid;
     7→
     8→/// Machine record from database
     9→#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
    10→pub struct Machine {
    11→    pub id: Uuid,
    12→    pub agent_id: String,
    13→    pub hostname: String,
    14→    pub os_version: Option<String>,
    15→    pub is_elevated: bool,
    16→    pub is_persistent: bool,
    17→    pub first_seen: DateTime<Utc>,
    18→    pub last_seen: DateTime<Utc>,
    19→    pub last_session_id: Option<Uuid>,
    20→    pub status: String,
    21→    pub created_at: DateTime<Utc>,
    22→    pub updated_at: DateTime<Utc>,
    23→}
    24→
    25→/// Get or create a machine by agent_id (upsert)
    26→pub async fn upsert_machine(
    27→    pool: &PgPool,
    28→    agent_id: &str,
    29→    hostname: &str,
    30→    is_persistent: bool,
    31→) -> Result<Machine, sqlx::Error> {
    32→    sqlx::query_as::<_, Machine>(
    33→        r#"
    34→        INSERT INTO connect_machines (agent_id, hostname, is_persistent, status, last_seen)
    35→        VALUES ($1, $2, $3, 'online', NOW())
    36→        ON CONFLICT (agent_id) DO UPDATE SET
    37→            hostname = EXCLUDED.hostname,
    38→            status = 'online',
    39→            last_seen = NOW()
    40→        RETURNING *
    41→        "#,
    42→    )
    43→    .bind(agent_id)
    44→    .bind(hostname)
    45→    .bind(is_persistent)
    46→    .fetch_one(pool)
    47→    .await
    48→}
    49→
    50→/// Update machine status and info
    51→pub async fn update_machine_status(
    52→    pool: &PgPool,
    53→    agent_id: &str,
    54→    status: &str,
    55→    os_version: Option<&str>,
    56→    is_elevated: bool,
    57→    session_id: Option<Uuid>,
    58→) -> Result<(), sqlx::Error> {
    59→    sqlx::query(
    60→        r#"
    61→        UPDATE connect_machines SET
    62→            status = $1,
    63→            os_version = COALESCE($2, os_version),
    64→            is_elevated = $3,
    65→            last_seen = NOW(),
    66→            last_session_id = COALESCE($4, last_session_id)
    67→        WHERE agent_id = $5
    68→        "#,
    69→    )
    70→    .bind(status)
    71→    .bind(os_version)
    72→    .bind(is_elevated)
    73→    .bind(session_id)
    74→    .bind(agent_id)
    75→    .execute(pool)
    76→    .await?;
    77→    Ok(())
    78→}
    79→
    80→/// Get all persistent machines (for restore on startup)
    81→pub async fn get_all_machines(pool: &PgPool) -> Result<Vec<Machine>, sqlx::Error> {
    82→    sqlx::query_as::<_, Machine>(
    83→        "SELECT * FROM connect_machines WHERE is_persistent = true ORDER BY hostname"
    84→    )
    85→    .fetch_all(pool)
    86→    .await
    87→}
    88→
    89→/// Get machine by agent_id
    90→pub async fn get_machine_by_agent_id(
    91→    pool: &PgPool,
    92→    agent_id: &str,
    93→) -> Result<Option<Machine>, sqlx::Error> {
    94→    sqlx::query_as::<_, Machine>(
    95→        "SELECT * FROM connect_machines WHERE agent_id = $1"
    96→    )
    97→    .bind(agent_id)
    98→    .fetch_optional(pool)
    99→    .await
   100→}
   101→
   102→/// Mark machine as offline
   103→pub async fn mark_machine_offline(pool: &PgPool, agent_id: &str) -> Result<(), sqlx::Error> {
   104→    sqlx::query("UPDATE connect_machines SET status = 'offline', last_seen = NOW() WHERE agent_id = $1")
   105→        .bind(agent_id)
   106→        .execute(pool)
   107→        .await?;
   108→    Ok(())
   109→}
   110→
   111→/// Delete a machine record
   112→pub async fn delete_machine(pool: &PgPool, agent_id: &str) -> Result<(), sqlx::Error> {
   113→    sqlx::query("DELETE FROM connect_machines WHERE agent_id = $1")
   114→        .bind(agent_id)
   115→        .execute(pool)
   116→        .await?;
   117→    Ok(())
   118→}
   119→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Machine/Agent database operations

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use uuid::Uuid;

/// Machine record from database
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Machine {
    pub id: Uuid,
    pub agent_id: String,
    pub hostname: String,
    pub os_version: Option<String>,
    pub is_elevated: bool,
    pub is_persistent: bool,
    pub first_seen: DateTime<Utc>,
    pub last_seen: DateTime<Utc>,
    pub last_session_id: Option<Uuid>,
    pub status: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Get or create a machine by agent_id (upsert)
pub async fn upsert_machine(
    pool: &PgPool,
    agent_id: &str,
    hostname: &str,
    is_persistent: bool,
) -> Result<Machine, sqlx::Error> {
    sqlx::query_as::<_, Machine>(
        r#"
        INSERT INTO connect_machines (agent_id, hostname, is_persistent, status, last_seen)
        VALUES ($1, $2, $3, 'online', NOW())
        ON CONFLICT (agent_id) DO UPDATE SET
            hostname = EXCLUDED.hostname,
            status = 'online',
            last_seen = NOW()
        RETURNING *
        "#,
    )
    .bind(agent_id)
    .bind(hostname)
    .bind(is_persistent)
    .fetch_one(pool)
    .await
}

/// Update machine status and info
pub async fn update_machine_status(
    pool: &PgPool,
    agent_id: &str,
    status: &str,
    os_version: Option<&str>,
    is_elevated: bool,
    session_id: Option<Uuid>,
) -> Result<(), sqlx::Error> {
    sqlx::query(
        r#"
        UPDATE connect_machines SET
            status = $1,
            os_version = COALESCE($2, os_version),
            is_elevated = $3,
            last_seen = NOW(),
            last_session_id = COALESCE($4, last_session_id)
        WHERE agent_id = $5
        "#,
    )
    .bind(status)
    .bind(os_version)
    .bind(is_elevated)
    .bind(session_id)
    .bind(agent_id)
    .execute(pool)
    .await?;
    Ok(())
}

/// Get all persistent machines (for restore on startup)
pub async fn get_all_machines(pool: &PgPool) -> Result<Vec<Machine>, sqlx::Error> {
    sqlx::query_as::<_, Machine>(
        "SELECT * FROM connect_machines WHERE is_persistent = true ORDER BY hostname"
    )
    .fetch_all(pool)
    .await
}

/// Get machine by agent_id
pub async fn get_machine_by_agent_id(
    pool: &PgPool,
    agent_id: &str,
) -> Result<Option<Machine>, sqlx::Error> {
    sqlx::query_as::<_, Machine>(
        "SELECT * FROM connect_machines WHERE agent_id = $1"
    )
    .bind(agent_id)
    .fetch_optional(pool)
    .await
}

/// Mark machine as offline
pub async fn mark_machine_offline(pool: &PgPool, agent_id: &str) -> Result<(), sqlx::Error> {
    sqlx::query("UPDATE connect_machines SET status = 'offline', last_seen = NOW() WHERE agent_id = $1")
        .bind(agent_id)
        .execute(pool)
        .await?;
    Ok(())
}

/// Delete a machine record
pub async fn delete_machine(pool: &PgPool, agent_id: &str) -> Result<(), sqlx::Error> {
    sqlx::query("DELETE FROM connect_machines WHERE agent_id = $1")
        .bind(agent_id)
        .execute(pool)
        .await?;
    Ok(())
}


     1→//! Session database operations
     2→
     3→use chrono::{DateTime, Utc};
     4→use serde::{Deserialize, Serialize};
     5→use sqlx::PgPool;
     6→use uuid::Uuid;
     7→
     8→/// Session record from database
     9→#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
    10→pub struct DbSession {
    11→    pub id: Uuid,
    12→    pub machine_id: Option<Uuid>,
    13→    pub started_at: DateTime<Utc>,
    14→    pub ended_at: Option<DateTime<Utc>>,
    15→    pub duration_secs: Option<i32>,
    16→    pub is_support_session: bool,
    17→    pub support_code: Option<String>,
    18→    pub status: String,
    19→}
    20→
    21→/// Create a new session record
    22→pub async fn create_session(
    23→    pool: &PgPool,
    24→    session_id: Uuid,
    25→    machine_id: Uuid,
    26→    is_support_session: bool,
    27→    support_code: Option<&str>,
    28→) -> Result<DbSession, sqlx::Error> {
    29→    sqlx::query_as::<_, DbSession>(
    30→        r#"
    31→        INSERT INTO connect_sessions (id, machine_id, is_support_session, support_code, status)
    32→        VALUES ($1, $2, $3, $4, 'active')
    33→        RETURNING *
    34→        "#,
    35→    )
    36→    .bind(session_id)
    37→    .bind(machine_id)
    38→    .bind(is_support_session)
    39→    .bind(support_code)
    40→    .fetch_one(pool)
    41→    .await
    42→}
    43→
    44→/// End a session
    45→pub async fn end_session(
    46→    pool: &PgPool,
    47→    session_id: Uuid,
    48→    status: &str,  // 'ended' or 'disconnected' or 'timeout'
    49→) -> Result<(), sqlx::Error> {
    50→    sqlx::query(
    51→        r#"
    52→        UPDATE connect_sessions SET
    53→            ended_at = NOW(),
    54→            duration_secs = EXTRACT(EPOCH FROM (NOW() - started_at))::INTEGER,
    55→            status = $1
    56→        WHERE id = $2
    57→        "#,
    58→    )
    59→    .bind(status)
    60→    .bind(session_id)
    61→    .execute(pool)
    62→    .await?;
    63→    Ok(())
    64→}
    65→
    66→/// Get session by ID
    67→pub async fn get_session(pool: &PgPool, session_id: Uuid) -> Result<Option<DbSession>, sqlx::Error> {
    68→    sqlx::query_as::<_, DbSession>("SELECT * FROM connect_sessions WHERE id = $1")
    69→        .bind(session_id)
    70→        .fetch_optional(pool)
    71→        .await
    72→}
    73→
    74→/// Get active sessions for a machine
    75→pub async fn get_active_sessions_for_machine(
    76→    pool: &PgPool,
    77→    machine_id: Uuid,
    78→) -> Result<Vec<DbSession>, sqlx::Error> {
    79→    sqlx::query_as::<_, DbSession>(
    80→        "SELECT * FROM connect_sessions WHERE machine_id = $1 AND status = 'active' ORDER BY started_at DESC"
    81→    )
    82→    .bind(machine_id)
    83→    .fetch_all(pool)
    84→    .await
    85→}
    86→
    87→/// Get recent sessions (for dashboard)
    88→pub async fn get_recent_sessions(
    89→    pool: &PgPool,
    90→    limit: i64,
    91→) -> Result<Vec<DbSession>, sqlx::Error> {
    92→    sqlx::query_as::<_, DbSession>(
    93→        "SELECT * FROM connect_sessions ORDER BY started_at DESC LIMIT $1"
    94→    )
    95→    .bind(limit)
    96→    .fetch_all(pool)
    97→    .await
    98→}
    99→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Session database operations

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use uuid::Uuid;

/// Session record from database
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct DbSession {
    pub id: Uuid,
    pub machine_id: Option<Uuid>,
    pub started_at: DateTime<Utc>,
    pub ended_at: Option<DateTime<Utc>>,
    pub duration_secs: Option<i32>,
    pub is_support_session: bool,
    pub support_code: Option<String>,
    pub status: String,
}

/// Create a new session record
pub async fn create_session(
    pool: &PgPool,
    session_id: Uuid,
    machine_id: Uuid,
    is_support_session: bool,
    support_code: Option<&str>,
) -> Result<DbSession, sqlx::Error> {
    sqlx::query_as::<_, DbSession>(
        r#"
        INSERT INTO connect_sessions (id, machine_id, is_support_session, support_code, status)
        VALUES ($1, $2, $3, $4, 'active')
        RETURNING *
        "#,
    )
    .bind(session_id)
    .bind(machine_id)
    .bind(is_support_session)
    .bind(support_code)
    .fetch_one(pool)
    .await
}

/// End a session
pub async fn end_session(
    pool: &PgPool,
    session_id: Uuid,
    status: &str,  // 'ended' or 'disconnected' or 'timeout'
) -> Result<(), sqlx::Error> {
    sqlx::query(
        r#"
        UPDATE connect_sessions SET
            ended_at = NOW(),
            duration_secs = EXTRACT(EPOCH FROM (NOW() - started_at))::INTEGER,
            status = $1
        WHERE id = $2
        "#,
    )
    .bind(status)
    .bind(session_id)
    .execute(pool)
    .await?;
    Ok(())
}

/// Get session by ID
pub async fn get_session(pool: &PgPool, session_id: Uuid) -> Result<Option<DbSession>, sqlx::Error> {
    sqlx::query_as::<_, DbSession>("SELECT * FROM connect_sessions WHERE id = $1")
        .bind(session_id)
        .fetch_optional(pool)
        .await
}

/// Get active sessions for a machine
pub async fn get_active_sessions_for_machine(
    pool: &PgPool,
    machine_id: Uuid,
) -> Result<Vec<DbSession>, sqlx::Error> {
    sqlx::query_as::<_, DbSession>(
        "SELECT * FROM connect_sessions WHERE machine_id = $1 AND status = 'active' ORDER BY started_at DESC"
    )
    .bind(machine_id)
    .fetch_all(pool)
    .await
}

/// Get recent sessions (for dashboard)
pub async fn get_recent_sessions(
    pool: &PgPool,
    limit: i64,
) -> Result<Vec<DbSession>, sqlx::Error> {
    sqlx::query_as::<_, DbSession>(
        "SELECT * FROM connect_sessions ORDER BY started_at DESC LIMIT $1"
    )
    .bind(limit)
    .fetch_all(pool)
    .await
}


Now let me check the API module and look at the frontend HTML files:


find "C:/Users/MikeSwanson/Claude/guru-connect" -name "login.html" -o -name "dashboard.html"

Find HTML files


     1→//! REST API endpoints
     2→
     3→use axum::{
     4→    extract::{Path, State},
     5→    Json,
     6→};
     7→use serde::Serialize;
     8→use uuid::Uuid;
     9→
    10→use crate::session::SessionManager;
    11→
    12→/// Viewer info returned by API
    13→#[derive(Debug, Serialize)]
    14→pub struct ViewerInfoApi {
    15→    pub id: String,
    16→    pub name: String,
    17→    pub connected_at: String,
    18→}
    19→
    20→impl From<crate::session::ViewerInfo> for ViewerInfoApi {
    21→    fn from(v: crate::session::ViewerInfo) -> Self {
    22→        Self {
    23→            id: v.id,
    24→            name: v.name,
    25→            connected_at: v.connected_at.to_rfc3339(),
    26→        }
    27→    }
    28→}
    29→
    30→/// Session info returned by API
    31→#[derive(Debug, Serialize)]
    32→pub struct SessionInfo {
    33→    pub id: String,
    34→    pub agent_id: String,
    35→    pub agent_name: String,
    36→    pub started_at: String,
    37→    pub viewer_count: usize,
    38→    pub viewers: Vec<ViewerInfoApi>,
    39→    pub is_streaming: bool,
    40→    pub is_online: bool,
    41→    pub is_persistent: bool,
    42→    pub last_heartbeat: String,
    43→    pub os_version: Option<String>,
    44→    pub is_elevated: bool,
    45→    pub uptime_secs: i64,
    46→    pub display_count: i32,
    47→}
    48→
    49→impl From<crate::session::Session> for SessionInfo {
    50→    fn from(s: crate::session::Session) -> Self {
    51→        Self {
    52→            id: s.id.to_string(),
    53→            agent_id: s.agent_id,
    54→            agent_name: s.agent_name,
    55→            started_at: s.started_at.to_rfc3339(),
    56→            viewer_count: s.viewer_count,
    57→            viewers: s.viewers.into_iter().map(ViewerInfoApi::from).collect(),
    58→            is_streaming: s.is_streaming,
    59→            is_online: s.is_online,
    60→            is_persistent: s.is_persistent,
    61→            last_heartbeat: s.last_heartbeat.to_rfc3339(),
    62→            os_version: s.os_version,
    63→            is_elevated: s.is_elevated,
    64→            uptime_secs: s.uptime_secs,
    65→            display_count: s.display_count,
    66→        }
    67→    }
    68→}
    69→
    70→/// List all active sessions
    71→pub async fn list_sessions(
    72→    State(sessions): State<SessionManager>,
    73→) -> Json<Vec<SessionInfo>> {
    74→    let sessions = sessions.list_sessions().await;
    75→    Json(sessions.into_iter().map(SessionInfo::from).collect())
    76→}
    77→
    78→/// Get a specific session by ID
    79→pub async fn get_session(
    80→    State(sessions): State<SessionManager>,
    81→    Path(id): Path<String>,
    82→) -> Result<Json<SessionInfo>, (axum::http::StatusCode, &'static str)> {
    83→    let session_id = Uuid::parse_str(&id)
    84→        .map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "Invalid session ID"))?;
    85→
    86→    let session = sessions.get_session(session_id).await
    87→        .ok_or((axum::http::StatusCode::NOT_FOUND, "Session not found"))?;
    88→
    89→    Ok(Json(SessionInfo::from(session)))
    90→}
    91→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! REST API endpoints

use axum::{
    extract::{Path, State},
    Json,
};
use serde::Serialize;
use uuid::Uuid;

use crate::session::SessionManager;

/// Viewer info returned by API
#[derive(Debug, Serialize)]
pub struct ViewerInfoApi {
    pub id: String,
    pub name: String,
    pub connected_at: String,
}

impl From<crate::session::ViewerInfo> for ViewerInfoApi {
    fn from(v: crate::session::ViewerInfo) -> Self {
        Self {
            id: v.id,
            name: v.name,
            connected_at: v.connected_at.to_rfc3339(),
        }
    }
}

/// Session info returned by API
#[derive(Debug, Serialize)]
pub struct SessionInfo {
    pub id: String,
    pub agent_id: String,
    pub agent_name: String,
    pub started_at: String,
    pub viewer_count: usize,
    pub viewers: Vec<ViewerInfoApi>,
    pub is_streaming: bool,
    pub is_online: bool,
    pub is_persistent: bool,
    pub last_heartbeat: String,
    pub os_version: Option<String>,
    pub is_elevated: bool,
    pub uptime_secs: i64,
    pub display_count: i32,
}

impl From<crate::session::Session> for SessionInfo {
    fn from(s: crate::session::Session) -> Self {
        Self {
            id: s.id.to_string(),
            agent_id: s.agent_id,
            agent_name: s.agent_name,
            started_at: s.started_at.to_rfc3339(),
            viewer_count: s.viewer_count,
            viewers: s.viewers.into_iter().map(ViewerInfoApi::from).collect(),
            is_streaming: s.is_streaming,
            is_online: s.is_online,
            is_persistent: s.is_persistent,
            last_heartbeat: s.last_heartbeat.to_rfc3339(),
            os_version: s.os_version,
            is_elevated: s.is_elevated,
            uptime_secs: s.uptime_secs,
            display_count: s.display_count,
        }
    }
}

/// List all active sessions
pub async fn list_sessions(
    State(sessions): State<SessionManager>,
) -> Json<Vec<SessionInfo>> {
    let sessions = sessions.list_sessions().await;
    Json(sessions.into_iter().map(SessionInfo::from).collect())
}

/// Get a specific session by ID
pub async fn get_session(
    State(sessions): State<SessionManager>,
    Path(id): Path<String>,
) -> Result<Json<SessionInfo>, (axum::http::StatusCode, &'static str)> {
    let session_id = Uuid::parse_str(&id)
        .map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "Invalid session ID"))?;

    let session = sessions.get_session(session_id).await
        .ok_or((axum::http::StatusCode::NOT_FOUND, "Session not found"))?;

    Ok(Json(SessionInfo::from(session)))
}


C:/Users/MikeSwanson/Claude/guru-connect/server/static/dashboard.html
C:/Users/MikeSwanson/Claude/guru-connect/server/static/login.html


Now let me read the HTML files and check the support_codes module:


1→<!DOCTYPE html>
     2→
     3→
     4→    <meta charset="UTF-8">
     5→    <meta name="viewport" content="width=device-width, initial-scale=1.0">
     6→    <title>GuruConnect - Technician Login</title>
     7→
   139→
   140→
   141→

   142→

   143→
GuruConnect

   144→
Technician Login

   145→

   146→
   147→        <form class="login-form" id="loginForm">
   148→

   149→                Email
   150→                <input type="email" id="email" placeholder="you@company.com" required>
   151→

   152→
   153→

   154→                Password
   155→                <input type="password" id="password" placeholder="Enter your password" required>
   156→

   157→
   158→

   159→
   160→            <button type="submit" class="login-btn" id="loginBtn">
   161→
   162→                Sign In
   163→            </button>
   164→        </form>
   165→
   166→

   167→
Auth not yet configured. Skip to Dashboard

   168→

   169→
   170→

   171→
Back to Support Portal

   172→

   173→

   174→
   175→
   229→
   230→
   231→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

<!DOCTYPE html>

    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GuruConnect - Technician Login</title>

GuruConnect

Technician Login

        <form class="login-form" id="loginForm">

                Email
                <input type="email" id="email" placeholder="you@company.com" required>

                Password
                <input type="password" id="password" placeholder="Enter your password" required>

            <button type="submit" class="login-btn" id="loginBtn">

                Sign In
            </button>
        </form>

Auth not yet configured. Skip to Dashboard

Back to Support Portal


1→<!DOCTYPE html>
     2→
     3→
     4→    <meta charset="UTF-8">
     5→    <meta name="viewport" content="width=device-width, initial-scale=1.0">
     6→    <title>GuruConnect - Dashboard</title>
     7→
   334→
   335→
   336→
   337→

   338→
GuruConnect

   339→

   340→

   341→            Loading...
   342→            <button class="logout-btn" id="logoutBtn">Sign Out</button>
   343→

   344→
   345→
   346→
   347→        <button class="tab active" data-tab="support">Support</button>
   348→        <button class="tab" data-tab="access">Access</button>
   349→        <button class="tab" data-tab="build">Build</button>
   350→        <button class="tab" data-tab="settings">Settings</button>
   351→
   352→
   353→
   354→
   355→

   356→

   357→

   358→

   359→
Active Support Sessions

   360→
Temporary sessions initiated by support codes

   361→

   362→                    <button class="btn btn-primary" id="generateCodeBtn">Generate Code</button>
   363→

   364→

   365→

   366→

   367→

   368→                                Code
   369→                                Status
   370→                                Created
   371→                                Technician
   372→                                Actions
   373→

   374→

   375→

   376→

   377→
   378→

   379→
No active sessions

   380→
Generate a code to start a support session

   381→

   382→
   383→

   384→

   385→

   386→

   387→

   388→

   389→
   390→
   391→

   392→

   393→

   394→

   395→
Status

   396→

   397→                            All Machines
   398→                            0
   399→

   400→

   401→                            Online
   402→                            0
   403→

   404→

   405→                            Offline
   406→                            0
   407→

   408→

   409→

   410→
By Company

   411→

   412→
No machines installed

   413→

   414→

   415→

   416→

   417→

   418→
No machines

   419→
Install the agent on a machine to see it here

   420→

   421→

   422→

   423→

   424→
Select a machine

   425→
Click a machine to view details

   426→

   427→

   428→

   429→

   430→
   431→
   432→

   433→

   434→

   435→

   436→
Installer Builder

   437→
Create customized agent installers for unattended access

   438→

   439→

   440→

   441→

   442→                        Name
   443→                        <input type="text" id="buildName" placeholder="Machine name (auto if blank)">
   444→

   445→

   446→                        Company
   447→                        <input type="text" id="buildCompany" placeholder="Client organization">
   448→

   449→

   450→                        Site
   451→                        <input type="text" id="buildSite" placeholder="Physical location">
   452→

   453→

   454→                        Department
   455→                        <input type="text" id="buildDepartment" placeholder="Business unit">
   456→

   457→

   458→                        Device Type
   459→                        <select id="buildDeviceType">
   460→                            <option value="workstation">Workstation</option>
   461→                            <option value="laptop">Laptop</option>
   462→                            <option value="server">Server</option>
   463→                        </select>
   464→

   465→

   466→                        Tag
   467→                        <input type="text" id="buildTag" placeholder="Custom label">
   468→

   469→

   470→

   471→                    <button class="btn btn-primary" disabled>Build EXE (64-bit)</button>
   472→                    <button class="btn btn-outline" disabled>Build EXE (32-bit)</button>
   473→                    <button class="btn btn-outline" disabled>Build MSI</button>
   474→

   475→

   476→                    Agent builds will be available once the agent is compiled.
   477→

   478→

   479→

   480→
   481→
   482→

   483→

   484→

   485→

   486→
Settings

   487→
Configure your GuruConnect preferences

   488→

   489→

   490→

   491→

   492→                        Theme
   493→                        <select disabled>
   494→                            <option>Dark (Default)</option>
   495→                            <option>Light</option>
   496→                            <option>System</option>
   497→                        </select>
   498→

   499→

   500→                        Notifications
   501→                        <select disabled>
   502→                            <option>All notifications</option>
   503→                            <option>Important only</option>
   504→                            <option>None</option>
   505→                        </select>
   506→

   507→

   508→

   509→                    Additional settings coming soon.
   510→

   511→

   512→

   513→
   514→
   515→
   516→

   517→

   518→

   519→
Chat with Client

   520→                <button class="modal-close" id="chatClose">×</button>
   521→

   522→

   523→
No messages yet. Start the conversation!

   524→

   525→

   526→                <input type="text" class="chat-input" id="chatInput" placeholder="Type a message..." />
   527→                <button class="chat-send" id="chatSend">Send</button>
   528→

   529→

   530→

   531→
   532→
  1118→
  1119→
  1120→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

<!DOCTYPE html>

    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GuruConnect - Dashboard</title>

GuruConnect

            Loading...
            <button class="logout-btn" id="logoutBtn">Sign Out</button>

        <button class="tab active" data-tab="support">Support</button>
        <button class="tab" data-tab="access">Access</button>
        <button class="tab" data-tab="build">Build</button>
        <button class="tab" data-tab="settings">Settings</button>

Active Support Sessions

Temporary sessions initiated by support codes

                    <button class="btn btn-primary" id="generateCodeBtn">Generate Code</button>

                                Code
                                Status
                                Created
                                Technician
                                Actions

No active sessions

Generate a code to start a support session

Status

                            All Machines
                            0

                            Online
                            0

                            Offline
                            0

By Company

No machines installed

No machines

Install the agent on a machine to see it here

Select a machine

Click a machine to view details

Installer Builder

Create customized agent installers for unattended access

                        Name
                        <input type="text" id="buildName" placeholder="Machine name (auto if blank)">

                        Company
                        <input type="text" id="buildCompany" placeholder="Client organization">

                        Site
                        <input type="text" id="buildSite" placeholder="Physical location">

                        Department
                        <input type="text" id="buildDepartment" placeholder="Business unit">

                        Device Type
                        <select id="buildDeviceType">
                            <option value="workstation">Workstation</option>
                            <option value="laptop">Laptop</option>
                            <option value="server">Server</option>
                        </select>

                        Tag
                        <input type="text" id="buildTag" placeholder="Custom label">

                    <button class="btn btn-primary" disabled>Build EXE (64-bit)</button>
                    <button class="btn btn-outline" disabled>Build EXE (32-bit)</button>
                    <button class="btn btn-outline" disabled>Build MSI</button>

                    Agent builds will be available once the agent is compiled.

Settings

Configure your GuruConnect preferences

                        Theme
                        <select disabled>
                            <option>Dark (Default)</option>
                            <option>Light</option>
                            <option>System</option>
                        </select>

                        Notifications
                        <select disabled>
                            <option>All notifications</option>
                            <option>Important only</option>
                            <option>None</option>
                        </select>

                    Additional settings coming soon.

Chat with Client

                <button class="modal-close" id="chatClose">×</button>

No messages yet. Start the conversation!

                <input type="text" class="chat-input" id="chatInput" placeholder="Type a message..." />
                <button class="chat-send" id="chatSend">Send</button>


     1→//! Support session codes management
     2→//!
     3→//! Handles generation and validation of 6-digit support codes
     4→//! for one-time remote support sessions.
     5→
     6→use std::collections::HashMap;
     7→use std::sync::Arc;
     8→use tokio::sync::RwLock;
     9→use chrono::{DateTime, Utc};
    10→use rand::Rng;
    11→use serde::{Deserialize, Serialize};
    12→use uuid::Uuid;
    13→
    14→/// A support session code
    15→#[derive(Debug, Clone, Serialize)]
    16→pub struct SupportCode {
    17→    pub code: String,
    18→    pub session_id: Uuid,
    19→    pub created_by: String,
    20→    pub created_at: DateTime<Utc>,
    21→    pub status: CodeStatus,
    22→    pub client_name: Option<String>,
    23→    pub client_machine: Option<String>,
    24→    pub connected_at: Option<DateTime<Utc>>,
    25→}
    26→
    27→#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
    28→#[serde(rename_all = "lowercase")]
    29→pub enum CodeStatus {
    30→    Pending,    // Waiting for client to connect
    31→    Connected,  // Client connected, session active
    32→    Completed,  // Session ended normally
    33→    Cancelled,  // Code cancelled by tech
    34→}
    35→
    36→/// Request to create a new support code
    37→#[derive(Debug, Deserialize)]
    38→pub struct CreateCodeRequest {
    39→    pub technician_id: Option<String>,
    40→    pub technician_name: Option<String>,
    41→}
    42→
    43→/// Response when a code is validated
    44→#[derive(Debug, Serialize)]
    45→pub struct CodeValidation {
    46→    pub valid: bool,
    47→    pub session_id: Option<String>,
    48→    pub server_url: Option<String>,
    49→    #[serde(skip_serializing_if = "Option::is_none")]
    50→    pub error: Option<String>,
    51→}
    52→
    53→/// Manages support codes
    54→#[derive(Clone)]
    55→pub struct SupportCodeManager {
    56→    codes: Arc<RwLock<HashMap<String, SupportCode>>>,
    57→    session_to_code: Arc<RwLock<HashMap<Uuid, String>>>,
    58→}
    59→
    60→impl SupportCodeManager {
    61→    pub fn new() -> Self {
    62→        Self {
    63→            codes: Arc::new(RwLock::new(HashMap::new())),
    64→            session_to_code: Arc::new(RwLock::new(HashMap::new())),
    65→        }
    66→    }
    67→
    68→    /// Generate a unique 6-digit code
    69→    async fn generate_unique_code(&self) -> String {
    70→        let codes = self.codes.read().await;
    71→        let mut rng = rand::thread_rng();
    72→
    73→        loop {
    74→            let code: u32 = rng.gen_range(100000..999999);
    75→            let code_str = code.to_string();
    76→
    77→            if !codes.contains_key(&code_str) {
    78→                return code_str;
    79→            }
    80→        }
    81→    }
    82→
    83→    /// Create a new support code
    84→    pub async fn create_code(&self, request: CreateCodeRequest) -> SupportCode {
    85→        let code = self.generate_unique_code().await;
    86→        let session_id = Uuid::new_v4();
    87→
    88→        let support_code = SupportCode {
    89→            code: code.clone(),
    90→            session_id,
    91→            created_by: request.technician_name.unwrap_or_else(|| "Unknown".to_string()),
    92→            created_at: Utc::now(),
    93→            status: CodeStatus::Pending,
    94→            client_name: None,
    95→            client_machine: None,
    96→            connected_at: None,
    97→        };
    98→
    99→        let mut codes = self.codes.write().await;
   100→        codes.insert(code.clone(), support_code.clone());
   101→
   102→        let mut session_to_code = self.session_to_code.write().await;
   103→        session_to_code.insert(session_id, code);
   104→
   105→        support_code
   106→    }
   107→
   108→    /// Validate a code and return session info
   109→    pub async fn validate_code(&self, code: &str) -> CodeValidation {
   110→        let codes = self.codes.read().await;
   111→
   112→        match codes.get(code) {
   113→            Some(support_code) => {
   114→                if support_code.status == CodeStatus::Pending || support_code.status == CodeStatus::Connected {
   115→                    CodeValidation {
   116→                        valid: true,
   117→                        session_id: Some(support_code.session_id.to_string()),
   118→                        server_url: Some("wss://connect.azcomputerguru.com/ws/support".to_string()),
   119→                        error: None,
   120→                    }
   121→                } else {
   122→                    CodeValidation {
   123→                        valid: false,
   124→                        session_id: None,
   125→                        server_url: None,
   126→                        error: Some("This code has expired or been used".to_string()),
   127→                    }
   128→                }
   129→            }
   130→            None => CodeValidation {
   131→                valid: false,
   132→                session_id: None,
   133→                server_url: None,
   134→                error: Some("Invalid code".to_string()),
   135→            },
   136→        }
   137→    }
   138→
   139→    /// Mark a code as connected
   140→    pub async fn mark_connected(&self, code: &str, client_name: Option<String>, client_machine: Option<String>) {
   141→        let mut codes = self.codes.write().await;
   142→        if let Some(support_code) = codes.get_mut(code) {
   143→            support_code.status = CodeStatus::Connected;
   144→            support_code.client_name = client_name;
   145→            support_code.client_machine = client_machine;
   146→            support_code.connected_at = Some(Utc::now());
   147→        }
   148→    }
   149→
   150→    /// Link a support code to an actual WebSocket session
   151→    pub async fn link_session(&self, code: &str, real_session_id: Uuid) {
   152→        let mut codes = self.codes.write().await;
   153→        if let Some(support_code) = codes.get_mut(code) {
   154→            // Update session_to_code mapping with real session ID
   155→            let old_session_id = support_code.session_id;
   156→            support_code.session_id = real_session_id;
   157→
   158→            // Update the reverse mapping
   159→            let mut session_to_code = self.session_to_code.write().await;
   160→            session_to_code.remove(&old_session_id);
   161→            session_to_code.insert(real_session_id, code.to_string());
   162→        }
   163→    }
   164→
   165→    /// Get code by its code string
   166→    pub async fn get_code(&self, code: &str) -> Option<SupportCode> {
   167→        let codes = self.codes.read().await;
   168→        codes.get(code).cloned()
   169→    }
   170→
   171→    /// Mark a code as completed
   172→    pub async fn mark_completed(&self, code: &str) {
   173→        let mut codes = self.codes.write().await;
   174→        if let Some(support_code) = codes.get_mut(code) {
   175→            support_code.status = CodeStatus::Completed;
   176→        }
   177→    }
   178→
   179→    /// Cancel a code (works for both pending and connected)
   180→    pub async fn cancel_code(&self, code: &str) -> bool {
   181→        let mut codes = self.codes.write().await;
   182→        if let Some(support_code) = codes.get_mut(code) {
   183→            if support_code.status == CodeStatus::Pending || support_code.status == CodeStatus::Connected {
   184→                support_code.status = CodeStatus::Cancelled;
   185→                return true;
   186→            }
   187→        }
   188→        false
   189→    }
   190→
   191→    /// Check if a code is cancelled
   192→    pub async fn is_cancelled(&self, code: &str) -> bool {
   193→        let codes = self.codes.read().await;
   194→        codes.get(code).map(|c| c.status == CodeStatus::Cancelled).unwrap_or(false)
   195→    }
   196→
   197→    /// Check if a code is valid for connection (exists and is pending)
   198→    pub async fn is_valid_for_connection(&self, code: &str) -> bool {
   199→        let codes = self.codes.read().await;
   200→        codes.get(code).map(|c| c.status == CodeStatus::Pending).unwrap_or(false)
   201→    }
   202→
   203→    /// List all codes (for dashboard)
   204→    pub async fn list_codes(&self) -> Vec<SupportCode> {
   205→        let codes = self.codes.read().await;
   206→        codes.values().cloned().collect()
   207→    }
   208→
   209→    /// List active codes only
   210→    pub async fn list_active_codes(&self) -> Vec<SupportCode> {
   211→        let codes = self.codes.read().await;
   212→        codes.values()
   213→            .filter(|c| c.status == CodeStatus::Pending || c.status == CodeStatus::Connected)
   214→            .cloned()
   215→            .collect()
   216→    }
   217→
   218→    /// Get code by session ID
   219→    pub async fn get_by_session(&self, session_id: Uuid) -> Option<SupportCode> {
   220→        let session_to_code = self.session_to_code.read().await;
   221→        let code = session_to_code.get(&session_id)?;
   222→
   223→        let codes = self.codes.read().await;
   224→        codes.get(code).cloned()
   225→    }
   226→}
   227→
   228→impl Default for SupportCodeManager {
   229→    fn default() -> Self {
   230→        Self::new()
   231→    }
   232→}
   233→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Support session codes management
//!
//! Handles generation and validation of 6-digit support codes
//! for one-time remote support sessions.

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use chrono::{DateTime, Utc};
use rand::Rng;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// A support session code
#[derive(Debug, Clone, Serialize)]
pub struct SupportCode {
    pub code: String,
    pub session_id: Uuid,
    pub created_by: String,
    pub created_at: DateTime<Utc>,
    pub status: CodeStatus,
    pub client_name: Option<String>,
    pub client_machine: Option<String>,
    pub connected_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum CodeStatus {
    Pending,    // Waiting for client to connect
    Connected,  // Client connected, session active
    Completed,  // Session ended normally
    Cancelled,  // Code cancelled by tech
}

/// Request to create a new support code
#[derive(Debug, Deserialize)]
pub struct CreateCodeRequest {
    pub technician_id: Option<String>,
    pub technician_name: Option<String>,
}

/// Response when a code is validated
#[derive(Debug, Serialize)]
pub struct CodeValidation {
    pub valid: bool,
    pub session_id: Option<String>,
    pub server_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

/// Manages support codes
#[derive(Clone)]
pub struct SupportCodeManager {
    codes: Arc<RwLock<HashMap<String, SupportCode>>>,
    session_to_code: Arc<RwLock<HashMap<Uuid, String>>>,
}

impl SupportCodeManager {
    pub fn new() -> Self {
        Self {
            codes: Arc::new(RwLock::new(HashMap::new())),
            session_to_code: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Generate a unique 6-digit code
    async fn generate_unique_code(&self) -> String {
        let codes = self.codes.read().await;
        let mut rng = rand::thread_rng();

        loop {
            let code: u32 = rng.gen_range(100000..999999);
            let code_str = code.to_string();

            if !codes.contains_key(&code_str) {
                return code_str;
            }
        }
    }

    /// Create a new support code
    pub async fn create_code(&self, request: CreateCodeRequest) -> SupportCode {
        let code = self.generate_unique_code().await;
        let session_id = Uuid::new_v4();

        let support_code = SupportCode {
            code: code.clone(),
            session_id,
            created_by: request.technician_name.unwrap_or_else(|| "Unknown".to_string()),
            created_at: Utc::now(),
            status: CodeStatus::Pending,
            client_name: None,
            client_machine: None,
            connected_at: None,
        };

        let mut codes = self.codes.write().await;
        codes.insert(code.clone(), support_code.clone());

        let mut session_to_code = self.session_to_code.write().await;
        session_to_code.insert(session_id, code);

        support_code
    }

    /// Validate a code and return session info
    pub async fn validate_code(&self, code: &str) -> CodeValidation {
        let codes = self.codes.read().await;

        match codes.get(code) {
            Some(support_code) => {
                if support_code.status == CodeStatus::Pending || support_code.status == CodeStatus::Connected {
                    CodeValidation {
                        valid: true,
                        session_id: Some(support_code.session_id.to_string()),
                        server_url: Some("wss://connect.azcomputerguru.com/ws/support".to_string()),
                        error: None,
                    }
                } else {
                    CodeValidation {
                        valid: false,
                        session_id: None,
                        server_url: None,
                        error: Some("This code has expired or been used".to_string()),
                    }
                }
            }
            None => CodeValidation {
                valid: false,
                session_id: None,
                server_url: None,
                error: Some("Invalid code".to_string()),
            },
        }
    }

    /// Mark a code as connected
    pub async fn mark_connected(&self, code: &str, client_name: Option<String>, client_machine: Option<String>) {
        let mut codes = self.codes.write().await;
        if let Some(support_code) = codes.get_mut(code) {
            support_code.status = CodeStatus::Connected;
            support_code.client_name = client_name;
            support_code.client_machine = client_machine;
            support_code.connected_at = Some(Utc::now());
        }
    }

    /// Link a support code to an actual WebSocket session
    pub async fn link_session(&self, code: &str, real_session_id: Uuid) {
        let mut codes = self.codes.write().await;
        if let Some(support_code) = codes.get_mut(code) {
            // Update session_to_code mapping with real session ID
            let old_session_id = support_code.session_id;
            support_code.session_id = real_session_id;

            // Update the reverse mapping
            let mut session_to_code = self.session_to_code.write().await;
            session_to_code.remove(&old_session_id);
            session_to_code.insert(real_session_id, code.to_string());
        }
    }

    /// Get code by its code string
    pub async fn get_code(&self, code: &str) -> Option<SupportCode> {
        let codes = self.codes.read().await;
        codes.get(code).cloned()
    }

    /// Mark a code as completed
    pub async fn mark_completed(&self, code: &str) {
        let mut codes = self.codes.write().await;
        if let Some(support_code) = codes.get_mut(code) {
            support_code.status = CodeStatus::Completed;
        }
    }

    /// Cancel a code (works for both pending and connected)
    pub async fn cancel_code(&self, code: &str) -> bool {
        let mut codes = self.codes.write().await;
        if let Some(support_code) = codes.get_mut(code) {
            if support_code.status == CodeStatus::Pending || support_code.status == CodeStatus::Connected {
                support_code.status = CodeStatus::Cancelled;
                return true;
            }
        }
        false
    }

    /// Check if a code is cancelled
    pub async fn is_cancelled(&self, code: &str) -> bool {
        let codes = self.codes.read().await;
        codes.get(code).map(|c| c.status == CodeStatus::Cancelled).unwrap_or(false)
    }

    /// Check if a code is valid for connection (exists and is pending)
    pub async fn is_valid_for_connection(&self, code: &str) -> bool {
        let codes = self.codes.read().await;
        codes.get(code).map(|c| c.status == CodeStatus::Pending).unwrap_or(false)
    }

    /// List all codes (for dashboard)
    pub async fn list_codes(&self) -> Vec<SupportCode> {
        let codes = self.codes.read().await;
        codes.values().cloned().collect()
    }

    /// List active codes only
    pub async fn list_active_codes(&self) -> Vec<SupportCode> {
        let codes = self.codes.read().await;
        codes.values()
            .filter(|c| c.status == CodeStatus::Pending || c.status == CodeStatus::Connected)
            .cloned()
            .collect()
    }

    /// Get code by session ID
    pub async fn get_by_session(&self, session_id: Uuid) -> Option<SupportCode> {
        let session_to_code = self.session_to_code.read().await;
        let code = session_to_code.get(&session_id)?;

        let codes = self.codes.read().await;
        codes.get(code).cloned()
    }
}

impl Default for SupportCodeManager {
    fn default() -> Self {
        Self::new()
    }
}


Perfect! Now let me check the relay and session modules to understand the overall flow:


     1→//! WebSocket relay handlers
     2→//!
     3→//! Handles WebSocket connections from agents and viewers,
     4→//! relaying video frames and input events between them.
     5→
     6→use axum::{
     7→    extract::{
     8→        ws::{Message, WebSocket, WebSocketUpgrade},
     9→        Query, State,
    10→    },
    11→    response::IntoResponse,
    12→};
    13→use futures_util::{SinkExt, StreamExt};
    14→use prost::Message as ProstMessage;
    15→use serde::Deserialize;
    16→use tracing::{error, info, warn};
    17→use uuid::Uuid;
    18→
    19→use crate::proto;
    20→use crate::session::SessionManager;
    21→use crate::db::{self, Database};
    22→use crate::AppState;
    23→
    24→#[derive(Debug, Deserialize)]
    25→pub struct AgentParams {
    26→    agent_id: String,
    27→    #[serde(default)]
    28→    agent_name: Option<String>,
    29→    #[serde(default)]
    30→    support_code: Option<String>,
    31→    #[serde(default)]
    32→    hostname: Option<String>,
    33→}
    34→
    35→#[derive(Debug, Deserialize)]
    36→pub struct ViewerParams {
    37→    session_id: String,
    38→    #[serde(default = "default_viewer_name")]
    39→    viewer_name: String,
    40→}
    41→
    42→fn default_viewer_name() -> String {
    43→    "Technician".to_string()
    44→}
    45→
    46→/// WebSocket handler for agent connections
    47→pub async fn agent_ws_handler(
    48→    ws: WebSocketUpgrade,
    49→    State(state): State<AppState>,
    50→    Query(params): Query<AgentParams>,
    51→) -> impl IntoResponse {
    52→    let agent_id = params.agent_id;
    53→    let agent_name = params.hostname.or(params.agent_name).unwrap_or_else(|| agent_id.clone());
    54→    let support_code = params.support_code;
    55→    let sessions = state.sessions.clone();
    56→    let support_codes = state.support_codes.clone();
    57→    let db = state.db.clone();
    58→
    59→    ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, support_codes, db, agent_id, agent_name, support_code))
    60→}
    61→
    62→/// WebSocket handler for viewer connections
    63→pub async fn viewer_ws_handler(
    64→    ws: WebSocketUpgrade,
    65→    State(state): State<AppState>,
    66→    Query(params): Query<ViewerParams>,
    67→) -> impl IntoResponse {
    68→    let session_id = params.session_id;
    69→    let viewer_name = params.viewer_name;
    70→    let sessions = state.sessions.clone();
    71→    let db = state.db.clone();
    72→
    73→    ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, db, session_id, viewer_name))
    74→}
    75→
    76→/// Handle an agent WebSocket connection
    77→async fn handle_agent_connection(
    78→    socket: WebSocket,
    79→    sessions: SessionManager,
    80→    support_codes: crate::support_codes::SupportCodeManager,
    81→    db: Option<Database>,
    82→    agent_id: String,
    83→    agent_name: String,
    84→    support_code: Option<String>,
    85→) {
    86→    info!("Agent connected: {} ({})", agent_name, agent_id);
    87→
    88→    let (mut ws_sender, mut ws_receiver) = socket.split();
    89→
    90→    // If a support code was provided, check if it's valid
    91→    if let Some(ref code) = support_code {
    92→        // Check if the code is cancelled or invalid
    93→        if support_codes.is_cancelled(code).await {
    94→            warn!("Agent tried to connect with cancelled code: {}", code);
    95→            // Send disconnect message to agent
    96→            let disconnect_msg = proto::Message {
    97→                payload: Some(proto::message::Payload::Disconnect(proto::Disconnect {
    98→                    reason: "Support session was cancelled by technician".to_string(),
    99→                })),
   100→            };

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! WebSocket relay handlers
//!
//! Handles WebSocket connections from agents and viewers,
//! relaying video frames and input events between them.

use axum::{
    extract::{
        ws::{Message, WebSocket, WebSocketUpgrade},
        Query, State,
    },
    response::IntoResponse,
};
use futures_util::{SinkExt, StreamExt};
use prost::Message as ProstMessage;
use serde::Deserialize;
use tracing::{error, info, warn};
use uuid::Uuid;

use crate::proto;
use crate::session::SessionManager;
use crate::db::{self, Database};
use crate::AppState;

#[derive(Debug, Deserialize)]
pub struct AgentParams {
    agent_id: String,
    #[serde(default)]
    agent_name: Option<String>,
    #[serde(default)]
    support_code: Option<String>,
    #[serde(default)]
    hostname: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct ViewerParams {
    session_id: String,
    #[serde(default = "default_viewer_name")]
    viewer_name: String,
}

fn default_viewer_name() -> String {
    "Technician".to_string()
}

/// WebSocket handler for agent connections
pub async fn agent_ws_handler(
    ws: WebSocketUpgrade,
    State(state): State<AppState>,
    Query(params): Query<AgentParams>,
) -> impl IntoResponse {
    let agent_id = params.agent_id;
    let agent_name = params.hostname.or(params.agent_name).unwrap_or_else(|| agent_id.clone());
    let support_code = params.support_code;
    let sessions = state.sessions.clone();
    let support_codes = state.support_codes.clone();
    let db = state.db.clone();

    ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, support_codes, db, agent_id, agent_name, support_code))
}

/// WebSocket handler for viewer connections
pub async fn viewer_ws_handler(
    ws: WebSocketUpgrade,
    State(state): State<AppState>,
    Query(params): Query<ViewerParams>,
) -> impl IntoResponse {
    let session_id = params.session_id;
    let viewer_name = params.viewer_name;
    let sessions = state.sessions.clone();
    let db = state.db.clone();

    ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, db, session_id, viewer_name))
}

/// Handle an agent WebSocket connection
async fn handle_agent_connection(
    socket: WebSocket,
    sessions: SessionManager,
    support_codes: crate::support_codes::SupportCodeManager,
    db: Option<Database>,
    agent_id: String,
    agent_name: String,
    support_code: Option<String>,
) {
    info!("Agent connected: {} ({})", agent_name, agent_id);

    let (mut ws_sender, mut ws_receiver) = socket.split();

    // If a support code was provided, check if it's valid
    if let Some(ref code) = support_code {
        // Check if the code is cancelled or invalid
        if support_codes.is_cancelled(code).await {
            warn!("Agent tried to connect with cancelled code: {}", code);
            // Send disconnect message to agent
            let disconnect_msg = proto::Message {
                payload: Some(proto::message::Payload::Disconnect(proto::Disconnect {
                    reason: "Support session was cancelled by technician".to_string(),
                })),
            };


     1→[package]
     2→name = "guruconnect-server"
     3→version = "0.1.0"
     4→edition = "2021"
     5→authors = ["AZ Computer Guru"]
     6→description = "GuruConnect Remote Desktop Relay Server"
     7→
     8→[dependencies]
     9→# Async runtime
    10→tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] }
    11→
    12→# Web framework
    13→axum = { version = "0.7", features = ["ws", "macros"] }
    14→tower = "0.5"
    15→tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip", "fs"] }
    16→
    17→# WebSocket
    18→futures-util = "0.3"
    19→
    20→# Database
    21→sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono", "json"] }
    22→
    23→# Protocol (protobuf)
    24→prost = "0.13"
    25→prost-types = "0.13"
    26→bytes = "1"
    27→
    28→# Serialization
    29→serde = { version = "1", features = ["derive"] }
    30→serde_json = "1"
    31→
    32→# Logging
    33→tracing = "0.1"
    34→tracing-subscriber = { version = "0.3", features = ["env-filter"] }
    35→
    36→# Error handling
    37→anyhow = "1"
    38→thiserror = "1"
    39→
    40→# Configuration
    41→toml = "0.8"
    42→
    43→# Auth
    44→jsonwebtoken = "9"
    45→argon2 = "0.5"
    46→
    47→# Crypto
    48→ring = "0.17"
    49→
    50→# UUID
    51→uuid = { version = "1", features = ["v4", "serde"] }
    52→
    53→# Time
    54→chrono = { version = "0.4", features = ["serde"] }
    55→rand = "0.8"
    56→
    57→[build-dependencies]
    58→prost-build = "0.13"
    59→
    60→[profile.release]
    61→lto = true
    62→codegen-units = 1
    63→strip = true
    64→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

[package]
name = "guruconnect-server"
version = "0.1.0"
edition = "2021"
authors = ["AZ Computer Guru"]
description = "GuruConnect Remote Desktop Relay Server"

[dependencies]
# Async runtime
tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] }

# Web framework
axum = { version = "0.7", features = ["ws", "macros"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip", "fs"] }

# WebSocket
futures-util = "0.3"

# Database
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono", "json"] }

# Protocol (protobuf)
prost = "0.13"
prost-types = "0.13"
bytes = "1"

# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# Error handling
anyhow = "1"
thiserror = "1"

# Configuration
toml = "0.8"

# Auth
jsonwebtoken = "9"
argon2 = "0.5"

# Crypto
ring = "0.17"

# UUID
uuid = { version = "1", features = ["v4", "serde"] }

# Time
chrono = { version = "0.4", features = ["serde"] }
rand = "0.8"

[build-dependencies]
prost-build = "0.13"

[profile.release]
lto = true
codegen-units = 1
strip = true


Perfect! Now let me check the config file and check if there are any environment variables being used:


grep -r "JWT_SECRET\|AUTH\|authentication" "C:/Users/MikeSwanson/Claude/guru-connect/server/src" --include="*.rs" | head -20

Search for auth-related configuration


     1→//! Server configuration
     2→
     3→use anyhow::Result;
     4→use serde::Deserialize;
     5→use std::env;
     6→
     7→#[derive(Debug, Clone, Deserialize)]
     8→pub struct Config {
     9→    /// Address to listen on (e.g., "0.0.0.0:8080")
    10→    pub listen_addr: String,
    11→
    12→    /// Database URL (optional - server works without it)
    13→    pub database_url: Option<String>,
    14→
    15→    /// Maximum database connections in pool
    16→    pub database_max_connections: u32,
    17→
    18→    /// JWT secret for authentication
    19→    pub jwt_secret: Option<String>,
    20→
    21→    /// Enable debug logging
    22→    pub debug: bool,
    23→}
    24→
    25→impl Config {
    26→    /// Load configuration from environment variables
    27→    pub fn load() -> Result<Self> {
    28→        Ok(Self {
    29→            listen_addr: env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:8080".to_string()),
    30→            database_url: env::var("DATABASE_URL").ok(),
    31→            database_max_connections: env::var("DATABASE_MAX_CONNECTIONS")
    32→                .ok()
    33→                .and_then(|v| v.parse().ok())
    34→                .unwrap_or(5),
    35→            jwt_secret: env::var("JWT_SECRET").ok(),
    36→            debug: env::var("DEBUG")
    37→                .map(|v| v == "1" || v.to_lowercase() == "true")
    38→                .unwrap_or(false),
    39→        })
    40→    }
    41→}
    42→
    43→impl Default for Config {
    44→    fn default() -> Self {
    45→        Self {
    46→            listen_addr: "0.0.0.0:8080".to_string(),
    47→            database_url: None,
    48→            database_max_connections: 5,
    49→            jwt_secret: None,
    50→            debug: false,
    51→        }
    52→    }
    53→}
    54→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Server configuration

use anyhow::Result;
use serde::Deserialize;
use std::env;

#[derive(Debug, Clone, Deserialize)]
pub struct Config {
    /// Address to listen on (e.g., "0.0.0.0:8080")
    pub listen_addr: String,

    /// Database URL (optional - server works without it)
    pub database_url: Option<String>,

    /// Maximum database connections in pool
    pub database_max_connections: u32,

    /// JWT secret for authentication
    pub jwt_secret: Option<String>,

    /// Enable debug logging
    pub debug: bool,
}

impl Config {
    /// Load configuration from environment variables
    pub fn load() -> Result<Self> {
        Ok(Self {
            listen_addr: env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:8080".to_string()),
            database_url: env::var("DATABASE_URL").ok(),
            database_max_connections: env::var("DATABASE_MAX_CONNECTIONS")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or(5),
            jwt_secret: env::var("JWT_SECRET").ok(),
            debug: env::var("DEBUG")
                .map(|v| v == "1" || v.to_lowercase() == "true")
                .unwrap_or(false),
        })
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            listen_addr: "0.0.0.0:8080".to_string(),
            database_url: None,
            database_max_connections: 5,
            jwt_secret: None,
            debug: false,
        }
    }
}


C:/Users/MikeSwanson/Claude/guru-connect/server/src/config.rs:    /// JWT secret for authentication
C:/Users/MikeSwanson/Claude/guru-connect/server/src/config.rs:            jwt_secret: env::var("JWT_SECRET").ok(),