diff --git a/Cargo.lock b/Cargo.lock
index 24ee65e..91a456e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -52,7 +52,7 @@ dependencies = [
"actix-rt",
"actix-service",
"actix-utils",
- "base64",
+ "base64 0.22.1",
"bitflags",
"brotli",
"bytes",
@@ -80,6 +80,22 @@ dependencies = [
"zstd",
]
+[[package]]
+name = "actix-identity"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "810f47733f956175bd5b2ae17ae5237fa92bd1b6a4a65f646a7240dbe9ff2728"
+dependencies = [
+ "actix-service",
+ "actix-session",
+ "actix-utils",
+ "actix-web",
+ "derive_more",
+ "futures-core",
+ "serde",
+ "tracing",
+]
+
[[package]]
name = "actix-macros"
version = "0.2.4"
@@ -141,6 +157,23 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "actix-session"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "400c27fd4cdbe0082b7bbd29ac44a3070cbda1b2114138dc106ba39fe2f90dff"
+dependencies = [
+ "actix-service",
+ "actix-utils",
+ "actix-web",
+ "anyhow",
+ "derive_more",
+ "rand 0.9.2",
+ "serde",
+ "serde_json",
+ "tracing",
+]
+
[[package]]
name = "actix-utils"
version = "3.0.1"
@@ -169,6 +202,7 @@ dependencies = [
"bytes",
"bytestring",
"cfg-if",
+ "cookie",
"derive_more",
"encoding_rs",
"foldhash",
@@ -224,6 +258,16 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
[[package]]
name = "aes"
version = "0.8.4"
@@ -235,6 +279,20 @@ dependencies = [
"cpufeatures",
]
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
[[package]]
name = "ahash"
version = "0.7.8"
@@ -341,6 +399,12 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
[[package]]
name = "approx"
version = "0.1.1"
@@ -467,6 +531,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+[[package]]
+name = "base64"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -800,6 +870,24 @@ dependencies = [
"unicode-segmentation",
]
+[[package]]
+name = "cookie"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
+dependencies = [
+ "aes-gcm",
+ "base64 0.20.0",
+ "hkdf",
+ "hmac",
+ "percent-encoding",
+ "rand 0.8.5",
+ "sha2",
+ "subtle",
+ "time",
+ "version_check",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -879,9 +967,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
+ "rand_core 0.6.4",
"typenum",
]
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
@@ -1422,6 +1520,16 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
[[package]]
name = "git-version"
version = "0.3.9"
@@ -1678,7 +1786,7 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"futures-channel",
"futures-util",
@@ -1955,7 +2063,7 @@ version = "10.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1"
dependencies = [
- "base64",
+ "base64 0.22.1",
"ed25519-dalek",
"getrandom 0.2.17",
"hmac",
@@ -2006,7 +2114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68fff2e457fb18346f6de760a0bbfe2de69febc5ab1782c5ff470db14de2241d"
dependencies = [
"async-trait",
- "base64",
+ "base64 0.22.1",
"cgmath 0.18.0",
"chrono",
"genmesh",
@@ -2522,7 +2630,7 @@ version = "1.2.2"
dependencies = [
"argon2",
"async-trait",
- "base64",
+ "base64 0.22.1",
"chrono",
"futures",
"hex",
@@ -2563,7 +2671,7 @@ name = "oj_rc_factory"
version = "1.2.2"
dependencies = [
"async-trait",
- "base64",
+ "base64 0.22.1",
"chrono",
"hex",
"libfj",
@@ -2579,7 +2687,7 @@ version = "1.2.2"
dependencies = [
"actix-files",
"actix-web",
- "base64",
+ "base64 0.22.1",
"clap",
"env_logger",
"git-version",
@@ -2772,6 +2880,29 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "oj_rc_society"
+version = "1.2.2"
+dependencies = [
+ "actix-files",
+ "actix-identity",
+ "actix-session",
+ "actix-web",
+ "actix-ws",
+ "chrono",
+ "clap",
+ "cookie",
+ "env_logger",
+ "git-version",
+ "handlebars",
+ "libfj",
+ "log",
+ "oj_rc_core",
+ "serde",
+ "serde_json",
+ "tokio",
+]
+
[[package]]
name = "oj_serdes"
version = "0.3.0"
@@ -2794,6 +2925,12 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
[[package]]
name = "ordered-float"
version = "4.6.0"
@@ -2907,7 +3044,7 @@ version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
- "base64",
+ "base64 0.22.1",
"serde_core",
]
@@ -3038,6 +3175,18 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
[[package]]
name = "portable-atomic"
version = "1.13.1"
@@ -3429,7 +3578,7 @@ version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
@@ -4074,7 +4223,7 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bigdecimal",
"bytes",
"chrono",
@@ -4154,7 +4303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
- "base64",
+ "base64 0.22.1",
"bigdecimal",
"bitflags",
"byteorder",
@@ -4201,7 +4350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
- "base64",
+ "base64 0.22.1",
"bigdecimal",
"bitflags",
"byteorder",
@@ -4748,6 +4897,16 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -4760,7 +4919,7 @@ version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
dependencies = [
- "base64",
+ "base64 0.22.1",
"flate2",
"log",
"once_cell",
diff --git a/Cargo.toml b/Cargo.toml
index fc1f57e..472db84 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ members = [
"rc_factory", "rc_factory_web",
"rc_multiplayer",
"rc_plugins",
+ "rc_society",
]
[workspace.dependencies]
diff --git a/assets/templates/rc_society/dashboard.html.hbs b/assets/templates/rc_society/dashboard.html.hbs
new file mode 100644
index 0000000..c4b1211
--- /dev/null
+++ b/assets/templates/rc_society/dashboard.html.hbs
@@ -0,0 +1,273 @@
+
+
+
+
+
+ Dashboard - Openjam
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/templates/rc_society/index.html.hbs b/assets/templates/rc_society/index.html.hbs
new file mode 100644
index 0000000..2d2a359
--- /dev/null
+++ b/assets/templates/rc_society/index.html.hbs
@@ -0,0 +1,233 @@
+
+
+
+
+
+ Home - Openjam
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/templates/rc_society/login.html.hbs b/assets/templates/rc_society/login.html.hbs
new file mode 100644
index 0000000..0e97a06
--- /dev/null
+++ b/assets/templates/rc_society/login.html.hbs
@@ -0,0 +1,197 @@
+
+
+
+
+
+ Login - Openjam
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs
index 0dabdb5..6584556 100644
--- a/rc_core/src/persist/user/account_json.rs
+++ b/rc_core/src/persist/user/account_json.rs
@@ -81,16 +81,12 @@ impl AccountProvider {
db: self.db.clone(),
})
}*/
-}
-#[async_trait::async_trait]
-impl super::UserProvider for AccountProvider {
- async fn authenticate(&self, token: super::UserToken) -> Result + Send + Sync>, super::AuthError> {
- //let new_root = self.root.join(&token.uuid);
+ async fn auth_internal(&self, token: &str) -> Result {
let secret = jsonwebtoken::DecodingKey::from_secret(&self.secret);
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
validation.set_required_spec_claims::<&str>(&[]);
- let token_data = jsonwebtoken::decode::(&token.token, &secret, &validation).map_err(|e| super::AuthError {
+ let token_data = jsonwebtoken::decode::(&token, &secret, &validation).map_err(|e| super::AuthError {
message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
})?;
@@ -119,8 +115,7 @@ impl super::UserProvider for AccountProvider {
};
#[cfg(debug_assertions)]
log::info!("Authenticated user {} with flags {:?}", display_name, token_data.claims.client_details.flags.as_slice());
- //let account_info = AccountInfo::load(&new_root).map_err(|e| e.to_string())?;
- Ok(Box::new(UserData {
+ Ok(UserData {
account: user_info,
perms: user_perms,
cubes: self.cubes.clone(),
@@ -133,7 +128,14 @@ impl super::UserProvider for AccountProvider {
http_client: std::sync::Arc::new(reqwest::Client::new()),
db: self.db.clone(),
secret: self.secret.clone(),
- }))
+ })
+ }
+}
+
+#[async_trait::async_trait]
+impl super::UserProvider for AccountProvider {
+ async fn authenticate(&self, token: super::UserToken) -> Result + Send + Sync>, super::AuthError> {
+ Ok(Box::new(self.auth_internal(&token.token).await?))
}
async fn multiplayer_authenticate(&self, user: String) -> Result + Send + Sync>, super::AuthError> {
@@ -174,6 +176,10 @@ impl super::UserProvider for AccountProvider {
secret: self.secret.clone(),
}))
}
+
+ async fn web_authenticate(&self, token: String) -> Result, super::AuthError> {
+ Ok(Box::new(self.auth_internal(&token).await?))
+ }
}
#[async_trait::async_trait]
@@ -349,6 +355,18 @@ impl super::UserAuthenticator for AccountProvider {
async fn register(&self, info: super::RegistrationInfo) -> Result {
super::register_new_user(&info, &self.db).await.map_err(|e| e.to_string())
}
+
+ async fn verify(&self, token: String) -> Result {
+ let secret = jsonwebtoken::DecodingKey::from_secret(&self.secret);
+ let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
+ validation.set_required_spec_claims::<&str>(&[]);
+ jsonwebtoken::decode::(&token, &secret, &validation)
+ .map_err(|e| super::AuthError {
+ message: e.to_string(),
+ code: crate::data::error_codes::AuthErrorCode::BadCredentials,
+ })
+ .map(|decoded| decoded.claims)
+ }
}
pub(super) struct UserData {
diff --git a/rc_core/src/persist/user/common.rs b/rc_core/src/persist/user/common.rs
index dadb5eb..8203bcf 100644
--- a/rc_core/src/persist/user/common.rs
+++ b/rc_core/src/persist/user/common.rs
@@ -14,6 +14,14 @@ impl super::CommonUser for UserData {
&self.account.public_id
}
+ fn display_name(&self) -> &'_ str {
+ &self.account.display_name
+ }
+
+ fn creation(&self) -> i64 {
+ self.account.creation_time
+ }
+
fn is_mod(&self) -> bool {
self.perms.moderator
}
diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs
index 3d59c7e..e16c21b 100644
--- a/rc_core/src/persist/user/mod.rs
+++ b/rc_core/src/persist/user/mod.rs
@@ -11,7 +11,7 @@ mod inventory;
pub use inventory::{UnlockedParts, UnlockOverride};
mod traits;
-pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides};
+pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides, WebUser};
pub mod intercom;
pub use intercom::generate_token as generate_intercom_token;
@@ -26,6 +26,7 @@ mod factory;
mod userless;
mod team;
pub use team::{TeamChooser, StandardTeamChooser};
+mod web;
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
diff --git a/rc_core/src/persist/user/multiplayer.rs b/rc_core/src/persist/user/multiplayer.rs
index 566cd0e..ac3acb9 100644
--- a/rc_core/src/persist/user/multiplayer.rs
+++ b/rc_core/src/persist/user/multiplayer.rs
@@ -106,18 +106,6 @@ impl UserData {
#[async_trait::async_trait]
impl super::MultiplayerUser for UserData {
- fn user_id(&self) -> i32 {
- self.account.id
- }
-
- fn user_name(&self) -> &'_ str {
- &self.account.public_id
- }
-
- fn display_name(&self) -> &'_ str {
- &self.account.display_name
- }
-
async fn current_game(&self) -> Result, super::MultiplayerError> {
Ok(self.db.game_by_user_id_and_completion(self.account.id, false).await
.map_err(|e| {
diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs
index 7dc01a3..977f6a4 100644
--- a/rc_core/src/persist/user/traits.rs
+++ b/rc_core/src/persist/user/traits.rs
@@ -63,6 +63,8 @@ pub trait UserProvider {
async fn authenticate(&self, user: UserToken) -> Result + Send + Sync>, AuthError>;
async fn multiplayer_authenticate(&self, user: String) -> Result + Send + Sync>, AuthError>;
+
+ async fn web_authenticate(&self, token: String) -> Result, AuthError>;
}
#[async_trait::async_trait]
@@ -70,6 +72,7 @@ pub trait UserAuthenticator {
async fn login(&self, info: UserAuthInfo) -> Result;
async fn user_exists(&self, user: UserId) -> Result;
async fn register(&self, info: RegistrationInfo) -> Result;
+ async fn verify(&self, token: String) -> Result;
}
#[async_trait::async_trait]
@@ -385,9 +388,6 @@ pub enum MultiplayerErrorCode {
#[async_trait::async_trait]
pub trait MultiplayerUser: IntercomUser + CommonUser {
- fn user_id(&self) -> i32;
- fn user_name(&self) -> &'_ str;
- fn display_name(&self) -> &'_ str;
async fn current_game(&self) -> Result, MultiplayerError>;
async fn game_players(&self, guid: &str) -> Result, MultiplayerError>;
async fn complete_game(&self, guid: &str) -> Result<(), MultiplayerError>;
@@ -455,6 +455,9 @@ pub trait CommonUser: Send + Sync {
fn account_id(&self) -> i32;
async fn resolve_config_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result;
fn public_id(&self) -> &'_ str;
+ fn display_name(&self) -> &'_ str;
+ /// Seconds since Unix epoch
+ fn creation(&self) -> i64;
fn is_mod(&self) -> bool;
fn is_admin(&self) -> bool;
fn is_dev(&self) -> bool;
@@ -671,6 +674,11 @@ pub trait FactoryUser {
async fn rate_vehicle(&self, slot: i32, combat: i32, cosmetic: i32) -> Result, polariton_server::operations::SimpleOpError>;
}
+#[async_trait::async_trait]
+pub trait WebUser: CommonUser {
+
+}
+
#[async_trait::async_trait]
pub trait Userless: Send + Sync {
async fn lobby_state_listener(&self) -> Result, reqwest_websocket::Error>;
diff --git a/rc_core/src/persist/user/web.rs b/rc_core/src/persist/user/web.rs
new file mode 100644
index 0000000..aa2d0e9
--- /dev/null
+++ b/rc_core/src/persist/user/web.rs
@@ -0,0 +1,4 @@
+#[async_trait::async_trait]
+impl super::WebUser for super::account_json::UserData {
+ // TODO
+}
diff --git a/rc_multiplayer/src/disconnect.rs b/rc_multiplayer/src/disconnect.rs
index 7aec423..b06afbf 100644
--- a/rc_multiplayer/src/disconnect.rs
+++ b/rc_multiplayer/src/disconnect.rs
@@ -19,7 +19,7 @@ impl crate::DisconnectHandler for ClientDisconnecter {
async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData) {
if let Some(user_info) = user.user().await {
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::EndConnection {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
is_unregister: false,
}).await);
} else {
diff --git a/rc_multiplayer/src/events/activate_sync.rs b/rc_multiplayer/src/events/activate_sync.rs
index b7f848d..11ae9e6 100644
--- a/rc_multiplayer/src/events/activate_sync.rs
+++ b/rc_multiplayer/src/events/activate_sync.rs
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for RequestLoadingSync {
async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RequestLoadingSync {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
}).await);
} else {
log::error!("Failed to handle sync loading request for unknown user");
diff --git a/rc_multiplayer/src/events/all_loading_progress.rs b/rc_multiplayer/src/events/all_loading_progress.rs
index cb565fd..a8c2ab1 100644
--- a/rc_multiplayer/src/events/all_loading_progress.rs
+++ b/rc_multiplayer/src/events/all_loading_progress.rs
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for RequestAllLoadingProgress {
async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RequestLoadingProgress {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
}).await);
} else {
log::error!("Failed to broadcast loading progress for unknown user");
diff --git a/rc_multiplayer/src/events/assist_bonus.rs b/rc_multiplayer/src/events/assist_bonus.rs
index fcdbf8e..13120d9 100644
--- a/rc_multiplayer/src/events/assist_bonus.rs
+++ b/rc_multiplayer/src/events/assist_bonus.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for AssistBonus {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::AssistBonus {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
shootee: data.requester_player_id as u8,
shooters: data.player_ids.into_iter().map(|x| x.player).collect(),
}).await);
diff --git a/rc_multiplayer/src/events/client_unregister.rs b/rc_multiplayer/src/events/client_unregister.rs
index 120d836..3ea61c5 100644
--- a/rc_multiplayer/src/events/client_unregister.rs
+++ b/rc_multiplayer/src/events/client_unregister.rs
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for ClientUnregisterer {
async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::EndConnection {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
is_unregister: true,
}).await);
} else {
diff --git a/rc_multiplayer/src/events/damage_bonus.rs b/rc_multiplayer/src/events/damage_bonus.rs
index ee44f9f..e2a14fe 100644
--- a/rc_multiplayer/src/events/damage_bonus.rs
+++ b/rc_multiplayer/src/events/damage_bonus.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for DamageBonus {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::DestroyCubesBonus {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
info: data,
}).await);
}
diff --git a/rc_multiplayer/src/events/flipper_start.rs b/rc_multiplayer/src/events/flipper_start.rs
index 546678c..e32f411 100644
--- a/rc_multiplayer/src/events/flipper_start.rs
+++ b/rc_multiplayer/src/events/flipper_start.rs
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for RectifierStart {
async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::FlippingStarted {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
}).await);
} else {
log::error!("Failed to handle sync loading request for unknown user");
diff --git a/rc_multiplayer/src/events/heal_assist_bonus.rs b/rc_multiplayer/src/events/heal_assist_bonus.rs
index 857ce99..aa3eecd 100644
--- a/rc_multiplayer/src/events/heal_assist_bonus.rs
+++ b/rc_multiplayer/src/events/heal_assist_bonus.rs
@@ -23,7 +23,7 @@ impl crate::handlers::RlnlEventCodeHandler for HealAssistBonus {
if let Some(user_info) = user.user().await {
//log::info!("Heal assist for player {}", data.healing_player_id);
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::HealAssistBonus {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
healer: data.healing_player_id,
healee: data.healed_player_id,
}).await);
diff --git a/rc_multiplayer/src/events/heal_bonus.rs b/rc_multiplayer/src/events/heal_bonus.rs
index 9309004..41d9340 100644
--- a/rc_multiplayer/src/events/heal_bonus.rs
+++ b/rc_multiplayer/src/events/heal_bonus.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for HealBonus {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::HealCubesBonus {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
info: data,
}).await);
}
diff --git a/rc_multiplayer/src/events/kill_bonus.rs b/rc_multiplayer/src/events/kill_bonus.rs
index 398903f..d997e00 100644
--- a/rc_multiplayer/src/events/kill_bonus.rs
+++ b/rc_multiplayer/src/events/kill_bonus.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for KillEnemyBonus {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::KillBonus {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
shootee: data.killee_player_id,
shooter: data.killer_player_id,
}).await);
diff --git a/rc_multiplayer/src/events/kill_player.rs b/rc_multiplayer/src/events/kill_player.rs
index 392669f..c040a9f 100644
--- a/rc_multiplayer/src/events/kill_player.rs
+++ b/rc_multiplayer/src/events/kill_player.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for KillEnemy {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::DestroyVehicle {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
remote_player: data.killee_player_id,
killer_player: data.killer_player_id,
}).await);
diff --git a/rc_multiplayer/src/events/loading_done.rs b/rc_multiplayer/src/events/loading_done.rs
index 292e5e3..d01769d 100644
--- a/rc_multiplayer/src/events/loading_done.rs
+++ b/rc_multiplayer/src/events/loading_done.rs
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for LoadComplete {
async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::LoadComplete {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
}).await);
} else {
log::error!("Failed to handle sync loading request for unknown user");
diff --git a/rc_multiplayer/src/events/loading_progress.rs b/rc_multiplayer/src/events/loading_progress.rs
index 3505022..c205985 100644
--- a/rc_multiplayer/src/events/loading_progress.rs
+++ b/rc_multiplayer/src/events/loading_progress.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for GameLoadingProgress {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::LoadingProgress {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
user_name: data.user_name.0,
progress: data.progress,
}).await);
diff --git a/rc_multiplayer/src/events/map_ping.rs b/rc_multiplayer/src/events/map_ping.rs
index b565315..3d3c59a 100644
--- a/rc_multiplayer/src/events/map_ping.rs
+++ b/rc_multiplayer/src/events/map_ping.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for PingMap {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::MapPing {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
ping: data,
}).await);
}
diff --git a/rc_multiplayer/src/events/player_input.rs b/rc_multiplayer/src/events/player_input.rs
index 0221e4d..4359f88 100644
--- a/rc_multiplayer/src/events/player_input.rs
+++ b/rc_multiplayer/src/events/player_input.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for PlayerInput {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::PlayerInputChanged {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
data,
}).await);
} else {
diff --git a/rc_multiplayer/src/events/player_leave.rs b/rc_multiplayer/src/events/player_leave.rs
index 1de37cc..0e7dec8 100644
--- a/rc_multiplayer/src/events/player_leave.rs
+++ b/rc_multiplayer/src/events/player_leave.rs
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for PlayerQuit {
async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RequestLeave {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
}).await);
} else {
log::error!("Failed to handle sync loading request for unknown user");
diff --git a/rc_multiplayer/src/events/self_destruct_elimination.rs b/rc_multiplayer/src/events/self_destruct_elimination.rs
index 55b9bc6..4a2cb59 100644
--- a/rc_multiplayer/src/events/self_destruct_elimination.rs
+++ b/rc_multiplayer/src/events/self_destruct_elimination.rs
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for SelfDestructElim {
async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::SelfDestruct {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
is_classic: true,
}).await);
} else {
diff --git a/rc_multiplayer/src/events/spot_player.rs b/rc_multiplayer/src/events/spot_player.rs
index 1159e15..60301b3 100644
--- a/rc_multiplayer/src/events/spot_player.rs
+++ b/rc_multiplayer/src/events/spot_player.rs
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for SpotEnemy {
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc>, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::SpotVehicle {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
remote_player: data.player_id,
}).await);
}
diff --git a/rc_multiplayer/src/events/validate_game_guid.rs b/rc_multiplayer/src/events/validate_game_guid.rs
index 7603a27..844e9df 100644
--- a/rc_multiplayer/src/events/validate_game_guid.rs
+++ b/rc_multiplayer/src/events/validate_game_guid.rs
@@ -64,7 +64,7 @@ impl crate::handlers::RlnlEventCodeHandler for AuthUserGame {
}
},
Ok(None) => {
- log::warn!("Cannot validate game guid for user {} with no ongoing game [disconnecting...]", user_info.user_id());
+ log::warn!("Cannot validate game guid for user {} with no ongoing game [disconnecting...]", user_info.account_id());
super::log_lnl_send_failure(crate::handlers::RlnlSender::new(sender)
.send_data(&rlnl::types::StringCode {
ty: rlnl::types::GameServerErrorCodes::StrErrIncorrectGameGuid,
@@ -76,7 +76,7 @@ impl crate::handlers::RlnlEventCodeHandler for AuthUserGame {
peer.disconnect();
},
Err(e) => {
- log::error!("Failed to get current game for user {}: {} [disconnecting...]", user_info.user_id(), e.message);
+ log::error!("Failed to get current game for user {}: {} [disconnecting...]", user_info.account_id(), e.message);
super::log_lnl_send_failure(crate::handlers::RlnlSender::new(sender)
.send_data(&rlnl::types::StringCode {
ty: core_to_rlnl_mp_error_code(e.code),
diff --git a/rc_multiplayer/src/events/weapon_select.rs b/rc_multiplayer/src/events/weapon_select.rs
index 1c3ae76..8015d6e 100644
--- a/rc_multiplayer/src/events/weapon_select.rs
+++ b/rc_multiplayer/src/events/weapon_select.rs
@@ -24,7 +24,7 @@ impl crate::handlers::RlnlEventCodeHandler for WeaponSelect {
if let Some(category) = oj_rc_core::data::weapon_list::ItemCategory::from_smaller(data.item_category as _) {
if let Some(tier) = oj_rc_core::data::cube_list::ItemTier::from_u32(data.item_size as _) {
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::WeaponSelect {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
machine_id: data.machine_id,
category,
size: tier,
diff --git a/rc_multiplayer/src/handler.rs b/rc_multiplayer/src/handler.rs
index e396221..eca1ba2 100644
--- a/rc_multiplayer/src/handler.rs
+++ b/rc_multiplayer/src/handler.rs
@@ -79,7 +79,7 @@ impl literustlib_server::EventHandler for LnlEventHandler {
async fn on_disconnect(&self, peer: &std::sync::Arc>, user: &Self::UserData) {
self.disconnect_handler.handle(peer, user).await;
if let Some(user_info) = user.user().await {
- log::info!("Disconnect from user {} ({})", user_info.user_id(), peer.id());
+ log::info!("Disconnect from user {} ({})", user_info.account_id(), peer.id());
} else {
log::debug!("Disconnect from connection {}", peer.id());
}
diff --git a/rc_multiplayer/src/handlers/gamemode_specific.rs b/rc_multiplayer/src/handlers/gamemode_specific.rs
index 7c65dd4..b83200e 100644
--- a/rc_multiplayer/src/handlers/gamemode_specific.rs
+++ b/rc_multiplayer/src/handlers/gamemode_specific.rs
@@ -29,7 +29,7 @@ impl >, user: &crate::UserData, _sender: &std::sync::Arc>) {
if let Some(user_info) = user.user().await {
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::CustomLogicRlnl {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
event: self.event,
property: self.property,
data: Box::new(data),
diff --git a/rc_multiplayer/src/handlers/ingame_broadcast.rs b/rc_multiplayer/src/handlers/ingame_broadcast.rs
index 375b902..689e2f7 100644
--- a/rc_multiplayer/src/handlers/ingame_broadcast.rs
+++ b/rc_multiplayer/src/handlers/ingame_broadcast.rs
@@ -30,7 +30,7 @@ impl GenericGamemodeEngine {
pub(super) async fn rebroadcast(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T, in_game: bool) {
for (player_id, conn) in self.users.read().await.iter() {
- if user_id == conn.user.user_id() { continue; }
+ if user_id == conn.user.account_id() { continue; }
if conn.aliases.contains(player_id) { continue; }
if in_game {
let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed));
@@ -388,7 +388,7 @@ impl GenericGamemodeEngine {
pub(super) async fn rebroadcast_dataless(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, in_game: bool) {
for (player_id, conn) in self.users.read().await.iter() {
- if user_id == conn.user.user_id() { continue; }
+ if user_id == conn.user.account_id() { continue; }
if conn.aliases.contains(player_id) { continue; }
if in_game {
let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed));
@@ -586,7 +586,7 @@ impl GenericGamemodeEngine {
} else {
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
//let id = users.len() as u8;
- let user_id = user.user_id();
+ let user_id = user.account_id();
let player_info_opt = self.descriptors.values().find(|p| p.descriptor.user_id == Some(user_id));
if player_info_opt.is_none() {
log::warn!("User {} tried to connect to match {} which they are not in", user_id, self.game_guid());
@@ -617,7 +617,7 @@ impl GenericGamemodeEngine {
literustlib::packet::Property::ReliableOrdered,
&new_user.connection.connection
).await);
- log::debug!("User {} is validated to play game {}", new_user.user.user_id(), game_guid);
+ log::debug!("User {} is validated to play game {}", new_user.user.account_id(), game_guid);
let new_user = std::sync::Arc::new(new_user);
if let Err(e) = new_user.user.save_player_connected_status(self.game_guid(), true).await {
log::error!("Failed to mark player {} (user {}) as connected to game {}: {}", id, user_id, self.game_guid(), e);
@@ -757,7 +757,7 @@ impl GenericGamemodeEngine {
};
for (player_id, conn) in self.users.read().await.iter() {
let user_desc = self.user_descriptor(*player_id).unwrap();
- if user_id == conn.user.user_id() {
+ if user_id == conn.user.account_id() {
let progress_percent = ((progress * 100.0).ceil() as u8).clamp(0, 100);
log::debug!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid());
user_desc.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed);
@@ -894,7 +894,7 @@ impl GenericGamemodeEngine {
for (player_id, user) in self.users.read().await.iter() {
let user_desc = self.user_descriptor(*player_id).unwrap();
if user.aliases.contains(player_id) { continue; }
- if user.user.user_id() == user_id {
+ if user.user.account_id() == user_id {
if !matches!(ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) {
log::warn!("Got RequestLoadingSync after user {} was already in/past WaitingForSync stage", user_id);
continue;
@@ -1361,7 +1361,7 @@ impl GenericGamemodeEngine {
fn spawn_send_loading_events(&self, user: &UserConnection, player_id: u8, players: Vec>, client_ais: Vec) {
let connection = user.connection.clone();
- let user_id = user.user.user_id();
+ let user_id = user.user.account_id();
tokio::spawn(Self::send_loading_events_wrapper(connection, player_id, user_id, players, client_ais));
}
diff --git a/rc_multiplayer/src/matches/messages.rs b/rc_multiplayer/src/matches/messages.rs
index a28c46c..b7a989e 100644
--- a/rc_multiplayer/src/matches/messages.rs
+++ b/rc_multiplayer/src/matches/messages.rs
@@ -120,7 +120,7 @@ impl GameMessage {
pub fn user_id(&self) -> i32 {
match self {
Self::NewConnection { user, .. } => {
- user.user_id()
+ user.account_id()
}
Self::EndConnection { user_id, .. } => *user_id,
Self::RequestLeave { user_id, .. } => *user_id,
diff --git a/rc_multiplayer/src/matches/modes/pit.rs b/rc_multiplayer/src/matches/modes/pit.rs
index 47cdcb5..9a0ac0f 100644
--- a/rc_multiplayer/src/matches/modes/pit.rs
+++ b/rc_multiplayer/src/matches/modes/pit.rs
@@ -411,7 +411,7 @@ impl CustomGameLogic for PitLogic {
}
}
if single_client.is_some() {
- let user_id = read_lock.values().next().unwrap().user.user_id();
+ let user_id = read_lock.values().next().unwrap().user.account_id();
let player_id = generic.user_key_by_user_id(user_id).unwrap();
let user_info = generic.user_descriptor(player_id).unwrap();
WinTracker::do_win(generic, self, user_info.descriptor.team as u8).await;
diff --git a/rc_multiplayer/src/vehicle_motion.rs b/rc_multiplayer/src/vehicle_motion.rs
index a58c104..08b924d 100644
--- a/rc_multiplayer/src/vehicle_motion.rs
+++ b/rc_multiplayer/src/vehicle_motion.rs
@@ -24,7 +24,7 @@ impl crate::RobotMotionHandler for VehicleMotionHandler {
match rlnl::machine_motion::MachineMotion::byte_deserialize(&mut des) {
Ok(motion_data) => {
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::Motion {
- user_id: user_info.user_id(),
+ user_id: user_info.account_id(),
motion: motion_data,
}).await);
},
diff --git a/rc_society/Cargo.toml b/rc_society/Cargo.toml
new file mode 100644
index 0000000..cb40be6
--- /dev/null
+++ b/rc_society/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "oj_rc_society"
+version.workspace = true
+edition.workspace = true
+repository.workspace = true
+license.workspace = true
+authors.workspace = true
+readme.workspace = true
+
+[dependencies]
+actix-web = { workspace = true, features = [ "secure-cookies" ] }
+actix-session = { version = "0.11", features = [ "cookie-session" ], default-features = false }
+actix-identity = "0.9"
+cookie = "0.16"
+actix-files.workspace = true
+actix-ws = "0.3"
+log.workspace = true
+env_logger.workspace = true
+tokio = { version = "1.43", features = [ "rt-multi-thread" ] }
+#futures.workspace = true
+clap.workspace = true
+handlebars = { version = "6", features = ["dir_source"] }
+oj_rc_core = { version = "*", path = "../rc_core" }
+libfj.workspace = true
+git-version.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+chrono.workspace = true
diff --git a/rc_society/README.md b/rc_society/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/rc_society/run_debug.sh b/rc_society/run_debug.sh
new file mode 100755
index 0000000..a6e46e5
--- /dev/null
+++ b/rc_society/run_debug.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+RUST_LOG=debug cargo run
diff --git a/rc_society/src/api/mod.rs b/rc_society/src/api/mod.rs
new file mode 100644
index 0000000..e69de29
diff --git a/rc_society/src/cli.rs b/rc_society/src/cli.rs
new file mode 100644
index 0000000..aca3dea
--- /dev/null
+++ b/rc_society/src/cli.rs
@@ -0,0 +1,58 @@
+use clap::Parser;
+
+#[derive(Parser, Debug, Clone)]
+#[command(version, about, long_about = None)]
+pub struct CliArgs {
+ /// TCP port on which to accept connections
+ #[arg(short, long, default_value_t = 8002)]
+ pub port: u16,
+
+ /// IP Address on which to accept connections
+ #[arg(long, default_value_t = {"127.0.0.1".to_string()})]
+ pub ip: String,
+
+ /// Assets root
+ #[arg(long, default_value_t = {"../assets/robocraft".to_string()})]
+ pub assets_robocraft: String,
+
+ /// User data root
+ #[arg(long, default_value_t = {"../data/robocraft".to_string()})]
+ pub data_robocraft: String,
+}
+
+impl CliArgs {
+ pub fn get() -> Self {
+ Self::parse()
+ }
+
+ pub fn loaded(&self) -> LoadedArgs {
+ let assets_path = std::path::PathBuf::from(&self.assets_robocraft);
+ let data_path = std::path::PathBuf::from(&self.data_robocraft);
+ let token_path = data_path.join(oj_rc_core::persist::user::TOKEN_SECRET_FILENAME);
+ let secret = std::fs::read(&token_path).expect("Bad token");
+ let cookie_key = if secret.len() < 32 {
+ log::warn!("{} should be >= 32 bytes (extending with zeroes)", token_path.display());
+ let mut secret_ext = secret.clone();
+ while secret_ext.len() < 32 {
+ secret_ext.push(0);
+ }
+ actix_web::cookie::Key::derive_from(&secret_ext)
+ } else {
+ actix_web::cookie::Key::derive_from(&secret)
+ };
+ LoadedArgs {
+ secret: std::sync::Arc::new(secret.clone()),
+ cookie_key,
+ assets: assets_path,
+ data: data_path,
+ }
+ }
+}
+
+#[allow(dead_code)]
+pub struct LoadedArgs {
+ pub secret: std::sync::Arc>,
+ pub cookie_key: actix_web::cookie::Key,
+ pub assets: std::path::PathBuf,
+ pub data: std::path::PathBuf,
+}
diff --git a/rc_society/src/main.rs b/rc_society/src/main.rs
new file mode 100644
index 0000000..1473258
--- /dev/null
+++ b/rc_society/src/main.rs
@@ -0,0 +1,78 @@
+#![forbid(unsafe_code)]
+mod cli;
+mod api;
+mod web;
+
+use actix_web::{App, HttpServer, Responder};
+
+pub static START_TIME: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1);
+
+#[actix_web::get("/version")]
+async fn version_info() -> impl Responder {
+ let name = env!("CARGO_PKG_NAME");
+ let version = env!("CARGO_PKG_VERSION");
+ let git_version = git_version::git_version!(args = ["--always", "--dirty=+"]);
+ let authors = env!("CARGO_PKG_AUTHORS");
+ let license = env!("CARGO_PKG_LICENSE");
+ let repo = env!("CARGO_PKG_REPOSITORY");
+ format!("{} {}:{} by [{}]\n{}\n{}", name, version, git_version, authors, license, repo)
+}
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+ env_logger::init();
+ let cli_args = cli::CliArgs::get();
+
+ let config = oj_rc_core::ConfigImpl::load(&cli_args.assets_robocraft)?;
+
+ let server_settings = actix_web::web::Data::new(>::server_config(&config));
+
+ let users = oj_rc_core::UserImpl::load(&cli_args.data_robocraft, &config).await.expect("Bad user data");
+ let auth_ref = actix_web::web::Data::new(Box::new(users));
+
+ let cli_args2 = actix_web::web::Data::new(cli_args.clone());
+ let loadeds_args = actix_web::web::Data::new(cli_args.loaded());
+
+ let mut handlebars_conf = handlebars::Handlebars::new();
+ let mut dir_conf = handlebars::DirectorySourceOptions::default();
+ dir_conf.tpl_extension = ".html.hbs".to_owned();
+ dir_conf.hidden = false;
+ dir_conf.temporary = false;
+ handlebars_conf
+ .register_templates_directory(
+ std::path::PathBuf::from(&cli_args.assets_robocraft).parent().expect("Bad robocraft asset path").join("templates/rc_society"),
+ dir_conf,
+ )
+ .unwrap();
+ let handlebars_ref = actix_web::web::Data::new(handlebars_conf);
+
+ START_TIME.store(chrono::Utc::now().timestamp(), std::sync::atomic::Ordering::SeqCst);
+
+ HttpServer::new(move || {
+ App::new()
+ .wrap_fn(|req, srv| {
+ use actix_web::dev::Service;
+ log::trace!("Request {} {}", req.method(), req.path());
+ srv.call(req)
+ })
+ .wrap(actix_identity::IdentityMiddleware::default())
+ .wrap(actix_session::SessionMiddleware::new(
+ actix_session::storage::CookieSessionStore::default(),
+ loadeds_args.cookie_key.clone(),
+ ))
+ .app_data(cli_args2.clone())
+ .app_data(loadeds_args.clone())
+ .app_data(handlebars_ref.clone())
+ .app_data(server_settings.clone())
+ .app_data(auth_ref.clone())
+ .service(version_info)
+ .service(web::login::form_submit)
+ .service(web::login::form_load)
+ .service(web::favicon::favicon_standard)
+ .service(web::dashboard::get)
+ .service(web::index::get)
+ })
+ .bind((cli_args.ip, cli_args.port))?
+ .run()
+ .await
+}
diff --git a/rc_society/src/web/dashboard.rs b/rc_society/src/web/dashboard.rs
new file mode 100644
index 0000000..ea760e7
--- /dev/null
+++ b/rc_society/src/web/dashboard.rs
@@ -0,0 +1,65 @@
+use actix_web::{get, web::Data, Responder, HttpRequest};
+use actix_identity::Identity;
+use serde::{Serialize, Deserialize};
+
+const FORM_NAME: &str = "dashboard";
+
+#[derive(Serialize, Deserialize)]
+struct RenderData {
+ // TODO
+ display_name: String,
+ public_id: String,
+ debug: DebugData,
+ perms: PermissionData,
+}
+
+#[derive(Serialize, Deserialize)]
+struct DebugData {
+ user_id: i32,
+ creation_time_unix: i64,
+ creation_time_iso: String,
+}
+
+#[derive(Serialize, Deserialize)]
+struct PermissionData {
+ r#mod: bool,
+ admin: bool,
+ dev: bool,
+ royal: bool,
+ banned: bool,
+}
+
+#[get("/dashboard")]
+pub async fn get(handlebars_ref: Data>, auth: Data>, user_opt: Option, req: HttpRequest) -> Result {
+ match super::try_auth_user(user_opt, auth.as_ref(), &req).await? {
+ super::LoginReturn::AuthFail(resp) => Ok(resp),
+ super::LoginReturn::Success(user) => {
+ // TODO
+ let creation_time = user.creation();
+ let creation_time_chrono = chrono::DateTime::::from_timestamp_secs(creation_time).unwrap_or_default();
+ Ok(super::render_ok(
+ RenderData {
+ display_name: user.display_name().to_owned(),
+ public_id: user.public_id().to_owned(),
+ debug: DebugData {
+ user_id: user.account_id(),
+ creation_time_unix: creation_time,
+ creation_time_iso: creation_time_chrono.to_rfc3339(),
+ },
+ perms: PermissionData {
+ r#mod: user.is_mod(),
+ admin: user.is_admin(),
+ dev: user.is_dev(),
+ royal: user.is_royal(),
+ banned: user.is_banned(),
+ }
+ },
+ handlebars_ref.as_ref(),
+ FORM_NAME,
+ )
+ .respond_to(&req)
+ .map_into_boxed_body()
+ )
+ }
+ }
+}
diff --git a/rc_society/src/web/favicon.rs b/rc_society/src/web/favicon.rs
new file mode 100644
index 0000000..0fef295
--- /dev/null
+++ b/rc_society/src/web/favicon.rs
@@ -0,0 +1,11 @@
+use actix_web::{get, web::Data, Responder};
+
+async fn favicon_impl(cli_args: Data) -> impl Responder {
+ let path = std::path::PathBuf::from(&cli_args.assets).join("favicon.jpg");
+ actix_files::NamedFile::open_async(path).await
+}
+
+#[get("/favicon.ico")]
+pub async fn favicon_standard(cli_args: Data) -> impl Responder {
+ favicon_impl(cli_args).await
+}
diff --git a/rc_society/src/web/index.rs b/rc_society/src/web/index.rs
new file mode 100644
index 0000000..b829bce
--- /dev/null
+++ b/rc_society/src/web/index.rs
@@ -0,0 +1,77 @@
+use actix_web::{get, web::Data, Responder, HttpRequest};
+use actix_identity::Identity;
+use serde::{Serialize, Deserialize};
+
+pub const FORM_NAME: &str = "index";
+
+#[derive(Serialize, Deserialize)]
+struct RenderData {
+ is_logged_in: bool,
+ display_name: Option,
+ server: ServerDetails,
+}
+
+#[derive(Serialize, Deserialize)]
+struct ServerDetails {
+ domain: String,
+ cdn: String,
+ auth: String,
+ min_version: i32,
+ server_version: String,
+ start_time_iso: String,
+ start_time_unix: i64,
+}
+
+fn server_details(conf: &oj_rc_core::persist::config::ServerConfig) -> ServerDetails {
+ let start_time_unix = crate::START_TIME.load(std::sync::atomic::Ordering::Relaxed);
+ let start_time_chrono = chrono::DateTime::::from_timestamp_secs(start_time_unix).unwrap_or_default();
+ let version = env!("CARGO_PKG_VERSION");
+ let git_version = git_version::git_version!(args = ["--always", "--dirty=+"]);
+ let server_version = format!("{}:{}", version, git_version);
+ ServerDetails {
+ domain: conf.domain.clone(),
+ cdn: conf.cdn_url.clone(),
+ auth: conf.auth_url.clone(),
+ min_version: conf.minimum_version,
+ server_version,
+ start_time_unix,
+ start_time_iso: start_time_chrono.to_rfc3339(),
+ }
+}
+
+#[get("/")]
+pub async fn get(handlebars_ref: Data>, auth: Data>, server_config: Data, user_opt: Option, req: HttpRequest) -> Result {
+ let server_info = server_details(server_config.as_ref());
+ if let Some(user) = user_opt {
+ match super::try_auth_user(Some(user), auth.as_ref(), &req).await? {
+ super::LoginReturn::AuthFail(resp) => Ok(resp),
+ super::LoginReturn::Success(user) => {
+ Ok(super::render_ok(
+ RenderData {
+ is_logged_in: true,
+ display_name: Some(user.display_name().to_owned()),
+ server: server_info,
+ },
+ handlebars_ref.as_ref(),
+ FORM_NAME,
+ )
+ .respond_to(&req)
+ .map_into_boxed_body()
+ )
+ }
+ }
+ } else {
+ Ok(super::render_ok(
+ RenderData {
+ is_logged_in: false,
+ display_name: None,
+ server: server_info,
+ },
+ handlebars_ref.as_ref(),
+ FORM_NAME,
+ )
+ .respond_to(&req)
+ .map_into_boxed_body()
+ )
+ }
+}
diff --git a/rc_society/src/web/login.rs b/rc_society/src/web/login.rs
new file mode 100644
index 0000000..f700dbe
--- /dev/null
+++ b/rc_society/src/web/login.rs
@@ -0,0 +1,66 @@
+use actix_web::{get, post, web::{Data, Form, Redirect}, Responder, HttpRequest, HttpMessage};
+use actix_identity::Identity;
+use serde::{Serialize, Deserialize};
+
+const FORM_NAME: &str = "login";
+
+#[derive(Serialize, Deserialize, Default)]
+struct LoginForm {
+ username: String,
+ password: String,
+}
+
+#[post("/login")]
+pub async fn form_submit(form: Form, auth: Data>, handlebars_ref: Data>, req: HttpRequest) -> Result {
+ use oj_rc_core::UserAuthenticator;
+ let auth_result = auth.login(oj_rc_core::persist::user::UserAuthInfo::Username {
+ username: form.username.to_owned(),
+ password: form.password.to_owned(),
+ }).await;
+ match auth_result {
+ Ok(user) => {
+ let resp = Redirect::to("/dashboard")
+ .respond_to(&req)
+ .map_into_boxed_body();
+ Identity::login(&req.extensions(), user.response.token)?;
+ Ok(resp)
+ },
+ Err(e) => {
+ Ok(super::render_err(form.0, e.message, handlebars_ref.as_ref(), FORM_NAME)
+ .respond_to(&req)
+ .map_into_boxed_body())
+ }
+ }
+}
+
+#[get("/login")]
+pub async fn form_load(handlebars_ref: Data>, auth: Data>, user_opt: Option, req: HttpRequest) -> Result {
+ if let Some(user) = user_opt {
+ let user_id = user.id()?;
+ use oj_rc_core::UserAuthenticator;
+ if auth.verify(user_id).await.is_ok() {
+ Ok(Redirect::to("/dashboard")
+ .respond_to(&req)
+ .map_into_boxed_body())
+ } else {
+ Ok(super::render_err(
+ LoginForm::default(),
+ "Invalid login token".to_owned(),
+ handlebars_ref.as_ref(),
+ FORM_NAME,
+ )
+ .respond_to(&req)
+ .map_into_boxed_body()
+ )
+ }
+ } else {
+ Ok(super::render_ok(
+ LoginForm::default(),
+ handlebars_ref.as_ref(),
+ FORM_NAME,
+ )
+ .respond_to(&req)
+ .map_into_boxed_body()
+ )
+ }
+}
diff --git a/rc_society/src/web/mod.rs b/rc_society/src/web/mod.rs
new file mode 100644
index 0000000..2422763
--- /dev/null
+++ b/rc_society/src/web/mod.rs
@@ -0,0 +1,71 @@
+pub mod dashboard;
+pub mod login;
+pub mod favicon;
+pub mod index;
+
+use serde::Serialize;
+use actix_web::{web::{Html, Redirect}, Responder};
+
+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)
+}
+
+#[derive(Serialize)]
+struct Context {
+ form: T,
+ error: Option,
+ version: String,
+ source_url: String,
+}
+
+fn render_ok(form: T, renderer: &handlebars::Handlebars<'_>, form_name: &str) -> Html {
+ let rendered = renderer.render(form_name, &Context {
+ form,
+ error: None,
+ version: version_string(),
+ source_url: env!("CARGO_PKG_REPOSITORY").to_owned(),
+ }).unwrap();
+ Html::new(rendered)
+}
+
+fn render_err(form: T, error: String , renderer: &handlebars::Handlebars<'_>, form_name: &str) -> Html {
+ let rendered = renderer.render(form_name, &Context {
+ form,
+ error: Some(error),
+ version: version_string(),
+ source_url: env!("CARGO_PKG_REPOSITORY").to_owned(),
+ }).unwrap();
+ Html::new(rendered)
+}
+
+enum LoginReturn {
+ Success(Box),
+ AuthFail(actix_web::HttpResponse),
+}
+
+async fn try_auth_user(user_opt: Option, auth: &oj_rc_core::UserImpl, req: &actix_web::HttpRequest) -> Result {
+ if let Some(user) = user_opt {
+ let user_id = user.id()?;
+ match >::web_authenticate(auth, user_id.clone()).await {
+ Ok(user) => Ok(LoginReturn::Success(user)),
+ Err(e) => {
+ log::warn!("Failed to login with token {}: {} ({:?})", user_id, e.message, e.code);
+ Ok(LoginReturn::AuthFail(
+ Redirect::to("/login")
+ .respond_to(req)
+ .map_into_boxed_body()
+ ))
+ }
+ }
+ } else {
+ Ok(LoginReturn::AuthFail(
+ Redirect::to("/login")
+ .respond_to(req)
+ .map_into_boxed_body()
+ ))
+ }
+}