Enhance room access validation and database structure in yourchat2

Updated the room access logic in `handle_init_command` and `handle_join_command` to improve validation against user rights and room ownership. Introduced new fields in the `RoomMeta` structure for room type and friends-only access. Modified database queries to accommodate these changes, ensuring robust access control based on user relationships and room settings.
This commit is contained in:
Torsten Schulz (local)
2026-03-04 22:35:16 +01:00
parent fbbb698ed9
commit 3eaf31d64f
3 changed files with 216 additions and 44 deletions

116
src/db.rs
View File

@@ -161,6 +161,18 @@ pub async fn load_room_configs(config: &ServerConfig) -> Result<Vec<RoomMeta>, &
let has_required_user_right = column_exists(client.clone(), "chat", "room", "required_user_right_id")
.await
.unwrap_or(false);
let has_friends_of_owner_only = column_exists(client.clone(), "chat", "room", "friends_of_owner_only")
.await
.unwrap_or(false);
let has_room_type = column_exists(client.clone(), "chat", "room", "room_type_id")
.await
.unwrap_or(false);
let has_password_plain = column_exists(client.clone(), "chat", "room", "password")
.await
.unwrap_or(false);
let has_password_hash = column_exists(client.clone(), "chat", "room", "password_hash")
.await
.unwrap_or(false);
let gender_column = if has_gender_restriction {
"r.gender_restriction_id"
} else {
@@ -171,8 +183,26 @@ pub async fn load_room_configs(config: &ServerConfig) -> Result<Vec<RoomMeta>, &
} else {
"NULL::int"
};
let friends_only_column = if has_friends_of_owner_only {
"r.friends_of_owner_only"
} else {
"false"
};
let room_type_column = if has_room_type {
"r.room_type_id"
} else {
"NULL::int"
};
let room_password_column = match (has_password_hash, has_password_plain) {
(true, true) => "COALESCE(NULLIF(r.password_hash, ''), NULLIF(r.\"password\", ''))",
(true, false) => "NULLIF(r.password_hash, '')",
(false, true) => "NULLIF(r.\"password\", '')",
(false, false) => "NULL::text",
};
let query = format!(
"SELECT r.title, r.password_hash, r.is_public, r.owner_id, r.min_age, r.max_age, {gender_column} AS gender_restriction_id, {required_right_column} AS required_user_right_id
"SELECT r.title, {room_password_column} AS room_password, r.is_public, r.owner_id, r.min_age, r.max_age,
{gender_column} AS gender_restriction_id, {required_right_column} AS required_user_right_id,
{room_type_column} AS room_type_id, {friends_only_column} AS friends_of_owner_only
FROM chat.room r
LEFT JOIN chat.room_type rt ON r.room_type_id = rt.id"
);
@@ -182,13 +212,15 @@ pub async fn load_room_configs(config: &ServerConfig) -> Result<Vec<RoomMeta>, &
for row in rows {
rooms.push(RoomMeta {
name: row.get::<_, String>("title"),
password: row.get::<_, Option<String>>("password_hash"),
password: row.get::<_, Option<String>>("room_password"),
gender_restriction_id: row.get::<_, Option<i32>>("gender_restriction_id"),
required_user_right_id: row.get::<_, Option<i32>>("required_user_right_id"),
min_age: row.get::<_, Option<i32>>("min_age"),
max_age: row.get::<_, Option<i32>>("max_age"),
is_public: row.get::<_, bool>("is_public"),
owner_id: row.get::<_, Option<i32>>("owner_id"),
room_type_id: row.get::<_, Option<i32>>("room_type_id"),
friends_of_owner_only: row.get::<_, bool>("friends_of_owner_only"),
});
}
@@ -383,3 +415,83 @@ async fn load_community_rights(client: Arc<PgClient>, falukant_user_id: i32) ->
}
Ok((rights, right_ids))
}
pub async fn user_is_room_owner(config: &ServerConfig, room_owner_id: i32, falukant_user_id: i32) -> bool {
if room_owner_id <= 0 || falukant_user_id <= 0 {
return false;
}
if room_owner_id == falukant_user_id {
return true;
}
let Some(client) = config.db_client.clone() else {
return false;
};
match client
.query_opt(
"SELECT falukant_user_id FROM chat.\"user\" WHERE id = $1 LIMIT 1",
&[&room_owner_id],
)
.await
{
Ok(Some(row)) => row.get::<_, Option<i32>>("falukant_user_id") == Some(falukant_user_id),
_ => false,
}
}
pub async fn is_friend_of_room_owner(config: &ServerConfig, room_owner_id: i32, falukant_user_id: i32) -> bool {
if room_owner_id <= 0 || falukant_user_id <= 0 {
return false;
}
if user_is_room_owner(config, room_owner_id, falukant_user_id).await {
return true;
}
let Some(client) = config.db_client.clone() else {
return false;
};
let owner_falukant_id = match resolve_owner_falukant_id(client.clone(), room_owner_id).await {
Some(id) => id,
None => return false,
};
if owner_falukant_id == falukant_user_id {
return true;
}
for query in [
"SELECT 1 FROM community.user_friend uf WHERE ((uf.user_id = $1 AND uf.friend_user_id = $2) OR (uf.user_id = $2 AND uf.friend_user_id = $1)) LIMIT 1",
"SELECT 1 FROM community.friend f WHERE ((f.user_id = $1 AND f.friend_user_id = $2) OR (f.user_id = $2 AND f.friend_user_id = $1)) LIMIT 1",
"SELECT 1
FROM community.friendship f
WHERE ((f.user1_id = $1 AND f.user2_id = $2) OR (f.user1_id = $2 AND f.user2_id = $1))
AND COALESCE(f.accepted, false) = true
AND COALESCE(f.denied, false) = false
AND COALESCE(f.withdrawn, false) = false
LIMIT 1",
"SELECT 1 FROM community.contact c WHERE ((c.user_id = $1 AND c.contact_user_id = $2) OR (c.user_id = $2 AND c.contact_user_id = $1)) LIMIT 1",
] {
match client.query_opt(query, &[&falukant_user_id, &owner_falukant_id]).await {
Ok(Some(_)) => return true,
Ok(None) => {}
Err(_) => {}
}
}
false
}
async fn resolve_owner_falukant_id(client: Arc<PgClient>, room_owner_id: i32) -> Option<i32> {
if room_owner_id <= 0 {
return None;
}
if let Ok(Some(_)) = client
.query_opt("SELECT 1 FROM community.\"user\" WHERE id = $1 LIMIT 1", &[&room_owner_id])
.await
{
return Some(room_owner_id);
}
let row = client
.query_opt(
"SELECT falukant_user_id FROM chat.\"user\" WHERE id = $1 LIMIT 1",
&[&room_owner_id],
)
.await
.ok()??;
row.get::<_, Option<i32>>("falukant_user_id")
}