Skip to content

Commit

Permalink
Prevent duplicate profiles (#178)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
lokka30 authored Oct 25, 2024
1 parent 761ca11 commit b68ad51
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 9 deletions.
18 changes: 17 additions & 1 deletion pumpkin/src/client/client_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ impl Client {
};

if BASIC_CONFIG.online_mode {
// Online mode auth
match self
.authenticate(server, &shared_secret, &profile.name)
.await
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pumpkin/src/commands/arg_player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Expand Down Expand Up @@ -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);
}
}
Expand Down
16 changes: 13 additions & 3 deletions pumpkin/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,20 @@ impl Server {
}
}

/// Searches every world for a player by name
pub fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
/// Searches every world for a player by username
pub async fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
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<Arc<Player>> {
for world in &self.worlds {
if let Some(player) = world.get_player_by_uuid(id).await {
return Some(player);
}
}
Expand Down
21 changes: 18 additions & 3 deletions pumpkin/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,19 @@ impl World {
None
}

/// Gets a Player by name
pub fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
// not sure of blocking lock
/// Gets a Player by username
pub async fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
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<Arc<Player>> {
// 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());
Expand All @@ -372,6 +382,11 @@ impl World {
None
}

/// Gets a Player by UUID
pub async fn get_player_by_uuid(&self, id: uuid::Uuid) -> Option<Arc<Player>> {
return self.current_players.lock().await.get(&id).cloned();
}

pub async fn add_player(&self, uuid: uuid::Uuid, player: Arc<Player>) {
self.current_players.lock().await.insert(uuid, player);
}
Expand Down

0 comments on commit b68ad51

Please sign in to comment.