//! Audit event logging use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use sqlx::PgPool; use std::net::IpAddr; use uuid::Uuid; /// Session event record from database #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct SessionEvent { pub id: i64, pub session_id: Uuid, pub event_type: String, pub timestamp: DateTime, pub viewer_id: Option, pub viewer_name: Option, pub details: Option, pub ip_address: Option, } /// Event types for session audit logging pub struct EventTypes; impl EventTypes { pub const SESSION_STARTED: &'static str = "session_started"; pub const SESSION_ENDED: &'static str = "session_ended"; pub const SESSION_TIMEOUT: &'static str = "session_timeout"; pub const VIEWER_JOINED: &'static str = "viewer_joined"; pub const VIEWER_LEFT: &'static str = "viewer_left"; pub const STREAMING_STARTED: &'static str = "streaming_started"; pub const STREAMING_STOPPED: &'static str = "streaming_stopped"; // Failed connection events (security audit trail) pub const CONNECTION_REJECTED_NO_AUTH: &'static str = "connection_rejected_no_auth"; pub const CONNECTION_REJECTED_INVALID_CODE: &'static str = "connection_rejected_invalid_code"; pub const CONNECTION_REJECTED_EXPIRED_CODE: &'static str = "connection_rejected_expired_code"; pub const CONNECTION_REJECTED_INVALID_API_KEY: &'static str = "connection_rejected_invalid_api_key"; pub const CONNECTION_REJECTED_CANCELLED_CODE: &'static str = "connection_rejected_cancelled_code"; } /// Log a session event pub async fn log_event( pool: &PgPool, session_id: Uuid, event_type: &str, viewer_id: Option<&str>, viewer_name: Option<&str>, details: Option, ip_address: Option, ) -> Result { let ip_str = ip_address.map(|ip| ip.to_string()); let result = sqlx::query_scalar::<_, i64>( r#" INSERT INTO connect_session_events (session_id, event_type, viewer_id, viewer_name, details, ip_address) VALUES ($1, $2, $3, $4, $5, $6::inet) RETURNING id "#, ) .bind(session_id) .bind(event_type) .bind(viewer_id) .bind(viewer_name) .bind(details) .bind(ip_str) .fetch_one(pool) .await?; Ok(result) } /// Get events for a session pub async fn get_session_events( pool: &PgPool, session_id: Uuid, ) -> Result, sqlx::Error> { sqlx::query_as::<_, SessionEvent>( "SELECT id, session_id, event_type, timestamp, viewer_id, viewer_name, details, ip_address::text as ip_address FROM connect_session_events WHERE session_id = $1 ORDER BY timestamp" ) .bind(session_id) .fetch_all(pool) .await } /// Get recent events (for dashboard) pub async fn get_recent_events( pool: &PgPool, limit: i64, ) -> Result, sqlx::Error> { sqlx::query_as::<_, SessionEvent>( "SELECT id, session_id, event_type, timestamp, viewer_id, viewer_name, details, ip_address::text as ip_address FROM connect_session_events ORDER BY timestamp DESC LIMIT $1" ) .bind(limit) .fetch_all(pool) .await } /// Get events by type pub async fn get_events_by_type( pool: &PgPool, event_type: &str, limit: i64, ) -> Result, sqlx::Error> { sqlx::query_as::<_, SessionEvent>( "SELECT id, session_id, event_type, timestamp, viewer_id, viewer_name, details, ip_address::text as ip_address FROM connect_session_events WHERE event_type = $1 ORDER BY timestamp DESC LIMIT $2" ) .bind(event_type) .bind(limit) .fetch_all(pool) .await } /// Get all events for a machine (by joining through sessions) pub async fn get_events_for_machine( pool: &PgPool, machine_id: Uuid, ) -> Result, sqlx::Error> { sqlx::query_as::<_, SessionEvent>( r#" SELECT e.id, e.session_id, e.event_type, e.timestamp, e.viewer_id, e.viewer_name, e.details, e.ip_address::text as ip_address FROM connect_session_events e JOIN connect_sessions s ON e.session_id = s.id WHERE s.machine_id = $1 ORDER BY e.timestamp DESC "# ) .bind(machine_id) .fetch_all(pool) .await }