Enhance room access validation and debugging in command handling

Improved error handling in `handle_init_command` and `handle_join_command` to provide clearer feedback when room resolution fails. Added detailed debug logging for room access attempts, including client ID and requested room information, when debugging is enabled. Introduced a new function, `room_debug_enabled`, to toggle debug logging based on environment variables. Updated password validation logic to require a password when one is set for a room.
This commit is contained in:
Torsten Schulz (local)
2026-03-04 23:30:26 +01:00
parent 553602d5b4
commit 8285466ba2

View File

@@ -2,6 +2,7 @@ use crate::types::{ChatState, ClientId, Command, RoomMeta, ServerConfig};
use bcrypt::verify as bcrypt_verify;
use serde_json::{json, Value};
use std::collections::{HashMap, HashSet};
use std::env;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock;
@@ -160,11 +161,22 @@ async fn handle_init_command(
let (resolved_room_name, room_meta) = {
let guard = state.read().await;
let Some(resolved_room_name) = resolve_room_name(&guard, &room_name) else {
send_error(client_id, Arc::clone(&state), "room_not_found_or_join_failed").await;
if room_debug_enabled() {
let known_rooms = guard.room_meta.keys().cloned().collect::<Vec<_>>();
eprintln!(
"[yourchat2][room-debug][init] client_id={client_id} requested_room='{room_name}' resolve_failed known_rooms={known_rooms:?}"
);
}
send_error(client_id, Arc::clone(&state), "room_not_found").await;
return;
};
let Some(room_meta) = guard.room_meta.get(&resolved_room_name).cloned() else {
send_error(client_id, Arc::clone(&state), "room_not_found_or_join_failed").await;
if room_debug_enabled() {
eprintln!(
"[yourchat2][room-debug][init] client_id={client_id} requested_room='{room_name}' resolved_room='{resolved_room_name}' missing_meta"
);
}
send_error(client_id, Arc::clone(&state), "room_not_found").await;
return;
};
(resolved_room_name, room_meta)
@@ -181,6 +193,20 @@ async fn handle_init_command(
)
.await
{
if room_debug_enabled() {
eprintln!(
"[yourchat2][room-debug][init] client_id={client_id} user='{}' room='{}' denied='{}' user_profile={{fid:{:?},gender:{:?},age:{:?},rights:{:?},right_ids:{:?}}} room_meta={:?}",
profile.display_name,
resolved_room_name,
access_error,
profile.falukant_user_id,
profile.gender_id,
profile.age,
profile.rights,
profile.right_type_ids,
room_meta
);
}
send_error(client_id, Arc::clone(&state), access_error).await;
return;
}
@@ -199,7 +225,12 @@ async fn handle_init_command(
}
if !guard.room_meta.contains_key(&resolved_room_name) {
drop(guard);
send_error(client_id, Arc::clone(&state), "room_not_found_or_join_failed").await;
if room_debug_enabled() {
eprintln!(
"[yourchat2][room-debug][init] client_id={client_id} resolved_room='{resolved_room_name}' vanished_before_join"
);
}
send_error(client_id, Arc::clone(&state), "room_not_found").await;
return;
}
@@ -299,11 +330,22 @@ async fn handle_join_command(
let (resolved_room, room_meta, falukant_user_id, gender_id, age, right_type_ids, rights) = {
let guard = state.read().await;
let Some(resolved_room) = resolve_room_name(&guard, &room) else {
send_error(client_id, Arc::clone(&state), "room_not_found_or_join_failed").await;
if room_debug_enabled() {
let known_rooms = guard.room_meta.keys().cloned().collect::<Vec<_>>();
eprintln!(
"[yourchat2][room-debug][join] client_id={client_id} requested_room='{room}' resolve_failed known_rooms={known_rooms:?}"
);
}
send_error(client_id, Arc::clone(&state), "room_not_found").await;
return;
};
let Some(room_meta) = guard.room_meta.get(&resolved_room).cloned() else {
send_error(client_id, Arc::clone(&state), "room_not_found_or_join_failed").await;
if room_debug_enabled() {
eprintln!(
"[yourchat2][room-debug][join] client_id={client_id} requested_room='{room}' resolved_room='{resolved_room}' missing_meta"
);
}
send_error(client_id, Arc::clone(&state), "room_not_found").await;
return;
};
let Some(client) = guard.clients.get(&client_id) else {
@@ -331,6 +373,19 @@ async fn handle_join_command(
)
.await
{
if room_debug_enabled() {
eprintln!(
"[yourchat2][room-debug][join] client_id={client_id} room='{}' denied='{}' user={{fid:{:?},gender:{:?},age:{:?},rights:{:?},right_ids:{:?}}} room_meta={:?}",
resolved_room,
access_error,
falukant_user_id,
gender_id,
age,
rights,
right_type_ids,
room_meta
);
}
send_error(client_id, Arc::clone(&state), access_error).await;
return;
}
@@ -338,7 +393,12 @@ async fn handle_join_command(
let mut guard = state.write().await;
if !guard.room_meta.contains_key(&resolved_room) {
drop(guard);
send_error(client_id, Arc::clone(&state), "room_not_found_or_join_failed").await;
if room_debug_enabled() {
eprintln!(
"[yourchat2][room-debug][join] client_id={client_id} resolved_room='{resolved_room}' vanished_before_join"
);
}
send_error(client_id, Arc::clone(&state), "room_not_found").await;
return;
}
let Some(client) = guard.clients.get_mut(&client_id) else {
@@ -883,6 +943,12 @@ async fn handle_create_room_command(
empty_since_unix: Some(now_unix()),
},
);
if room_debug_enabled() {
eprintln!(
"[yourchat2][room-debug][create] client_id={client_id} room='{}' owner_chat_user_id={} created",
parsed.room_name, owner_chat_user_id
);
}
}
state::send_room_list(client_id, Arc::clone(&state)).await;
state::send_to_client(
@@ -1133,8 +1199,13 @@ async fn room_access_allowed(
}
if let Some(room_password) = &room.password {
if !room_password.is_empty() && !password_matches(room_password, provided_password) {
return Err("room_password_invalid");
if !room_password.is_empty() {
if provided_password.trim().is_empty() {
return Err("room_password_required");
}
if !password_matches(room_password, provided_password) {
return Err("room_password_invalid");
}
}
}
@@ -1361,6 +1432,13 @@ fn now_unix() -> i64 {
.unwrap_or(0)
}
fn room_debug_enabled() -> bool {
matches!(
env::var("CHAT_DEBUG_ROOM_ACCESS").ok().as_deref(),
Some("1") | Some("true") | Some("TRUE") | Some("yes") | Some("YES") | Some("on") | Some("ON")
)
}
fn command_from_chat_line(message: &str, token: Option<String>) -> Option<Command> {
let trimmed = message.trim();
if !trimmed.starts_with('/') {