Some checks failed
Build and Test / Build Server (Linux) (push) Has been cancelled
Build and Test / Build Agent (Windows) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Build Summary (push) Has been cancelled
Run Tests / Test Server (push) Has been cancelled
Run Tests / Test Agent (push) Has been cancelled
Run Tests / Code Coverage (push) Has been cancelled
Run Tests / Lint and Format Check (push) Has been cancelled
Brings azcomputerguru/guru-connect up to the authoritative working copy that had been maintained in the claudetools monorepo: Phase 1 security and infrastructure (middleware, metrics, utils, token blacklist, deployment scripts, security audits) plus the native-remote-control integration spec. Preserves the repo .gitignore, .cargo, and server/static/downloads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
134 lines
4.3 KiB
Rust
134 lines
4.3 KiB
Rust
//! 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<Utc>,
|
|
pub viewer_id: Option<String>,
|
|
pub viewer_name: Option<String>,
|
|
pub details: Option<JsonValue>,
|
|
pub ip_address: Option<String>,
|
|
}
|
|
|
|
/// 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<JsonValue>,
|
|
ip_address: Option<IpAddr>,
|
|
) -> Result<i64, sqlx::Error> {
|
|
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<Vec<SessionEvent>, 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<Vec<SessionEvent>, 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<Vec<SessionEvent>, 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<Vec<SessionEvent>, 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
|
|
}
|