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
This commit is contained in:
lokka30
2024-10-25 23:43:02 +08:00
committed by GitHub
parent 761ca11295
commit b68ad51672
4 changed files with 50 additions and 9 deletions

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
@@ -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);
}