From c802bceb3c5a2548089b7ba0df2e534edb142731 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 27 Oct 2020 22:31:54 -0600 Subject: [PATCH] prevent double logins --- src/bin/main.rs | 1 - src/entity/account.rs | 29 ++++++++++- src/entity/gateway/inmemory.rs | 5 +- .../postgres/migrations/V0001__initial.sql | 5 +- src/entity/gateway/postgres/models.rs | 8 ++- src/login/character.rs | 26 ++++++++-- src/login/login.rs | 52 +++++++++++++++---- src/ship/packet/handler/auth.rs | 9 ++-- src/ship/ship.rs | 5 ++ tests/common.rs | 6 +-- 10 files changed, 118 insertions(+), 28 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 16af628..fcd99af 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -58,7 +58,6 @@ fn main() { team_id: None, banned_until: None, muted_until: None, - created_at: chrono::Utc::now(), flags: 0, activated: true, }; diff --git a/src/entity/account.rs b/src/entity/account.rs index 1276cf8..9727bc7 100644 --- a/src/entity/account.rs +++ b/src/entity/account.rs @@ -21,9 +21,24 @@ pub struct NewUserAccountEntity { pub team_id: Option, pub banned_until: Option>, pub muted_until: Option>, - pub created_at: chrono::DateTime, pub flags: u32, - pub activated: bool, + pub activated: bool +} + +impl Default for NewUserAccountEntity { + fn default() -> NewUserAccountEntity { + NewUserAccountEntity { + email: "".into(), + username: "".into(), + password: "".into(), + guildcard: 0xFFFFFFFF, + team_id: None, + banned_until: None, + muted_until: None, + flags: 0, + activated: false, + } + } } #[derive(Clone, Debug)] @@ -38,8 +53,18 @@ pub struct UserAccountEntity { pub created_at: chrono::DateTime, pub flags: u32, // TODO: is this used for anything other than character creation? pub activated: bool, + pub at_login: bool, + pub at_character: bool, + pub at_ship: bool, } +impl UserAccountEntity { + pub fn is_currently_online(&self) -> bool { + self.at_login | self.at_character | self.at_ship + } +} + + #[derive(Clone, Debug)] pub struct NewUserSettingsEntity { pub user_id: UserAccountId, diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 6341638..89a47e2 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -45,9 +45,12 @@ impl EntityGateway for InMemoryGateway { team_id: user.team_id, banned_until: user.banned_until, muted_until: user.muted_until, - created_at: user.created_at, + created_at: chrono::Utc::now(), flags: user.flags, activated: user.activated, + at_login: false, + at_character: false, + at_ship: false, }; users.insert(user.id, user.clone()); Ok(user) diff --git a/src/entity/gateway/postgres/migrations/V0001__initial.sql b/src/entity/gateway/postgres/migrations/V0001__initial.sql index 1e5a925..b4cd507 100644 --- a/src/entity/gateway/postgres/migrations/V0001__initial.sql +++ b/src/entity/gateway/postgres/migrations/V0001__initial.sql @@ -9,7 +9,10 @@ create table user_accounts ( flags integer default 0 not null, activated boolean default false not null, game_session integer, - interserver_session integer + interserver_session integer, + at_login boolean default false not null, + at_character boolean default false not null, + at_ship boolean default false not null ); create table user_settings ( diff --git a/src/entity/gateway/postgres/models.rs b/src/entity/gateway/postgres/models.rs index e1bbde5..b870da5 100644 --- a/src/entity/gateway/postgres/models.rs +++ b/src/entity/gateway/postgres/models.rs @@ -18,6 +18,9 @@ pub struct PgUserAccount { created_at: chrono::DateTime, flags: i32, activated: bool, + at_login: bool, + at_character: bool, + at_ship: bool, } impl Into for PgUserAccount { @@ -32,7 +35,10 @@ impl Into for PgUserAccount { flags: self.flags as u32, guildcard: self.id as u32 + 1, team_id: None, - activated: self.activated + activated: self.activated, + at_login: self.at_login, + at_character: self.at_character, + at_ship: self.at_ship, } } } diff --git a/src/login/character.rs b/src/login/character.rs index 2e98ad9..a855912 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -27,7 +27,7 @@ use crate::entity::item::tool::Tool; use crate::entity::item::mag::Mag; use crate::entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel}; -use crate::login::login::get_login_status; +use crate::login::login::{get_login_status, check_if_already_online}; pub const CHARACTER_PORT: u16 = 12001; const SHIP_MENU_ID: u32 = 1; @@ -39,6 +39,7 @@ pub enum CharacterError { CouldNotLoadSettings, CouldNotLoadCharacters, CouldNotLoadGuildcard, + DbError, } #[derive(Debug)] @@ -280,8 +281,11 @@ impl CharacterServerState { async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result, CharacterError> { let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?; - Ok(match get_login_status(&self.entity_gateway, pkt).await { - Ok(user) => { + Ok(match get_login_status(&self.entity_gateway, pkt).await.and_then(check_if_already_online) { + Ok(mut user) => { + user.at_character = true; + self.entity_gateway.save_user(&user).await.map_err(|_| CharacterError::DbError)?; + let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new()); response.guildcard = user.guildcard; response.team_id = user.team_id.map_or(0, |ti| ti) as u32; @@ -471,7 +475,7 @@ impl ServerState for CharacterServerState { type RecvPacket = RecvCharacterPacket; type PacketError = CharacterError; - fn on_connect(&mut self, id: ClientId) -> Vec> { + async fn on_connect(&mut self, id: ClientId) -> Vec> { self.clients.insert(id, ClientState::new()); let mut rng = rand::thread_rng(); @@ -531,7 +535,13 @@ impl ServerState for CharacterServerState { }) } - fn on_disconnect(&mut self, _id: ClientId) -> Vec<(ClientId, SendCharacterPacket)> { + async fn on_disconnect(&mut self, id: ClientId) -> Vec<(ClientId, SendCharacterPacket)> { + if let Some(client) = self.clients.remove(&id) { + if let Some(mut user) = client.user { + user.at_character= false; + self.entity_gateway.save_user(&user).await; + } + } Vec::new() } } @@ -684,6 +694,9 @@ mod test { team_id: None, flags: 0, activated: true, + at_login: false, + at_character: false, + at_ship: false, }); server.clients.insert(ClientId(5), clientstate); @@ -728,6 +741,9 @@ mod test { created_at: chrono::Utc::now(), flags: 0, activated: true, + at_login: false, + at_character: false, + at_ship: false, }); let mut server = CharacterServerState::new(test_data.clone()); diff --git a/src/login/login.rs b/src/login/login.rs index e2c8bc2..9bcb0c7 100644 --- a/src/login/login.rs +++ b/src/login/login.rs @@ -1,5 +1,6 @@ // TODO: rename this module to auth +use std::collections::HashMap; use std::net; use rand::Rng; @@ -21,6 +22,7 @@ pub const COMMUNICATION_PORT: u16 = 12123; #[derive(Debug)] pub enum LoginError { + DbError } @@ -61,6 +63,10 @@ pub async fn get_login_status(entity_gateway: &impl EntityGateway, pkt: &Login) let username = array_to_utf8(pkt.username).map_err(|_err| AccountStatus::Error)?; let password = array_to_utf8(pkt.password).map_err(|_err| AccountStatus::Error)?; let user = entity_gateway.get_user_by_name(username).await.map_err(|_| AccountStatus::InvalidUser)?; + if user.is_currently_online() { + return Err(AccountStatus::AlreadyOnline) + } + if !user.activated { return Err(AccountStatus::PayUp) } @@ -77,10 +83,19 @@ pub async fn get_login_status(entity_gateway: &impl EntityGateway, pkt: &Login) } } +pub fn check_if_already_online(user: UserAccountEntity) -> Result { + if user.is_currently_online() { + Err(AccountStatus::PayUp) + } + else { + Ok(user) + } +} pub struct LoginServerState { character_server_ip: net::Ipv4Addr, entity_gateway: EG, + clients: HashMap, } impl LoginServerState { @@ -88,20 +103,24 @@ impl LoginServerState { LoginServerState { entity_gateway: entity_gateway, character_server_ip: character_server_ip.into(), + clients: HashMap::new(), } } - async fn validate_login(&mut self, pkt: &Login) -> Vec { - match get_login_status(&self.entity_gateway, pkt).await { - Ok(_user) => { + async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result, LoginError> { + match get_login_status(&self.entity_gateway, pkt).await.and_then(check_if_already_online) { + Ok(mut user) => { + user.at_login = true; + self.entity_gateway.save_user(&user).await.map_err(|_| LoginError::DbError)?; + self.clients.insert(id, user.username.clone()); + let response = SendLoginPacket::LoginResponse(LoginResponse::by_status(AccountStatus::Ok, pkt.session)); - //let ip = net::Ipv4Addr::new(127,0,0,1); let ip = u32::from_ne_bytes(self.character_server_ip.octets()); - vec![response, - SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::login::character::CHARACTER_PORT))] + Ok(vec![response, + SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::login::character::CHARACTER_PORT))]) }, Err(err) => { - vec![SendLoginPacket::LoginResponse(LoginResponse::by_status(err, pkt.session))] + Ok(vec![SendLoginPacket::LoginResponse(LoginResponse::by_status(err, pkt.session))]) } } } @@ -131,7 +150,7 @@ impl ServerState for LoginServerState { -> Result + Send>, LoginError> { Ok(match pkt { RecvLoginPacket::Login(login) => { - Box::new(self.validate_login(login).await + Box::new(self.validate_login(id, login).await? .into_iter() .map(move |pkt| { (id, pkt) @@ -140,7 +159,13 @@ impl ServerState for LoginServerState { }) } - async fn on_disconnect(&mut self, _id: ClientId) -> Vec<(ClientId, SendLoginPacket)> { + async fn on_disconnect(&mut self, id: ClientId) -> Vec<(ClientId, SendLoginPacket)> { + if let Some(username) = self.clients.remove(&id) { + if let Ok(mut user) = self.entity_gateway.get_user_by_name(username).await { + user.at_login = false; + self.entity_gateway.save_user(&user).await; + } + } Vec::new() } } @@ -197,6 +222,9 @@ mod test { created_at: chrono::Utc::now(), flags: 0, activated: true, + at_login: false, + at_character: false, + at_ship: false, }) } }; @@ -282,6 +310,9 @@ mod test { created_at: chrono::Utc::now(), flags: 0, activated: true, + at_login: false, + at_character: false, + at_ship: false, }) } } @@ -328,6 +359,9 @@ mod test { created_at: chrono::Utc::now(), flags: 0, activated: true, + at_login: false, + at_character: false, + at_ship: false, }) } } diff --git a/src/ship/packet/handler/auth.rs b/src/ship/packet/handler/auth.rs index 9617998..c389c6c 100644 --- a/src/ship/packet/handler/auth.rs +++ b/src/ship/packet/handler/auth.rs @@ -2,7 +2,7 @@ use libpso::packet::login::{Login, LoginResponse, AccountStatus, Session}; use libpso::packet::ship::*; use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients}; -use crate::login::login::get_login_status; +use crate::login::login::{get_login_status, check_if_already_online}; use crate::entity::gateway::EntityGateway; use crate::ship::items::ItemManager; @@ -13,8 +13,11 @@ pub async fn validate_login(id: ClientId, item_manager: &mut ItemManager, ship_name: &String) -> Result, ShipError> { - Ok(match get_login_status(entity_gateway, pkt).await { - Ok(user) => { + Ok(match get_login_status(entity_gateway, pkt).await.and_then(check_if_already_online) { + Ok(mut user) => { + user.at_ship= true; + entity_gateway.save_user(&user).await?; + let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new()); response.guildcard = user.id.0 as u32; response.team_id = user.team_id.map_or(31, |ti| ti) as u32; diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 10cecac..1c5830b 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -600,6 +600,11 @@ impl ServerState for ShipServerState { self.client_location.remove_client_from_area(id); self.item_manager.remove_character_from_room(&client.character); + if let Some(mut client) = self.clients.remove(&id) { + client.user.at_ship = false; + self.entity_gateway.save_user(&client.user).await; + } + neighbors.into_iter().map(|n| { (n.client, pkt.clone()) }).collect() diff --git a/tests/common.rs b/tests/common.rs index 0b66360..7e7c925 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -18,12 +18,8 @@ pub async fn new_user_character(entity_gateway: &mut EG, user username: username.into(), password: bcrypt::hash(password, 5).unwrap(), guildcard: 1, - team_id: None, - banned_until: None, - muted_until: None, - created_at: chrono::Utc::now(), - flags: 0, activated: true, + ..NewUserAccountEntity::default() }; let user = entity_gateway.create_user(new_user).await.unwrap();