Browse Source

prevent double logins

pbs
jake 4 years ago
parent
commit
c802bceb3c
  1. 1
      src/bin/main.rs
  2. 29
      src/entity/account.rs
  3. 5
      src/entity/gateway/inmemory.rs
  4. 5
      src/entity/gateway/postgres/migrations/V0001__initial.sql
  5. 8
      src/entity/gateway/postgres/models.rs
  6. 26
      src/login/character.rs
  7. 52
      src/login/login.rs
  8. 9
      src/ship/packet/handler/auth.rs
  9. 5
      src/ship/ship.rs
  10. 6
      tests/common.rs

1
src/bin/main.rs

@ -58,7 +58,6 @@ fn main() {
team_id: None, team_id: None,
banned_until: None, banned_until: None,
muted_until: None, muted_until: None,
created_at: chrono::Utc::now(),
flags: 0, flags: 0,
activated: true, activated: true,
}; };

29
src/entity/account.rs

@ -21,9 +21,24 @@ pub struct NewUserAccountEntity {
pub team_id: Option<u32>, pub team_id: Option<u32>,
pub banned_until: Option<chrono::DateTime<chrono::Utc>>, pub banned_until: Option<chrono::DateTime<chrono::Utc>>,
pub muted_until: Option<chrono::DateTime<chrono::Utc>>, pub muted_until: Option<chrono::DateTime<chrono::Utc>>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub flags: u32, 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)] #[derive(Clone, Debug)]
@ -38,8 +53,18 @@ pub struct UserAccountEntity {
pub created_at: chrono::DateTime<chrono::Utc>, pub created_at: chrono::DateTime<chrono::Utc>,
pub flags: u32, // TODO: is this used for anything other than character creation? pub flags: u32, // TODO: is this used for anything other than character creation?
pub activated: bool, 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)] #[derive(Clone, Debug)]
pub struct NewUserSettingsEntity { pub struct NewUserSettingsEntity {
pub user_id: UserAccountId, pub user_id: UserAccountId,

5
src/entity/gateway/inmemory.rs

@ -45,9 +45,12 @@ impl EntityGateway for InMemoryGateway {
team_id: user.team_id, team_id: user.team_id,
banned_until: user.banned_until, banned_until: user.banned_until,
muted_until: user.muted_until, muted_until: user.muted_until,
created_at: user.created_at,
created_at: chrono::Utc::now(),
flags: user.flags, flags: user.flags,
activated: user.activated, activated: user.activated,
at_login: false,
at_character: false,
at_ship: false,
}; };
users.insert(user.id, user.clone()); users.insert(user.id, user.clone());
Ok(user) Ok(user)

5
src/entity/gateway/postgres/migrations/V0001__initial.sql

@ -9,7 +9,10 @@ create table user_accounts (
flags integer default 0 not null, flags integer default 0 not null,
activated boolean default false not null, activated boolean default false not null,
game_session integer, 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 ( create table user_settings (

8
src/entity/gateway/postgres/models.rs

@ -18,6 +18,9 @@ pub struct PgUserAccount {
created_at: chrono::DateTime<chrono::Utc>, created_at: chrono::DateTime<chrono::Utc>,
flags: i32, flags: i32,
activated: bool, activated: bool,
at_login: bool,
at_character: bool,
at_ship: bool,
} }
impl Into<UserAccountEntity> for PgUserAccount { impl Into<UserAccountEntity> for PgUserAccount {
@ -32,7 +35,10 @@ impl Into<UserAccountEntity> for PgUserAccount {
flags: self.flags as u32, flags: self.flags as u32,
guildcard: self.id as u32 + 1, guildcard: self.id as u32 + 1,
team_id: None, team_id: None,
activated: self.activated
activated: self.activated,
at_login: self.at_login,
at_character: self.at_character,
at_ship: self.at_ship,
} }
} }
} }

26
src/login/character.rs

