diff --git a/src/websocket_server.rs b/src/websocket_server.rs index d4b7f98..d0173c2 100644 --- a/src/websocket_server.rs +++ b/src/websocket_server.rs @@ -480,15 +480,24 @@ async fn handle_connection( let guard = user_id_for_incoming.lock().await; guard.clone() }; - let allowed = uid_opt + let access = uid_opt .as_deref() .map(|uid| user_can_read_worker_schedules(&pool_for_incoming, uid)) - .unwrap_or(false); - if !allowed { + .unwrap_or(WorkerScheduleAccessDebug { + requested_user_id: "".to_string(), + resolved_community_user_id: None, + matched_by: "missing_setUserId".to_string(), + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some("missing_setUserId".to_string()), + }); + if !access.allowed { let payload = serde_json::json!({ "event": "getWorkerSchedulesResponse", "ok": false, - "error": "forbidden" + "error": "forbidden", + "debug": access }) .to_string(); let _ = client_tx_incoming.send(payload).await; @@ -514,15 +523,24 @@ async fn handle_connection( let guard = user_id_for_incoming.lock().await; guard.clone() }; - let allowed = uid_opt + let access = uid_opt .as_deref() .map(|uid| user_can_read_worker_schedules(&pool_for_incoming, uid)) - .unwrap_or(false); - if !allowed { + .unwrap_or(WorkerScheduleAccessDebug { + requested_user_id: "".to_string(), + resolved_community_user_id: None, + matched_by: "missing_setUserId".to_string(), + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some("missing_setUserId".to_string()), + }); + if !access.allowed { let payload = serde_json::json!({ "event": "getWorkerSchedulesDetailedResponse", "ok": false, - "error": "forbidden" + "error": "forbidden", + "debug": access }) .to_string(); let _ = client_tx_incoming.send(payload).await; @@ -704,14 +722,47 @@ async fn handle_connection( println!("[WebSocketServer] Verbindung geschlossen: {}", peer_addr); } -fn user_can_read_worker_schedules(pool: &ConnectionPool, user_id_raw: &str) -> bool { +#[derive(Debug, Clone, Serialize)] +struct WorkerScheduleAccessDebug { + requested_user_id: String, + resolved_community_user_id: Option, + matched_by: String, + allowed_direct: bool, + allowed_falukant: bool, + allowed: bool, + error: Option, +} + +fn user_can_read_worker_schedules(pool: &ConnectionPool, user_id_raw: &str) -> WorkerScheduleAccessDebug { let mut conn = match pool.get() { Ok(c) => c, - Err(_) => return false, + Err(e) => { + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: None, + matched_by: "none".to_string(), + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some(format!("db_connection_failed: {e}")), + }; + } }; - let uid = if let Ok(v) = user_id_raw.parse::() { - if v > 0 { v } else { return false; } + let (uid, matched_by) = if let Ok(v) = user_id_raw.parse::() { + if v > 0 { + (v, "numeric".to_string()) + } else { + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: None, + matched_by: "numeric_invalid".to_string(), + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some("invalid_numeric_user_id".to_string()), + }; + } } else { // Fallback: setUserId kann auch username/hashed_id aus dem Community-User sein. let resolve_sql = r#" @@ -721,23 +772,58 @@ SELECT id OR LOWER(COALESCE(hashed_id, '')) = LOWER($1::text) LIMIT 1; "#; - if conn.prepare("ws_resolve_community_user_id", resolve_sql).is_err() { - return false; + if let Err(e) = conn.prepare("ws_resolve_community_user_id", resolve_sql) { + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: None, + matched_by: "lookup_prepare_failed".to_string(), + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some(format!("resolve_prepare_failed: {e}")), + }; } let rows = match conn.execute("ws_resolve_community_user_id", &[&user_id_raw]) { Ok(r) => r, - Err(_) => return false, + Err(e) => { + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: None, + matched_by: "lookup_execute_failed".to_string(), + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some(format!("resolve_execute_failed: {e}")), + }; + } }; let Some(row) = rows.first() else { - return false; + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: None, + matched_by: "username_or_hashed_id".to_string(), + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some("user_not_found".to_string()), + }; }; - row.get("id") + let resolved = row.get("id") .and_then(|v| v.parse::().ok()) .filter(|v| *v > 0) - .unwrap_or(-1) + .unwrap_or(-1); + (resolved, "username_or_hashed_id".to_string()) }; if uid <= 0 { - return false; + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: None, + matched_by, + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some("resolved_user_id_invalid".to_string()), + }; } let sql = r#" @@ -768,8 +854,16 @@ EXISTS ( ) AS allowed_falukant; "#; - if conn.prepare("ws_can_read_worker_schedules", sql).is_err() { - return false; + if let Err(e) = conn.prepare("ws_can_read_worker_schedules", sql) { + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: Some(uid), + matched_by, + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some(format!("rights_prepare_failed: {e}")), + }; } let admin = RIGHT_ADMIN.to_string(); let allowed_right = RIGHT_WORKER_SCHEDULE.to_string(); @@ -779,10 +873,28 @@ EXISTS ( &[&uid, &admin, &allowed_right, &mainadmin], ) { Ok(r) => r, - Err(_) => return false, + Err(e) => { + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: Some(uid), + matched_by, + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some(format!("rights_execute_failed: {e}")), + }; + } }; let Some(row) = rows.first() else { - return false; + return WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: Some(uid), + matched_by, + allowed_direct: false, + allowed_falukant: false, + allowed: false, + error: Some("rights_empty_result".to_string()), + }; }; let direct = row .get("allowed_direct") @@ -792,7 +904,15 @@ EXISTS ( .get("allowed_falukant") .map(|v| v == "true" || v == "t" || v == "1") .unwrap_or(false); - direct || falukant + WorkerScheduleAccessDebug { + requested_user_id: user_id_raw.to_string(), + resolved_community_user_id: Some(uid), + matched_by, + allowed_direct: direct, + allowed_falukant: falukant, + allowed: direct || falukant, + error: None, + } } fn build_worker_schedule_overview(now_secs: u64) -> Vec {