Fix #962 - kick old client and add new client on duplicate login

This commit is contained in:
heydabop 2021-03-18 01:28:35 -05:00
parent f48c1e6535
commit 0e3f7625a9
5 changed files with 57 additions and 6 deletions

View File

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

View File

@ -538,7 +538,7 @@ impl Client {
self.send_msg_err(ClientRegister { token_or_username })?;
match self.register_stream.recv::<ServerRegisterAnswer>().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),

View File

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

View File

@ -31,6 +31,15 @@ pub struct PendingLogin {
pending_r: oneshot::Receiver<Result<(String, Uuid), RegisterError>>,
}
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<Self>;
}
@ -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);

View File

@ -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<ServerEvent>>,
);
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.