From 0e3f7625a978e6b964534129b3677ef60bcc3707 Mon Sep 17 00:00:00 2001 From: heydabop Date: Thu, 18 Mar 2021 01:28:35 -0500 Subject: [PATCH] Fix #962 - kick old client and add new client on duplicate login --- CHANGELOG.md | 2 ++ client/src/lib.rs | 2 +- common/net/src/msg/server.rs | 3 ++- server/src/login_provider.rs | 11 ++++++++- server/src/sys/msg/register.rs | 45 +++++++++++++++++++++++++++++++--- 5 files changed, 57 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f110b0bc..418e9aea3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Server kicks old client when a user is trying to log in again (often the case when a user's original connection gets dropped) + ## [0.9.0] - 2021-03-20 ### Added diff --git a/client/src/lib.rs b/client/src/lib.rs index 9ea8f409e9..4066c36bc4 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -538,7 +538,7 @@ impl Client { self.send_msg_err(ClientRegister { token_or_username })?; match self.register_stream.recv::().await? { - Err(RegisterError::AlreadyLoggedIn) => Err(Error::AlreadyLoggedIn), + Err(RegisterError::AlreadyLoggedIn(_, _)) => Err(Error::AlreadyLoggedIn), Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)), Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter), Err(RegisterError::NotOnWhitelist) => Err(Error::NotOnWhitelist), diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 1cc6ff0fd6..b497bcd9c3 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -10,6 +10,7 @@ use common::{ terrain::{Block, TerrainChunk}, trade::{PendingTrade, TradeId, TradeResult}, uid::Uid, + uuid::Uuid, }; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; @@ -191,7 +192,7 @@ pub enum DisconnectReason { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum RegisterError { - AlreadyLoggedIn, + AlreadyLoggedIn(Uuid, String), AuthError(String), Banned(String), Kicked(String), diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs index 613b528895..34e8c3e863 100644 --- a/server/src/login_provider.rs +++ b/server/src/login_provider.rs @@ -31,6 +31,15 @@ pub struct PendingLogin { pending_r: oneshot::Receiver>, } +impl PendingLogin { + pub(crate) fn new_success(username: String, uuid: Uuid) -> Self { + let (pending_s, pending_r) = oneshot::channel(); + let _ = pending_s.send(Ok((username, uuid))); + + Self { pending_r } + } +} + impl Component for PendingLogin { type Storage = IdvStorage; } @@ -68,7 +77,7 @@ impl LoginProvider { fn login(&mut self, uuid: Uuid, username: String) -> Result<(), RegisterError> { // make sure that the user is not logged in already if self.accounts.contains_key(&uuid) { - return Err(RegisterError::AlreadyLoggedIn); + return Err(RegisterError::AlreadyLoggedIn(uuid, username)); } info!(?username, "New User"); self.accounts.insert(uuid, username); diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 89e4afe9b4..cbbbc990b7 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -6,12 +6,13 @@ use crate::{ }; use common::{ comp::{Admin, Player, Stats}, + event::{EventBus, ServerEvent}, uid::{Uid, UidAllocator}, }; use common_ecs::{Job, Origin, Phase, System}; use common_net::msg::{ - CharacterInfo, ClientRegister, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneral, - ServerRegisterAnswer, + CharacterInfo, ClientRegister, DisconnectReason, PlayerInfo, PlayerListUpdate, RegisterError, + ServerGeneral, ServerRegisterAnswer, }; use hashbrown::HashMap; use plugin_api::Health; @@ -48,6 +49,7 @@ impl<'a> System<'a> for Sys { WriteExpect<'a, LoginProvider>, WriteStorage<'a, Admin>, ReadExpect<'a, EditableSettings>, + Read<'a, EventBus>, ); const NAME: &'static str = "msg::register"; @@ -70,6 +72,7 @@ impl<'a> System<'a> for Sys { mut login_provider, mut admins, editable_settings, + server_event_bus, ): Self::SystemData, ) { // Player list to send new players. @@ -100,6 +103,7 @@ impl<'a> System<'a> for Sys { } let mut finished_pending = vec![]; + let mut retries = vec![]; for (entity, client, mut pending) in (&entities, &clients, &mut pending_logins).join() { if let Err(e) = || -> std::result::Result<(), crate::error::Error> { #[cfg(feature = "plugins")] @@ -127,7 +131,38 @@ impl<'a> System<'a> for Sys { trace!(?r, "pending login returned"); match r { Err(e) => { - client.send(ServerRegisterAnswer::Err(e))?; + let mut retry = false; + if let RegisterError::AlreadyLoggedIn(uuid, ref username) = e { + if let Some((old_entity, old_client, _)) = + (&entities, &clients, &players) + .join() + .find(|(_, _, old_player)| old_player.uuid() == uuid) + { + // Remove old client + server_event_bus + .emit_now(ServerEvent::ClientDisconnect(old_entity)); + let _ = old_client.send(ServerGeneral::Disconnect( + DisconnectReason::Kicked(String::from( + "You have logged in from another location.", + )), + )); + // We can't login the new client right now as the + // removal of the old client and player occurs later in + // the tick, so we instead setup the new login to be + // processed in the next tick + // Create "fake" successful pending auth and mark it to + // be inserted into pending_logins at the end of this + // run + retries.push(( + entity, + PendingLogin::new_success(username.to_string(), uuid), + )); + retry = true; + } + } + if !retry { + client.send(ServerRegisterAnswer::Err(e))?; + } return Ok(()); }, Ok((username, uuid)) => (username, uuid), @@ -174,6 +209,10 @@ impl<'a> System<'a> for Sys { for e in finished_pending { pending_logins.remove(e); } + // Insert retry attempts back into pending_logins to be processed next tick + for (entity, pending) in retries { + let _ = pending_logins.insert(entity, pending); + } // Handle new players. // Tell all clients to add them to the player list.