From b68ad516726c2815aeac35fd5c8a4702a8c2dc33 Mon Sep 17 00:00:00 2001 From: lokka30 <59464084+lokka30@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:43:02 +0800 Subject: [PATCH] Prevent duplicate profiles (#178) * Implement get_player_by_uuid for server & world Similar to the existing functions named get_player_by_name, these functions will search for a player with a matching Uuid. This implementation is asynchronous and thus better suits being combined with #176, which makes get_player_by_name asynchronous too. * make get_player_by_name async, rename old fn to get_player_by_name_blocking Currently, the existing commands system is all that requires the existing blocking version of get_player_by_name function. After discussing in the Discord, it seems renaming the old blocking method with a suffix of _blocking in addition to adding an async version under the original (unmodified) name of get_player_by_name. * Implement duplicate profile protections * fmt client_packet.rs * use hashmap search for players by uuid; move duplicate uuid check I've confirmed this commit compiles and runs, my client is able to play normally. However, I haven't confirmed it prevents duplicate usernames or UUIDs as of yet. * client_packet: check auth before duplications; check uuid offline too --- pumpkin/src/client/client_packet.rs | 18 +++++++++++++++++- pumpkin/src/commands/arg_player.rs | 4 ++-- pumpkin/src/server/mod.rs | 16 +++++++++++++--- pumpkin/src/world/mod.rs | 21 ++++++++++++++++++--- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index cba93abc9..92199817f 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -151,6 +151,7 @@ impl Client { }; if BASIC_CONFIG.online_mode { + // Online mode auth match self .authenticate(server, &shared_secret, &profile.name) .await @@ -163,6 +164,21 @@ impl Client { } } + // Don't allow duplicate UUIDs + if let Some(online_player) = &server.get_player_by_uuid(profile.id).await { + log::debug!("Player (IP '{}', username '{}') tried to log in with the same UUID ('{}') as an online player (IP '{}', username '{}')", &self.address.lock().await.to_string(), &profile.name, &profile.id.to_string(), &online_player.client.address.lock().await.to_string(), &online_player.gameprofile.name); + self.kick("You are already connected to this server").await; + return; + } + + // Don't allow a duplicate username + if let Some(online_player) = &server.get_player_by_name(&profile.name).await { + log::debug!("A player (IP '{}', attempted username '{}') tried to log in with the same username as an online player (UUID '{}', IP '{}', username '{}')", &self.address.lock().await.to_string(), &profile.name, &profile.id.to_string(), &online_player.client.address.lock().await.to_string(), &online_player.gameprofile.name); + self.kick("A player with this username is already connected") + .await; + return; + } + if ADVANCED_CONFIG.packet_compression.enabled { self.enable_compression().await; } @@ -190,8 +206,8 @@ impl Client { if let Some(auth_client) = &server.auth_client { let hash = server.digest_secret(shared_secret); let ip = self.address.lock().await.ip(); - let profile = authentication::authenticate(username, &hash, &ip, auth_client).await?; + // Check if player should join if let Some(actions) = &profile.profile_actions { if ADVANCED_CONFIG diff --git a/pumpkin/src/commands/arg_player.rs b/pumpkin/src/commands/arg_player.rs index 15d26ace9..ef59c4dae 100644 --- a/pumpkin/src/commands/arg_player.rs +++ b/pumpkin/src/commands/arg_player.rs @@ -26,7 +26,7 @@ pub fn consume_arg_player( name => { // todo: implement any other player than sender for world in &server.worlds { - if world.get_player_by_name(name).is_some() { + if world.get_player_by_name_blocking(name).is_some() { return Ok(name.into()); } } @@ -56,7 +56,7 @@ pub fn parse_arg_player( "@a" | "@e" => todo!(), // todo: implement all players target selector name => { for world in &server.worlds { - if let Some(player) = world.get_player_by_name(name) { + if let Some(player) = world.get_player_by_name_blocking(name) { return Ok(player); } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 14f4b8924..4e5feb505 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -143,10 +143,20 @@ impl Server { } } - /// Searches every world for a player by name - pub fn get_player_by_name(&self, name: &str) -> Option> { + /// Searches every world for a player by username + pub async fn get_player_by_name(&self, name: &str) -> Option> { for world in &self.worlds { - if let Some(player) = world.get_player_by_name(name) { + if let Some(player) = world.get_player_by_name(name).await { + return Some(player); + } + } + None + } + + /// Searches every world for a player by UUID + pub async fn get_player_by_uuid(&self, id: uuid::Uuid) -> Option> { + for world in &self.worlds { + if let Some(player) = world.get_player_by_uuid(id).await { return Some(player); } } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 4cccac5d5..a20a009aa 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -361,9 +361,19 @@ impl World { None } - /// Gets a Player by name - pub fn get_player_by_name(&self, name: &str) -> Option> { - // not sure of blocking lock + /// Gets a Player by username + pub async fn get_player_by_name(&self, name: &str) -> Option> { + for player in self.current_players.lock().await.values() { + if player.gameprofile.name == name { + return Some(player.clone()); + } + } + None + } + + /// Gets a Player by username (Blocking - Legacy use only) + pub fn get_player_by_name_blocking(&self, name: &str) -> Option> { + // TODO: Remove this blocking functions when commands are async. for player in self.current_players.blocking_lock().values() { if player.gameprofile.name == name { return Some(player.clone()); @@ -372,6 +382,11 @@ impl World { None } + /// Gets a Player by UUID + pub async fn get_player_by_uuid(&self, id: uuid::Uuid) -> Option> { + return self.current_players.lock().await.get(&id).cloned(); + } + pub async fn add_player(&self, uuid: uuid::Uuid, player: Arc) { self.current_players.lock().await.insert(uuid, player); }