@ -27,7 +27,7 @@ use crate::entity::item::tool::Tool;
use crate::entity::item::mag::Mag; use crate::entity::item::mag::Mag;
use crate::entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel}; 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; pub const CHARACTER_PORT: u16 = 12001;
const SHIP_MENU_ID: u32 = 1; const SHIP_MENU_ID: u32 = 1;
@ -39,6 +39,7 @@ pub enum CharacterError {
CouldNotLoadSettings, CouldNotLoadSettings,
CouldNotLoadCharacters, CouldNotLoadCharacters,
CouldNotLoadGuildcard, CouldNotLoadGuildcard,
DbError,
} }
#[derive(Debug)] #[derive(Debug)]
@ -280,8 +281,11 @@ impl<EG: EntityGateway> CharacterServerState<EG> {
async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result<Vec<SendCharacterPacket>, CharacterError> { async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result<Vec<SendCharacterPacket>, CharacterError> {
let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?; 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()); let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new());
response.guildcard = user.guildcard; response.guildcard = user.guildcard;
response.team_id = user.team_id.map_or(0, |ti| ti) as u32; response.team_id = user.team_id.map_or(0, |ti| ti) as u32;
@ -471,7 +475,7 @@ impl<EG: EntityGateway> ServerState for CharacterServerState<EG> {
type RecvPacket = RecvCharacterPacket; type RecvPacket = RecvCharacterPacket;
type PacketError = CharacterError; type PacketError = CharacterError;
fn on_connect(&mut self, id: ClientId) -> Vec<OnConnect<Self::SendPacket>> {
async fn on_connect(&mut self, id: ClientId) -> Vec<OnConnect<Self::SendPacket>> {
self.clients.insert(id, ClientState::new()); self.clients.insert(id, ClientState::new());
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -531,7 +535,13 @@ impl<EG: EntityGateway> ServerState for CharacterServerState<EG> {
}) })
} }
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() Vec::new()
} }
} }
@ -684,6 +694,9 @@ mod test {
team_id: None, team_id: None,
flags: 0, flags: 0,
activated: true, activated: true,
at_login: false,
at_character: false,
at_ship: false,
}); });
server.clients.insert(ClientId(5), clientstate); server.clients.insert(ClientId(5), clientstate);
@ -728,6 +741,9 @@ mod test {
created_at: chrono::Utc::now(), created_at: chrono::Utc::now(),
flags: 0, flags: 0,
activated: true, activated: true,
at_login: false,
at_character: false,
at_ship: false,
}); });
let mut server = CharacterServerState::new(test_data.clone()); let mut server = CharacterServerState::new(test_data.clone());

52
src/login/login.rs

