diff --git a/server/src/user.rs b/server/src/user.rs index 1015742a5..45137b991 100644 --- a/server/src/user.rs +++ b/server/src/user.rs @@ -11,15 +11,30 @@ pub struct User { #[unique] pub name: String, identities: Vec, - pass_hash: String, + pass_hash: Option, online: bool, last_login: Timestamp, } +#[spacetimedb(reducer)] +fn register_empty(ctx: ReducerContext) -> Result<(), String> { + User::clear_identity(&ctx.sender); + let user = User { + id: 0, + identities: vec![ctx.sender], + name: format!("user#{}", User::iter().count()), + pass_hash: None, + online: false, + last_login: Timestamp::UNIX_EPOCH, + }; + User::insert(user)?; + Ok(()) +} + #[spacetimedb(reducer)] fn register(ctx: ReducerContext, name: String, pass: String) -> Result<(), String> { let name = User::validate_name(name)?; - let pass_hash = User::hash_pass(pass)?; + let pass_hash = Some(User::hash_pass(pass)?); User::clear_identity(&ctx.sender); User::insert(User { id: 0, @@ -37,9 +52,17 @@ fn login(ctx: ReducerContext, name: String, pass: String) -> Result<(), String> let mut user = User::filter_by_name(&name) .context("User not found") .map_err(|e| e.to_string())?; + if user.pass_hash.is_none() { + return Err("No password set for user".to_owned()); + } if !user.check_pass(pass) { Err("Wrong password".to_owned()) } else { + if let Ok(mut user) = User::find_by_identity(&ctx.sender) { + user.online = false; + user.remove_identity(&ctx.sender); + User::update_by_id(&user.id.clone(), user); + } if !user.identities.contains(&ctx.sender) { User::clear_identity(&ctx.sender); user.identities.push(ctx.sender); @@ -50,16 +73,19 @@ fn login(ctx: ReducerContext, name: String, pass: String) -> Result<(), String> } #[spacetimedb(reducer)] -pub fn login_by_identity(ctx: ReducerContext, name: String) -> Result<(), String> { - let user = User::filter_by_name(&name) - .context("User not found") - .map_err(|e| e.to_string())?; - if !user.identities.contains(&ctx.sender) { - Err("Identity not connected to user name".to_string()) - } else { - user.login(); - Ok(()) - } +fn login_by_identity(ctx: ReducerContext) -> Result<(), String> { + let user = User::find_by_identity(&ctx.sender)?; + user.login(); + Ok(()) +} + +#[spacetimedb(reducer)] +fn logout(ctx: ReducerContext) -> Result<(), String> { + let mut user = User::find_by_identity(&ctx.sender)?; + user.online = false; + user.remove_identity(&ctx.sender); + User::update_by_id(&user.id.clone(), user); + Ok(()) } #[spacetimedb(reducer)] @@ -79,7 +105,7 @@ fn set_password(ctx: ReducerContext, old_pass: String, new_pass: String) -> Resu if !user.check_pass(old_pass) { return Err("Old password did not match".to_owned()); } - let pass_hash = User::hash_pass(new_pass)?; + let pass_hash = Some(User::hash_pass(new_pass)?); User::update_by_id(&user.id, User { pass_hash, ..user }); Ok(()) } else { @@ -107,7 +133,11 @@ impl User { } fn check_pass(&self, pass: String) -> bool { - bcrypt::verify(pass, &self.pass_hash) + if let Some(hash) = &self.pass_hash { + bcrypt::verify(pass, hash) + } else { + true + } } fn hash_pass(pass: String) -> Result { @@ -129,8 +159,12 @@ impl User { fn clear_identity(identity: &Identity) { if let Ok(mut user) = User::find_by_identity(identity) { - user.identities.retain(|i| !i.eq(identity)); + user.remove_identity(identity); User::update_by_id(&user.id.clone(), user); } } + + fn remove_identity(&mut self, identity: &Identity) { + self.identities.retain(|i| !i.eq(identity)); + } } diff --git a/src/module_bindings/login_by_identity_reducer.rs b/src/module_bindings/login_by_identity_reducer.rs index ede083f4a..c2e000660 100644 --- a/src/module_bindings/login_by_identity_reducer.rs +++ b/src/module_bindings/login_by_identity_reducer.rs @@ -13,36 +13,34 @@ use spacetimedb_sdk::{ }; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct LoginByIdentityArgs { - pub name: String, -} +pub struct LoginByIdentityArgs {} impl Reducer for LoginByIdentityArgs { const REDUCER_NAME: &'static str = "login_by_identity"; } #[allow(unused)] -pub fn login_by_identity(name: String) { - LoginByIdentityArgs { name }.invoke(); +pub fn login_by_identity() { + LoginByIdentityArgs {}.invoke(); } #[allow(unused)] pub fn on_login_by_identity( - mut __callback: impl FnMut(&Identity, Option
, &Status, &String) + Send + 'static, + mut __callback: impl FnMut(&Identity, Option
, &Status) + Send + 'static, ) -> ReducerCallbackId { LoginByIdentityArgs::on_reducer(move |__identity, __addr, __status, __args| { - let LoginByIdentityArgs { name } = __args; - __callback(__identity, __addr, __status, name); + let LoginByIdentityArgs {} = __args; + __callback(__identity, __addr, __status); }) } #[allow(unused)] pub fn once_on_login_by_identity( - __callback: impl FnOnce(&Identity, Option
, &Status, &String) + Send + 'static, + __callback: impl FnOnce(&Identity, Option
, &Status) + Send + 'static, ) -> ReducerCallbackId { LoginByIdentityArgs::once_on_reducer(move |__identity, __addr, __status, __args| { - let LoginByIdentityArgs { name } = __args; - __callback(__identity, __addr, __status, name); + let LoginByIdentityArgs {} = __args; + __callback(__identity, __addr, __status); }) } diff --git a/src/module_bindings/logout_reducer.rs b/src/module_bindings/logout_reducer.rs new file mode 100644 index 000000000..e82305ac1 --- /dev/null +++ b/src/module_bindings/logout_reducer.rs @@ -0,0 +1,50 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#[allow(unused)] +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct LogoutArgs {} + +impl Reducer for LogoutArgs { + const REDUCER_NAME: &'static str = "logout"; +} + +#[allow(unused)] +pub fn logout() { + LogoutArgs {}.invoke(); +} + +#[allow(unused)] +pub fn on_logout( + mut __callback: impl FnMut(&Identity, Option
, &Status) + Send + 'static, +) -> ReducerCallbackId { + LogoutArgs::on_reducer(move |__identity, __addr, __status, __args| { + let LogoutArgs {} = __args; + __callback(__identity, __addr, __status); + }) +} + +#[allow(unused)] +pub fn once_on_logout( + __callback: impl FnOnce(&Identity, Option
, &Status) + Send + 'static, +) -> ReducerCallbackId { + LogoutArgs::once_on_reducer(move |__identity, __addr, __status, __args| { + let LogoutArgs {} = __args; + __callback(__identity, __addr, __status); + }) +} + +#[allow(unused)] +pub fn remove_on_logout(id: ReducerCallbackId) { + LogoutArgs::remove_on_reducer(id); +} diff --git a/src/module_bindings/mod.rs b/src/module_bindings/mod.rs index 04f3700c6..0a14f479b 100644 --- a/src/module_bindings/mod.rs +++ b/src/module_bindings/mod.rs @@ -30,6 +30,8 @@ pub mod global_tower; pub mod house; pub mod login_by_identity_reducer; pub mod login_reducer; +pub mod logout_reducer; +pub mod register_empty_reducer; pub mod register_reducer; pub mod set_name_reducer; pub mod set_password_reducer; @@ -59,6 +61,8 @@ pub use global_tower::*; pub use house::*; pub use login_by_identity_reducer::*; pub use login_reducer::*; +pub use logout_reducer::*; +pub use register_empty_reducer::*; pub use register_reducer::*; pub use set_name_reducer::*; pub use set_password_reducer::*; @@ -85,7 +89,9 @@ pub enum ReducerEvent { GiveRight(give_right_reducer::GiveRightArgs), Login(login_reducer::LoginArgs), LoginByIdentity(login_by_identity_reducer::LoginByIdentityArgs), + Logout(logout_reducer::LogoutArgs), Register(register_reducer::RegisterArgs), + RegisterEmpty(register_empty_reducer::RegisterEmptyArgs), SetName(set_name_reducer::SetNameArgs), SetPassword(set_password_reducer::SetPasswordArgs), StartRun(start_run_reducer::StartRunArgs), @@ -186,7 +192,9 @@ match &function_call.reducer[..] { "give_right" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::GiveRight), "login" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::Login), "login_by_identity" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::LoginByIdentity), + "logout" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::Logout), "register" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::Register), + "register_empty" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::RegisterEmpty), "set_name" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::SetName), "set_password" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::SetPassword), "start_run" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::StartRun), diff --git a/src/module_bindings/register_empty_reducer.rs b/src/module_bindings/register_empty_reducer.rs new file mode 100644 index 000000000..f968ab0d2 --- /dev/null +++ b/src/module_bindings/register_empty_reducer.rs @@ -0,0 +1,50 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#[allow(unused)] +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct RegisterEmptyArgs {} + +impl Reducer for RegisterEmptyArgs { + const REDUCER_NAME: &'static str = "register_empty"; +} + +#[allow(unused)] +pub fn register_empty() { + RegisterEmptyArgs {}.invoke(); +} + +#[allow(unused)] +pub fn on_register_empty( + mut __callback: impl FnMut(&Identity, Option
, &Status) + Send + 'static, +) -> ReducerCallbackId { + RegisterEmptyArgs::on_reducer(move |__identity, __addr, __status, __args| { + let RegisterEmptyArgs {} = __args; + __callback(__identity, __addr, __status); + }) +} + +#[allow(unused)] +pub fn once_on_register_empty( + __callback: impl FnOnce(&Identity, Option
, &Status) + Send + 'static, +) -> ReducerCallbackId { + RegisterEmptyArgs::once_on_reducer(move |__identity, __addr, __status, __args| { + let RegisterEmptyArgs {} = __args; + __callback(__identity, __addr, __status); + }) +} + +#[allow(unused)] +pub fn remove_on_register_empty(id: ReducerCallbackId) { + RegisterEmptyArgs::remove_on_reducer(id); +} diff --git a/src/module_bindings/user.rs b/src/module_bindings/user.rs index 0b5780c83..7f5bf12d4 100644 --- a/src/module_bindings/user.rs +++ b/src/module_bindings/user.rs @@ -17,7 +17,7 @@ pub struct User { pub id: u64, pub name: String, pub identities: Vec, - pub pass_hash: String, + pub pass_hash: Option, pub online: bool, pub last_login: u64, } @@ -48,7 +48,7 @@ impl User { Self::filter(|row| row.identities == identities) } #[allow(unused)] - pub fn filter_by_pass_hash(pass_hash: String) -> TableIter { + pub fn filter_by_pass_hash(pass_hash: Option) -> TableIter { Self::filter(|row| row.pass_hash == pass_hash) } #[allow(unused)] diff --git a/src/plugins/login.rs b/src/plugins/login.rs index cc65cf681..3ea5b379c 100644 --- a/src/plugins/login.rs +++ b/src/plugins/login.rs @@ -7,8 +7,8 @@ use spacetimedb_sdk::{ }; use crate::module_bindings::{ - login, login_by_identity, once_on_login, once_on_login_by_identity, once_on_register, register, - GlobalData, User, + login, login_by_identity, once_on_login, once_on_login_by_identity, once_on_register, + once_on_register_empty, register, register_empty, GlobalData, User, }; use super::*; @@ -45,7 +45,7 @@ fn register_callbacks() { pub struct LoginData { pub name: String, pub pass: String, - pub prev_name: Option, + pub login_sent: bool, } #[derive(Resource, Default)] pub struct RegisterData { @@ -75,11 +75,25 @@ impl LoginPlugin { fn update(world: &mut World) { if *IS_CONNECTED.lock().unwrap() { let mut data = world.resource_mut::(); - if data.prev_name.is_none() { - if let Some(creds) = Self::load_credentials() { - if let Some(user) = User::find(|u| u.identities.contains(&creds.identity)) { - data.prev_name = Some(user.name.clone()); - } + if !data.login_sent { + let creds = Self::load_credentials().unwrap(); + data.login_sent = true; + if User::find(|u| u.identities.contains(&creds.identity)).is_some() { + Self::login_by_identity(); + } else { + register_empty(); + once_on_register_empty(|_, _, status| { + debug!("Register empty: {status:?}"); + match status { + Status::Committed => Self::login_by_identity(), + Status::Failed(e) => AlertPlugin::add_error( + Some("REGISTER ERROR".to_owned()), + e.to_owned(), + None, + ), + _ => panic!(), + } + }); } } if Self::get_username().is_some() { @@ -158,7 +172,7 @@ impl LoginPlugin { None, ); } - Status::OutOfEnergy => panic!(), + _ => panic!(), } } @@ -169,19 +183,19 @@ impl LoginPlugin { }); } + fn login_by_identity() { + login_by_identity(); + once_on_login_by_identity(|identity, _, status| { + let name = User::find(|u| u.identities.contains(identity)) + .unwrap() + .name; + Self::on_login(status, &name); + }); + } + pub fn login(ui: &mut Ui, world: &mut World) { let mut login_data = world.resource_mut::(); - if let Some(name) = &login_data.prev_name { - frame(ui, |ui| { - if ui.button(format!("LOGIN AS {name}")).clicked() { - login_by_identity(name.to_owned()); - once_on_login_by_identity(|_, _, status, name| { - Self::on_login(status, name); - }); - } - }); - } frame(ui, |ui| { ui.horizontal(|ui| { ui.vertical(|ui| { diff --git a/src/plugins/main_menu.rs b/src/plugins/main_menu.rs index 30ea00312..ec6f04bd0 100644 --- a/src/plugins/main_menu.rs +++ b/src/plugins/main_menu.rs @@ -17,27 +17,7 @@ impl MainMenuPlugin { .show(ctx, |ui| { if LoginPlugin::is_connected() { if CURRENT_USER.lock().unwrap().is_none() { - const CTX_KEY: &str = "register"; - let register = get_context_bool(world, CTX_KEY); - frame(ui, |ui| { - ui.columns(2, |ui| { - ui[0].vertical_centered_justified(|ui| { - if ui.button_or_primary("LOGIN", !register).clicked() { - set_context_bool(world, CTX_KEY, false); - } - }); - ui[1].vertical_centered_justified(|ui| { - if ui.button_or_primary("REGISTER", register).clicked() { - set_context_bool(world, CTX_KEY, true); - } - }); - }); - if register { - LoginPlugin::register(ui, world); - } else { - LoginPlugin::login(ui, world); - } - }); + LoginPlugin::login(ui, world); } } else { ui.label("DISCONNECTED"); @@ -46,7 +26,10 @@ impl MainMenuPlugin { } } - if LoginPlugin::get_username().is_some() { + if let Some(name) = LoginPlugin::get_username() { + frame(ui, |ui| { + ui.label(format!("Welcome {name}!")); + }); frame(ui, |ui| { let enabled = Save::get(world).is_ok(); ui.set_enabled(enabled); diff --git a/src/plugins/profile.rs b/src/plugins/profile.rs index 4262943ac..8b386cf84 100644 --- a/src/plugins/profile.rs +++ b/src/plugins/profile.rs @@ -1,5 +1,5 @@ use crate::module_bindings::{ - once_on_set_name, once_on_set_password, set_name, set_password, User, + logout, once_on_set_name, once_on_set_password, set_name, set_password, User, }; use super::*; @@ -86,11 +86,7 @@ impl ProfilePlugin { .ui(ui); }); }); - ui.set_enabled( - !data.old_pass.is_empty() - && !data.pass.is_empty() - && data.pass.eq(&data.pass_repeat), - ); + ui.set_enabled(!data.pass.is_empty() && data.pass.eq(&data.pass_repeat)); if ui.button("Save").clicked() { set_password(data.old_pass.clone(), data.pass.clone()); once_on_set_password(|_, _, status, _, _| match status { @@ -110,6 +106,17 @@ impl ProfilePlugin { }); } }); + + ui.collapsing("Login", |ui| { + LoginPlugin::login(ui, world); + }); + + frame(ui, |ui| { + if ui.button("LOGOUT").clicked() { + logout(); + world.send_event(AppExit); + } + }); } }); } diff --git a/src/resourses/pools.rs b/src/resourses/pools.rs index 53b2e2126..35d637e93 100644 --- a/src/resourses/pools.rs +++ b/src/resourses/pools.rs @@ -207,6 +207,10 @@ impl PoolsPlugin { return; } events.clear(); + if module_bindings::House::count() == 0 { + error!("Server assets are not synced"); + return; + } debug!("Cache server pools start"); pools.heroes.clear(); pools.enemies.clear();