Security: Require authentication for all WebSocket and API endpoints
- REST API: All session/code/machine endpoints now require AuthenticatedUser - Viewer WebSocket: Requires JWT token in query params (token=...) - Agent WebSocket: Requires either valid support code OR API key - Dashboard: Passes JWT token when connecting to viewer WS - Native viewer: Passes token in protocol URL and WebSocket connection - Added AGENT_API_KEY env var support for persistent agents - Added get_status() to SupportCodeManager for auth validation This fixes the security vulnerability where unauthenticated agents could connect and appear in the dashboard without any credentials. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ use tracing_subscriber::FmtSubscriber;
|
||||
use serde::Deserialize;
|
||||
|
||||
use support_codes::{SupportCodeManager, CreateCodeRequest, SupportCode, CodeValidation};
|
||||
use auth::{JwtConfig, hash_password, generate_random_password};
|
||||
use auth::{JwtConfig, hash_password, generate_random_password, AuthenticatedUser};
|
||||
|
||||
/// Application state
|
||||
#[derive(Clone)]
|
||||
@@ -43,6 +43,8 @@ pub struct AppState {
|
||||
support_codes: SupportCodeManager,
|
||||
db: Option<db::Database>,
|
||||
pub jwt_config: Arc<JwtConfig>,
|
||||
/// Optional API key for persistent agents (env: AGENT_API_KEY)
|
||||
pub agent_api_key: Option<String>,
|
||||
}
|
||||
|
||||
/// Middleware to inject JWT config into request extensions
|
||||
@@ -163,12 +165,21 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Agent API key for persistent agents (optional)
|
||||
let agent_api_key = std::env::var("AGENT_API_KEY").ok();
|
||||
if agent_api_key.is_some() {
|
||||
info!("AGENT_API_KEY configured for persistent agents");
|
||||
} else {
|
||||
info!("No AGENT_API_KEY set - persistent agents will need JWT token or support code");
|
||||
}
|
||||
|
||||
// Create application state
|
||||
let state = AppState {
|
||||
sessions,
|
||||
support_codes: SupportCodeManager::new(),
|
||||
db: database,
|
||||
jwt_config,
|
||||
agent_api_key,
|
||||
};
|
||||
|
||||
// Build router
|
||||
@@ -252,6 +263,7 @@ async fn health() -> &'static str {
|
||||
// Support code API handlers
|
||||
|
||||
async fn create_code(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
Json(request): Json<CreateCodeRequest>,
|
||||
) -> Json<SupportCode> {
|
||||
@@ -261,6 +273,7 @@ async fn create_code(
|
||||
}
|
||||
|
||||
async fn list_codes(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<SupportCode>> {
|
||||
Json(state.support_codes.list_active_codes().await)
|
||||
@@ -279,6 +292,7 @@ async fn validate_code(
|
||||
}
|
||||
|
||||
async fn cancel_code(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
Path(code): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
@@ -292,6 +306,7 @@ async fn cancel_code(
|
||||
// Session API handlers (updated to use AppState)
|
||||
|
||||
async fn list_sessions(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
) -> Json<Vec<api::SessionInfo>> {
|
||||
let sessions = state.sessions.list_sessions().await;
|
||||
@@ -299,6 +314,7 @@ async fn list_sessions(
|
||||
}
|
||||
|
||||
async fn get_session(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Json<api::SessionInfo>, (StatusCode, &'static str)> {
|
||||
@@ -312,6 +328,7 @@ async fn get_session(
|
||||
}
|
||||
|
||||
async fn disconnect_session(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
@@ -331,6 +348,7 @@ async fn disconnect_session(
|
||||
// Machine API handlers
|
||||
|
||||
async fn list_machines(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Vec<api::MachineInfo>>, (StatusCode, &'static str)> {
|
||||
let db = state.db.as_ref()
|
||||
@@ -343,6 +361,7 @@ async fn list_machines(
|
||||
}
|
||||
|
||||
async fn get_machine(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
Path(agent_id): Path<String>,
|
||||
) -> Result<Json<api::MachineInfo>, (StatusCode, &'static str)> {
|
||||
@@ -357,6 +376,7 @@ async fn get_machine(
|
||||
}
|
||||
|
||||
async fn get_machine_history(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
Path(agent_id): Path<String>,
|
||||
) -> Result<Json<api::MachineHistory>, (StatusCode, &'static str)> {
|
||||
@@ -387,6 +407,7 @@ async fn get_machine_history(
|
||||
}
|
||||
|
||||
async fn delete_machine(
|
||||
_user: AuthenticatedUser, // Require authentication
|
||||
State(state): State<AppState>,
|
||||
Path(agent_id): Path<String>,
|
||||
Query(params): Query<api::DeleteMachineParams>,
|
||||
|
||||
Reference in New Issue
Block a user