1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Add user registration

This commit is contained in:
NG (Graham)
2025-05-12 20:56:24 -04:00
parent d3bd795a01
commit 8affa33ace
14 changed files with 774 additions and 45 deletions

View File

@@ -259,6 +259,7 @@ impl <C: Clone> super::ConfigProvider<C> for CubeConfig {
fn server_config(&self) -> super::ServerConfig {
super::ServerConfig {
database: self.settings.server.database.clone(),
auto_signup: self.settings.server.auto_signup,
}
}

View File

@@ -88,6 +88,7 @@ pub struct TypedDevMessage<C> {
pub struct ServerConfig {
pub database: String,
pub auto_signup: bool,
}
#[derive(Clone, Debug)]

View File

@@ -72,6 +72,8 @@ fn default_slot_upgrades() -> Vec<GarageSlotUpgrade> {
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,
}
}

View File

@@ -5,6 +5,7 @@ use crate::persist::config::ConfigProvider;
pub struct AccountProvider {
cubes: std::sync::Arc<Vec<u32>>,
garage_upgrades: std::sync::Arc<crate::persist::config::GarageUpgrades>,
auto_signups: bool,
secret: Vec<u8>,
db: std::sync::Arc<rc_database::Database>,
}
@@ -12,13 +13,15 @@ pub struct AccountProvider {
impl AccountProvider {
pub async fn load(root: impl AsRef<std::path::Path>, conf: &crate::persist::config::ConfigImpl) -> std::io::Result<Self> {
let token_path = root.as_ref().join(super::TOKEN_SECRET_FILENAME);
let database_uri = <crate::persist::config::ConfigImpl as ConfigProvider<()>>::server_config(conf).database;
let server_settings = <crate::persist::config::ConfigImpl as ConfigProvider<()>>::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(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::ids(conf)),
garage_upgrades: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::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<bool, String> {
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<u32, String> {
super::register_new_user(&info, &self.db).await.map_err(|e| e.to_string())
}
}
#[allow(dead_code)]

View File

@@ -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(&reg_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<u32, rc_database::sea_orm::DbErr> {
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<rc_database::schema::user_aux::ActiveModel> {

View File

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

View File

@@ -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<String>,
pub steam_id: Option<u64>,
}
#[async_trait::async_trait]
pub trait UserProvider<C> {
async fn authenticate(&self, user: UserToken, ext: std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>) -> Result<Box<dyn User<C> + Send + Sync>, String>;
@@ -36,6 +49,8 @@ pub trait UserProvider<C> {
#[async_trait::async_trait]
pub trait UserAuthenticator {
async fn login(&self, info: UserInfo) -> Result<UserLoginInfo, String>;
async fn user_exists(&self, user: UserId) -> Result<bool, String>;
async fn register(&self, info: RegistrationInfo) -> Result<u32, String>;
}
#[async_trait::async_trait]