@ -1,5 +1,6 @@
// TODO: rename this module to auth // TODO: rename this module to auth
use std::collections::HashMap;
use std::net; use std::net;
use rand::Rng; use rand::Rng;
@ -21,6 +22,7 @@ pub const COMMUNICATION_PORT: u16 = 12123;
#[derive(Debug)] #[derive(Debug)]
pub enum LoginError { 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 username = array_to_utf8(pkt.username).map_err(|_err| AccountStatus::Error)?;
let password = array_to_utf8(pkt.password).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)?; 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 { if !user.activated {
return Err(AccountStatus::PayUp) 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<UserAccountEntity, AccountStatus> {
if user.is_currently_online() {
Err(AccountStatus::PayUp)
}
else {
Ok(user)
}
}
pub struct LoginServerState<EG: EntityGateway> { pub struct LoginServerState<EG: EntityGateway> {
character_server_ip: net::Ipv4Addr, character_server_ip: net::Ipv4Addr,
entity_gateway: EG, entity_gateway: EG,
clients: HashMap<ClientId, String>,
} }
impl<EG: EntityGateway> LoginServerState<EG> { impl<EG: EntityGateway> LoginServerState<EG> {
@ -88,20 +103,24 @@ impl<EG: EntityGateway> LoginServerState<EG> {
LoginServerState { LoginServerState {
entity_gateway: entity_gateway, entity_gateway: entity_gateway,
character_server_ip: character_server_ip.into(), character_server_ip: character_server_ip.into(),
clients: HashMap::new(),
} }
} }
async fn validate_login(&mut self, pkt: &Login) -> Vec<SendLoginPacket> {
match get_login_status(&self.entity_gateway, pkt).await {
Ok(_user) => {
async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result<Vec<SendLoginPacket>, 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 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()); 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) => { 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<EG: EntityGateway> ServerState for LoginServerState<EG> {
-> Result<Box<dyn Iterator<Item = (ClientId, Self::SendPacket)> + Send>, LoginError> { -> Result<Box<dyn Iterator<Item = (ClientId, Self::SendPacket)> + Send>, LoginError> {
Ok(match pkt { Ok(match pkt {
RecvLoginPacket::Login(login) => { RecvLoginPacket::Login(login) => {
Box::new(self.validate_login(login).await
Box::new(self.validate_login(id, login).await?
.into_iter() .into_iter()
.map(move |pkt| { .map(move |pkt| {
(id, pkt) (id, pkt)
@ -140,7 +159,13 @@ impl<EG: EntityGateway> ServerState for LoginServerState<EG> {
}) })
} }
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() Vec::new()
} }
} }
@ -197,6 +222,9 @@ mod test {
created_at: chrono::Utc::now(), created_at: chrono::Utc::now(),
flags: 0, flags: 0,
activated: true, activated: true,
at_login: false,
at_character: false,
at_ship: false,
}) })
} }
}; };
@ -282,6 +310,9 @@ mod test {
created_at: chrono::Utc::now(), created_at: chrono::Utc::now(),
flags: 0, flags: 0,
activated: true, activated: true,
at_login: false,
at_character: false,
at_ship: false,
}) })
} }
} }
@ -328,6 +359,9 @@ mod test {
created_at: chrono::Utc::now(), created_at: chrono::Utc::now(),
flags: 0, flags: 0,
activated: true, activated: true,
at_login: false,
at_character: false,
at_ship: false,
}) })
} }
} }

9
src/ship/packet/handler/auth.rs

@ -2,7 +2,7 @@ use libpso::packet::login::{Login, LoginResponse, AccountStatus, Session};
use libpso::packet::ship::*; use libpso::packet::ship::*;
use crate::common::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients}; 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::entity::gateway::EntityGateway;
use crate::ship::items::ItemManager; use crate::ship::items::ItemManager;
@ -13,8 +13,11 @@ pub async fn validate_login<EG: EntityGateway>(id: ClientId,
item_manager: &mut ItemManager, item_manager: &mut ItemManager,
ship_name: &String) ship_name: &String)
-> Result<Vec<SendShipPacket>, ShipError> { -> Result<Vec<SendShipPacket>, 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()); let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new());
response.guildcard = user.id.0 as u32; response.guildcard = user.id.0 as u32;
response.team_id = user.team_id.map_or(31, |ti| ti) as u32; response.team_id = user.team_id.map_or(31, |ti| ti) as u32;

5
src/ship/ship.rs

@ -600,6 +600,11 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
self.client_location.remove_client_from_area(id); self.client_location.remove_client_from_area(id);
self.item_manager.remove_character_from_room(&client.character); 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| { neighbors.into_iter().map(|n| {
(n.client, pkt.clone()) (n.client, pkt.clone())
}).collect() }).collect()

6
tests/common.rs

@ -18,12 +18,8 @@ pub async fn new_user_character<EG: EntityGateway>(entity_gateway: &mut EG, user
username: username.into(), username: username.into(),
password: bcrypt::hash(password, 5).unwrap(), password: bcrypt::hash(password, 5).unwrap(),
guildcard: 1, guildcard: 1,
team_id: None,
banned_until: None,
muted_until: None,
created_at: chrono::Utc::now(),
flags: 0,
activated: true, activated: true,
..NewUserAccountEntity::default()
}; };
let user = entity_gateway.create_user(new_user).await.unwrap(); let user = entity_gateway.create_user(new_user).await.unwrap();

Loading…
Cancel
Save