diff --git a/Cargo.lock b/Cargo.lock index f7ea7a6..65b8eee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,6 +216,8 @@ dependencies = [ "log", "rc_core", "rocket", + "rocket_dyn_templates", + "serde", "steamworks", "uuid", ] @@ -562,6 +564,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -809,6 +820,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "flate2" version = "1.0.35" @@ -851,6 +874,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1049,6 +1081,20 @@ dependencies = [ "crunchy", ] +[[package]] +name = "handlebars" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1491,6 +1537,26 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1554,6 +1620,26 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1606,6 +1692,17 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.8.0", + "libc", + "redox_syscall", +] + [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1706,6 +1803,18 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.3" @@ -1736,6 +1845,34 @@ dependencies = [ "version_check", ] +[[package]] +name = "normpath" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.8.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2031,6 +2168,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +dependencies = [ + "memchr", + "thiserror 2.0.11", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "pest_meta" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pgvector" version = "0.4.0" @@ -2765,6 +2947,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "rocket_dyn_templates" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bbab919c9e67df3f7ac6624a32ef897df4cd61c0969f4d66f3ced0534660d7a" +dependencies = [ + "handlebars", + "normpath", + "notify", + "rocket", + "walkdir", +] + [[package]] name = "rocket_http" version = "0.5.1" @@ -2909,6 +3104,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -3734,7 +3938,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -3942,6 +4146,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uncased" version = "0.9.10" @@ -4067,6 +4277,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4223,6 +4443,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/assets/templates/rc_register.html.hbs b/assets/templates/rc_register.html.hbs new file mode 100644 index 0000000..8f58e43 --- /dev/null +++ b/assets/templates/rc_register.html.hbs @@ -0,0 +1,171 @@ + + + OpenJam Registration + + + +
+

Create an account for OpenJam servers

+

FOSSifying proprietary games since 2025

+ {{#if error}} +
+

{{error}}

+
+ {{/if}} +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+

By submitting, you agree to the privacy policy.

+
+ +
+ +
+ +
+

This project is not affiliated with FreeJam, Cardlife or Robocraft. We're just fixing their """sunset""".

+
+
+ +
+ + diff --git a/assets/templates/rc_register_success.html.hbs b/assets/templates/rc_register_success.html.hbs new file mode 100644 index 0000000..aeabc91 --- /dev/null +++ b/assets/templates/rc_register_success.html.hbs @@ -0,0 +1,100 @@ + + + OpenJam Success + + + +
+

Account '{{display_name}}' created!

+

Welcome user #{{id}} to Robocraft!

+
Let the FOSS flow through you...
+ + +
+ + diff --git a/auth/Cargo.toml b/auth/Cargo.toml index d7f493f..05ddbfc 100644 --- a/auth/Cargo.toml +++ b/auth/Cargo.toml @@ -15,6 +15,7 @@ default = ["robocraft", "cardlife"] [dependencies] rocket.workspace = true +rocket_dyn_templates = { version = "0.2", features = [ "handlebars" ] } libfj.workspace = true log.workspace = true env_logger.workspace = true @@ -24,3 +25,4 @@ hex = "0.4" jsonwebtoken = "9" clap.workspace = true rc_core = { version = "*", optional = true, path = "../rc_core" } +serde.workspace = true diff --git a/auth/Rocket.toml b/auth/Rocket.toml index e466d38..c17d5ef 100644 --- a/auth/Rocket.toml +++ b/auth/Rocket.toml @@ -2,6 +2,7 @@ [default] address = "127.0.0.1" port = 8001 +template_dir = "../assets/templates/" ## set only when compiled in debug mode, i.e, `cargo build` [debug] diff --git a/auth/src/robocraft/mod.rs b/auth/src/robocraft/mod.rs index 43316f9..ac201b0 100644 --- a/auth/src/robocraft/mod.rs +++ b/auth/src/robocraft/mod.rs @@ -2,6 +2,7 @@ mod debug; mod username; mod steam; mod email; +mod registration; pub fn stage() -> rocket::fairing::AdHoc { rocket::fairing::AdHoc::on_ignite("robocraft", |rocket| async { @@ -9,6 +10,7 @@ pub fn stage() -> rocket::fairing::AdHoc { .attach(email::stage()) .attach(steam::stage()) .attach(debug::stage()) + .attach(registration::stage()) .register("/", rocket::catchers![unauthorized]) }) } diff --git a/auth/src/robocraft/registration.rs b/auth/src/robocraft/registration.rs new file mode 100644 index 0000000..db27f10 --- /dev/null +++ b/auth/src/robocraft/registration.rs @@ -0,0 +1,189 @@ +use rc_core::UserAuthenticator; +use rocket::{post, get, form::{Form, FromForm}, routes, http::Status, State}; +use rocket_dyn_templates::{Template, context}; +use serde::Serialize; + +const FORM_NAME: &str = "rc_register"; +const FORM_NAME_SUCCESS: &str = "rc_register_success"; + +const VALID_CHARS: &[char] = &[ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '_', +]; + +#[derive(FromForm, Serialize)] +struct RegisterForm { + display_name: String, + password: String, + password_c: String, + email: Option, + steam_id: Option, +} + +#[derive(Serialize)] +struct Context { + form: RegisterForm, + error: Option, + version: String, + source_url: String, +} + +fn version_string() -> String { + let name = env!("CARGO_PKG_NAME"); + let version = env!("CARGO_PKG_VERSION"); + //let license = env!("CARGO_PKG_LICENSE"); + //let repo = env!("CARGO_PKG_REPOSITORY"); + format!("OpenJam {} {}", name, version) +} + +fn all_valid_chars(s: &str) -> bool { + for c in s.chars() { + if !VALID_CHARS.contains(&c) { + return false; + } + } + true +} + +fn registration_ok(form: RegisterForm) -> Template { + Template::render(FORM_NAME, Context { + form, + error: None, + version: version_string(), + source_url: env!("CARGO_PKG_REPOSITORY").to_owned(), + }) +} + +fn registration_err(form: RegisterForm, error: String) -> Template { + Template::render(FORM_NAME, Context { + form, + error: Some(error), + version: version_string(), + source_url: env!("CARGO_PKG_REPOSITORY").to_owned(), + }) +} + +#[post("/register", data = "
")] +async fn form_submit(form: Form, config: &State) -> Result { + // password confirmation validation + if form.password != form.password_c { + return Ok(registration_err(form.into_inner(), "Passwords do not match".to_owned())); + } + if form.password.len() < 8 { + return Ok(registration_err(form.into_inner(), "Password too short (minimum 8 characters)".to_owned())); + } + if form.password.len() > 128 { + return Ok(registration_err(form.into_inner(), "Password too long (maximum 128 characters)".to_owned())); + } + + // email validation + let actual_email: Option; + if let Some(email) = &form.email { + if email.is_empty() { + actual_email = None; + } else { + if !email.contains('@') { + return Ok(registration_err(form.into_inner(), "Email must contain @".to_owned())); + } + let email_exists = config.robocraft.account_provider.user_exists(rc_core::persist::user::UserId::Email(email.to_owned())) + .await + .map_err(|e| { + log::error!("Failed to check if user email {} exists: {}", email, e); + Status { code: 500 } + })?; + if email_exists { + return Ok(registration_err(form.into_inner(), "Email already registered".to_owned())); + } + actual_email = Some(email.to_owned()); + } + } else { + actual_email = None; + } + + // steam id validation + let actual_steam_id: Option; + if let Some(steam_id) = &form.steam_id { + if steam_id.is_empty() { + actual_steam_id = None; + } else { + let steam_id = match steam_id.parse() { + Ok(id) => id, + Err(_e) => return Ok(registration_err(form.into_inner(), "Invalid SteamID (not an integer)".to_owned())), + }; + if steam_id >= 7656120_0000000000 || steam_id < 7656119_0000000000 { + return Ok(registration_err(form.into_inner(), "Invalid SteamID (should be like 7656119XXXXXXXXXX)".to_owned())); + } + let steam_exists = config.robocraft.account_provider.user_exists(rc_core::persist::user::UserId::SteamId(steam_id)) + .await + .map_err(|e| { + log::error!("Failed to check if user steam id {} exists: {}", steam_id, e); + Status { code: 500 } + })?; + if steam_exists { + return Ok(registration_err(form.into_inner(), "SteamID already registered".to_owned())); + } + actual_steam_id = Some(steam_id); + } + } else { + actual_steam_id = None; + } + + // username validation + if form.display_name.len() < 4 { + return Ok(registration_err(form.into_inner(), "Username too short (minimum 4 characters)".to_owned())); + } + if form.display_name.len() > 32 { + return Ok(registration_err(form.into_inner(), "Username too long (maximum 32 characters)".to_owned())); + } + if !all_valid_chars(&form.display_name.to_lowercase()) { + return Ok(registration_err(form.into_inner(), "Invalid username (only alphanumerics and _ allowed)".to_owned())); + } + let username_exists = config.robocraft.account_provider.user_exists(rc_core::persist::user::UserId::Username(form.display_name.to_owned())) + .await + .map_err(|e| { + log::error!("Failed to check if user name {} exists: {}", form.display_name, e); + Status { code: 500 } + })?; + if username_exists { + return Ok(registration_err(form.into_inner(), "Username already registered".to_owned())); + } + + let user_id = match config.robocraft.account_provider.register(rc_core::persist::user::RegistrationInfo { + display_name: form.display_name.clone(), + password: form.password.clone(), + email: actual_email, + steam_id: actual_steam_id, + }).await { + Ok(id) => id, + Err(e) => { + return Ok(registration_err(form.into_inner(), format!("Registration failed: {}", e))); + } + }; + + Ok(Template::render(FORM_NAME_SUCCESS, context! { + display_name: form.display_name.clone(), + id: user_id, + version: version_string(), + source_url: env!("CARGO_PKG_REPOSITORY").to_owned(), + })) +} + +#[get("/register")] +async fn form_load() -> Template { + registration_ok(RegisterForm { + display_name: "".to_owned(), + password: "".to_owned(), + password_c: "".to_owned(), + email: None, + steam_id: None, + }) +} + + +pub fn stage() -> rocket::fairing::AdHoc { + rocket::fairing::AdHoc::on_ignite("Robocraft Steam", |rocket| async { + rocket.mount("/", routes![form_load, form_submit]) + .attach(Template::fairing()) + }) +} diff --git a/rc_core/src/persist/config/cubes_json.rs b/rc_core/src/persist/config/cubes_json.rs index 828a161..80b0e1d 100644 --- a/rc_core/src/persist/config/cubes_json.rs +++ b/rc_core/src/persist/config/cubes_json.rs @@ -259,6 +259,7 @@ impl super::ConfigProvider for CubeConfig { fn server_config(&self) -> super::ServerConfig { super::ServerConfig { database: self.settings.server.database.clone(), + auto_signup: self.settings.server.auto_signup, } } diff --git a/rc_core/src/persist/config/traits.rs b/rc_core/src/persist/config/traits.rs index d42a996..127d5d6 100644 --- a/rc_core/src/persist/config/traits.rs +++ b/rc_core/src/persist/config/traits.rs @@ -88,6 +88,7 @@ pub struct TypedDevMessage { pub struct ServerConfig { pub database: String, + pub auto_signup: bool, } #[derive(Clone, Debug)] diff --git a/rc_core/src/persist/settings.rs b/rc_core/src/persist/settings.rs index cc84ce3..f66b9af 100644 --- a/rc_core/src/persist/settings.rs +++ b/rc_core/src/persist/settings.rs @@ -72,6 +72,8 @@ fn default_slot_upgrades() -> Vec { pub struct ServerSettings { #[serde(default = "default_db_conn")] pub database: String, + #[serde(default)] + pub auto_signup: bool, } fn default_db_conn() -> String { @@ -81,5 +83,6 @@ fn default_db_conn() -> String { fn default_server_conf() -> ServerSettings { ServerSettings { database: default_db_conn(), + auto_signup: false, } } diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 4993439..ea623ac 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -5,6 +5,7 @@ use crate::persist::config::ConfigProvider; pub struct AccountProvider { cubes: std::sync::Arc>, garage_upgrades: std::sync::Arc, + auto_signups: bool, secret: Vec, db: std::sync::Arc, } @@ -12,13 +13,15 @@ pub struct AccountProvider { impl AccountProvider { pub async fn load(root: impl AsRef, conf: &crate::persist::config::ConfigImpl) -> std::io::Result { let token_path = root.as_ref().join(super::TOKEN_SECRET_FILENAME); - let database_uri = >::server_config(conf).database; + let server_settings = >::server_config(conf); + let database_uri = server_settings.database; log::debug!("Connecting to user database URI: {}", database_uri); let db = rc_database::Database::init(&database_uri).await .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotConnected, e))?; Ok(Self { cubes: std::sync::Arc::new(>::ids(conf)), garage_upgrades: std::sync::Arc::new(>::garage_upgrades(conf)), + auto_signups: server_settings.auto_signup, secret: std::fs::read(&token_path)?, db: std::sync::Arc::new(db), }) @@ -72,9 +75,14 @@ impl super::UserAuthenticator for AccountProvider { user_info } else { is_new_user = true; - log::info!("New user {}", info.payload.public_id); - super::setup_new_user(&info, &self.db).await.map_err(|e| e.to_string())?; - self.db.user_by_display_name(info.payload.display_name.clone()).await.map_err(|e| e.to_string())?.unwrap() + if self.auto_signups { + log::info!("New user {}", info.payload.public_id); + super::setup_new_user(&info, &self.db).await.map_err(|e| e.to_string())?; + self.db.user_by_display_name(info.payload.display_name.clone()).await.map_err(|e| e.to_string())?.unwrap() + } else { + log::info!("Rejecting user sign-in for `{}` (set settings.server.auto_signup=true to disable this behaviour)", info.payload.public_id); + return Err(format!("User does not exist")); + } }; let override_password = user_info.password.is_empty() && user_info.steam_id.is_none(); match info.extra { @@ -128,6 +136,24 @@ impl super::UserAuthenticator for AccountProvider { is_new: is_new_user, }) } + + async fn user_exists(&self, user: super::UserId) -> Result { + Ok(match user { + super::UserId::SteamId(steam_id) => { + self.db.user_by_steam_id(steam_id).await.map_err(|e| e.to_string())?.is_some() + }, + super::UserId::Email(email) => { + self.db.user_by_email(email).await.map_err(|e| e.to_string())?.is_some() + }, + super::UserId::Username(display_name) => { + self.db.user_by_display_name(display_name).await.map_err(|e| e.to_string())?.is_some() + }, + }) + } + + async fn register(&self, info: super::RegistrationInfo) -> Result { + super::register_new_user(&info, &self.db).await.map_err(|e| e.to_string()) + } } #[allow(dead_code)] diff --git a/rc_core/src/persist/user/initial_data.rs b/rc_core/src/persist/user/initial_data.rs index 74c2651..66d597d 100644 --- a/rc_core/src/persist/user/initial_data.rs +++ b/rc_core/src/persist/user/initial_data.rs @@ -5,17 +5,17 @@ fn current_unix_time() -> i64 { } async fn build_new_account_data(user: &super::UserInfo, db: &rc_database::Database) -> Result<(), rc_database::sea_orm::DbErr> { - //std::fs::create_dir(&root)?; - //let garage_dir = root.as_ref().join(super::GARAGE_DIR); - //std::fs::create_dir(&garage_dir)?; - let user_data = db.insert_user(default_user_data(user)).await?; - db.insert_perms(default_user_perms(user_data.id)).await?; - db.insert_user_aux(default_user_aux_data(user_data.id)).await?; - db.insert_garages(default_garage_slots(user_data.id)).await?; - /*for slot in default_garage_slots() { - let filepath = garage_dir.join(format!("{}.json", slot.slot)); - slot.save(filepath)?; - }*/ + let reg_info = super::RegistrationInfo { + display_name: user.payload.display_name.clone(), + password: if let super::ExtraUserInfo::Email { password } | super::ExtraUserInfo::Username { password } = &user.extra { + password.to_owned() + } else { + "".to_owned() + }, + email: None, + steam_id: if let super::ExtraUserInfo::Steam { id } = &user.extra { Some(*id) } else { None }, + }; + register_new_user(®_info, db).await?; Ok(()) } @@ -31,23 +31,28 @@ pub async fn setup_new_user(user: &super::UserInfo, db: &rc_database::Database) Ok(()) } -fn default_user_data(user: &super::UserInfo) -> rc_database::schema::user::ActiveModel { - let password = if let super::ExtraUserInfo::Email { password } | super::ExtraUserInfo::Username { password } = &user.extra { - //password.to_owned() +pub async fn register_new_user(info: &super::RegistrationInfo, db: &rc_database::Database) -> Result { + let user_data = db.insert_user(default_user_data(info)).await?; + db.insert_perms(default_user_perms(user_data.id)).await?; + db.insert_user_aux(default_user_aux_data(user_data.id)).await?; + db.insert_garages(default_garage_slots(user_data.id)).await?; + Ok(user_data.id) +} + +fn default_user_data(info: &super::RegistrationInfo) -> rc_database::schema::user::ActiveModel { + let password = { use argon2::password_hash::PasswordHasher; let argon2_algo = argon2::Argon2::default(); let salt = argon2::password_hash::SaltString::generate(&mut argon2::password_hash::rand_core::OsRng); - match argon2_algo.hash_password(password.as_bytes(), &salt) { + match argon2_algo.hash_password(info.password.as_bytes(), &salt) { Err(e) => { - log::error!("Failed to hash password for user {}/{}: {}", user.payload.public_id, user.payload.display_name, e); + log::error!("Failed to hash password for user {}: {}", info.display_name, e); "".to_owned() }, Ok(password) => password.to_string(), } - } else { - Default::default() }; - let steam_id = if let super::ExtraUserInfo::Steam { id } = &user.extra { + let steam_id = if let Some(id) = info.steam_id { Some(id.to_string()) } else { None @@ -55,28 +60,12 @@ fn default_user_data(user: &super::UserInfo) -> rc_database::schema::user::Activ rc_database::schema::user::ActiveModel { id: Default::default(), creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), - public_id: rc_database::sea_orm::ActiveValue::Set(user.payload.public_id.clone()), - display_name: rc_database::sea_orm::ActiveValue::Set(user.payload.display_name.clone()), + public_id: rc_database::sea_orm::ActiveValue::Set(info.display_name.clone()), + display_name: rc_database::sea_orm::ActiveValue::Set(info.display_name.clone()), password: rc_database::sea_orm::ActiveValue::Set(password), - email: rc_database::sea_orm::ActiveValue::Set("//TODO".to_owned()), + email: rc_database::sea_orm::ActiveValue::Set(info.email.clone().unwrap_or_else(|| "".to_owned())), steam_id: rc_database::sea_orm::ActiveValue::Set(steam_id), } - - /*super::AccountInfo { - is_mod: false, - is_admin: false, - is_dev: false, - steam_id: None, - password: None, - inventory: super::UnlockedParts { - unlocked: vec![], - override_: super::inventory::UnlockOverride::UnlockAll, - }, - garage: super::SelectedGarage { - uuid: (0, 0), - slot: 0, - }, - }*/ } fn default_user_aux_data(user_id: u32) -> Vec { diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs index 039fff8..f489f55 100644 --- a/rc_core/src/persist/user/mod.rs +++ b/rc_core/src/persist/user/mod.rs @@ -5,13 +5,13 @@ mod garage_data; pub use garage_data::SelectedGarage; mod initial_data; -pub use initial_data::setup_new_user; +pub use initial_data::{setup_new_user, register_new_user}; mod inventory; pub use inventory::UnlockedParts; mod traits; -pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData}; +pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo}; pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key"; diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 28e100c..6ce5649 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -23,11 +23,24 @@ pub enum ExtraUserInfo { } } +pub enum UserId { + SteamId(u64), + Email(String), + Username(String), +} + pub struct UserLoginInfo { pub response: libfj::robocraft::AuthenticationResponseInfo, pub is_new: bool, } +pub struct RegistrationInfo { + pub display_name: String, + pub password: String, + pub email: Option, + pub steam_id: Option, +} + #[async_trait::async_trait] pub trait UserProvider { async fn authenticate(&self, user: UserToken, ext: std::collections::HashMap>) -> Result + Send + Sync>, String>; @@ -36,6 +49,8 @@ pub trait UserProvider { #[async_trait::async_trait] pub trait UserAuthenticator { async fn login(&self, info: UserInfo) -> Result; + async fn user_exists(&self, user: UserId) -> Result; + async fn register(&self, info: RegistrationInfo) -> Result; } #[async_trait::async_trait]