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

Simplify login data struct, add experimental login flags

This commit is contained in:
NG (Graham)
2026-02-14 17:35:09 -05:00
parent b9a6fae1e0
commit 872f224af6
13 changed files with 119 additions and 72 deletions

2
rc_core/src/auth/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
mod token;
pub use token::{Token, LoginMethod};

19
rc_core/src/auth/token.rs Normal file
View File

@@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct Token {
#[serde(flatten)]
pub client_details: libfj::robocraft::TokenPayload,
pub federate: bool,
pub auth_time: i64,
pub qualified_name: String,
pub login_method: LoginMethod,
}
#[derive(Serialize, Deserialize, Clone, Copy)]
pub enum LoginMethod {
Steam,
DisplayName,
Username,
Email,
}

View File

@@ -13,3 +13,5 @@ pub mod polariton;
pub mod factory;
pub mod cubes;
mod auth;

View File

@@ -293,6 +293,7 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
database: self.settings.server.database.clone(),
auto_signup: self.settings.server.auto_signup,
queue_mode: super::QueueChangeMode::from_persist(self.settings.server.queue_mode.clone()),
domain: self.settings.server.domain.to_owned(),
cdn_url: self.settings.server.cdn_url.trim_end_matches('/').to_owned(),
auth_url: self.settings.server.auth_url.trim_end_matches('/').to_owned(),
intercom_url: self.settings.server.intercom_url.trim_end_matches('/').to_owned(),

View File

@@ -90,6 +90,7 @@ pub struct ServerConfig {
pub database: String,
pub auto_signup: bool,
pub queue_mode: QueueChangeMode,
pub domain: String,
pub cdn_url: String,
pub auth_url: String,
pub intercom_url: String,

View File

@@ -84,6 +84,8 @@ pub struct ServerSettings {
pub auto_signup: bool,
#[serde(default)]
pub queue_mode: QueueMode,
#[serde(default = "default_domain_root")]
pub domain: String,
#[serde(default = "default_cdn_root_url")]
pub cdn_url: String,
#[serde(default = "default_auth_root_url")]
@@ -119,6 +121,7 @@ fn default_server_conf() -> ServerSettings {
database: default_db_conn(),
auto_signup: false,
queue_mode: QueueMode::Notify,
domain: default_domain_root(),
cdn_url: default_cdn_root_url(),
auth_url: default_auth_root_url(),
intercom_url: default_intercom_root_url(),
@@ -130,6 +133,10 @@ fn default_server_conf() -> ServerSettings {
}
}
fn default_domain_root() -> String {
"127.0.0.1".to_owned()
}
fn default_cdn_root_url() -> String {
"http://127.0.0.1:8010".to_owned()
}

View File

@@ -10,6 +10,7 @@ pub struct AccountProvider {
fake_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
filler_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
auto_signups: bool,
domain: std::sync::Arc<String>,
cdn: std::sync::Arc<String>,
auth: std::sync::Arc<String>,
intercom: std::sync::Arc<String>,
@@ -33,6 +34,7 @@ impl AccountProvider {
fake_players: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::fake_players(conf)),
filler_players: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::filler_players(conf)),
auto_signups: server_settings.auto_signup,
domain: std::sync::Arc::new(server_settings.domain),
cdn: std::sync::Arc::new(server_settings.cdn_url),
auth: std::sync::Arc::new(server_settings.auth_url),
intercom: std::sync::Arc::new(server_settings.intercom_url),
@@ -86,11 +88,12 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
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::<libfj::robocraft::TokenPayload>(&token.token, &secret, &validation).map_err(|e| super::AuthError {
let token_data = jsonwebtoken::decode::<crate::auth::Token>(&token.token, &secret, &validation).map_err(|e| super::AuthError {
message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
})?;
let user_info = if let Some(user_info) = self.db.user_by_any_unique_id(token.uuid.clone()).await.map_err(|e| super::AuthError {
let display_name = token_data.claims.client_details.display_name.clone();
let user_info = if let Some(user_info) = self.db.user_by_display_name(display_name.clone()).await.map_err(|e| super::AuthError {
message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::Unknown,
})? {
@@ -112,6 +115,8 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
});
};
#[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 {
account: user_info,
@@ -171,13 +176,13 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
#[async_trait::async_trait]
impl super::UserAuthenticator for AccountProvider {
async fn login(&self, info: super::UserInfo) -> Result<super::UserLoginInfo, super::AuthError> {
async fn login(&self, info: super::UserAuthInfo) -> Result<super::UserLoginInfo, super::AuthError> {
//let new_root = self.root.join(&info.payload.public_id);
let is_new_user;
let user_opt = match &info.extra {
super::ExtraUserInfo::Steam { id } => self.db.user_by_steam_id(*id).await,
super::ExtraUserInfo::Email { .. } => self.db.user_by_email(info.payload.email_address.clone()).await,
super::ExtraUserInfo::Username { .. } => self.db.user_by_display_name(info.payload.display_name.clone()).await,
let user_opt = match &info {
super::UserAuthInfo::Steam { id } => self.db.user_by_steam_id(*id).await,
super::UserAuthInfo::Email { email, .. } => self.db.user_by_email(email.to_owned()).await,
super::UserAuthInfo::Username { username, .. } => self.db.user_by_display_name(username.to_owned()).await,
}.map_err(|e| super::AuthError {
message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
@@ -188,17 +193,23 @@ impl super::UserAuthenticator for AccountProvider {
} else {
is_new_user = true;
if self.auto_signups {
log::info!("New user {}", info.payload.public_id);
super::setup_new_user(&info, &self.db).await.map_err(|e| super::AuthError {
log::info!("New user {}", info.display_id());
let auto_name = match &info {
super::UserAuthInfo::Steam { id } => id.to_string(),
super::UserAuthInfo::Email { email, .. } => email.to_owned(),
super::UserAuthInfo::Username { username, .. } => username.to_owned(),
};
super::setup_new_user(&info, auto_name.clone(), &self.db).await.map_err(|e| super::AuthError {
message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::Unknown,
})?;
self.db.user_by_display_name(info.payload.display_name.clone()).await.map_err(|e| super::AuthError {
self.db.user_by_display_name(auto_name).await.map_err(|e| super::AuthError {
message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::Unknown,
})?.unwrap()
} else {
log::info!("Rejecting user sign-in for `{}` (set settings.server.auto_signup=true to disable this behaviour)", info.payload.public_id);
log::info!("Rejecting user sign-in for `{}` (set settings.server.auto_signup=true to disable this behaviour)", info.display_id());
return Err(super::AuthError {
message: "User not found".to_owned(),
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
@@ -206,8 +217,8 @@ impl super::UserAuthenticator for AccountProvider {
}
};
let override_password = user_info.password.is_empty() && user_info.steam_id.is_none();
match info.extra {
super::ExtraUserInfo::Steam { id } => {
match &info {
super::UserAuthInfo::Steam { id } => {
let id_str = id.to_string();
if let Some(expected_steam_id) = user_info.steam_id {
if expected_steam_id != id_str {
@@ -223,8 +234,8 @@ impl super::UserAuthenticator for AccountProvider {
});
}
},
super::ExtraUserInfo::Email { password }
| super::ExtraUserInfo::Username { password } => {
super::UserAuthInfo::Email { password, .. }
| super::UserAuthInfo::Username { password, .. } => {
use argon2::password_hash::PasswordHasher;
let argon2_algo = argon2::Argon2::default();
if override_password {
@@ -276,12 +287,32 @@ impl super::UserAuthenticator for AccountProvider {
alg: jsonwebtoken::Algorithm::HS256,
..Default::default()
};
let mut payload = info.payload;
payload.public_id = user_info.public_id;
payload.display_name = user_info.display_name.clone();
payload.robocraft_name = user_info.display_name;
payload.email_address = user_info.email;
payload.email_verified = true;
let login_method = match &info {
super::UserAuthInfo::Steam { .. } => crate::auth::LoginMethod::Steam,
super::UserAuthInfo::Email { .. } => crate::auth::LoginMethod::Email,
super::UserAuthInfo::Username { .. } => crate::auth::LoginMethod::Username,
};
let client_details = libfj::robocraft::TokenPayload {
public_id: user_info.public_id.clone(),
display_name: user_info.display_name.clone(),
robocraft_name: user_info.display_name,
email_address: user_info.email,
email_verified: true,
flags: vec![
"federated=false".to_owned(),
],
};
let payload = crate::auth::Token {
client_details,
federate: false,
auth_time: chrono::Utc::now().timestamp(),
qualified_name: format!("{}@{}", user_info.public_id, self.domain),
login_method,
};
#[cfg(debug_assertions)]
log::debug!("Token payload\n{}", serde_json::to_string_pretty(&payload).unwrap());
let secret = jsonwebtoken::EncodingKey::from_secret(&self.secret);
let token = jsonwebtoken::encode(&header, &payload, &secret)
.unwrap_or_else(|e| {

View File

@@ -4,22 +4,22 @@ fn current_unix_time() -> i64 {
chrono::Utc::now().timestamp()
}
async fn build_new_account_data(user: &super::UserInfo, db: &oj_rc_database::Database) -> Result<(), oj_rc_database::sea_orm::DbErr> {
async fn build_new_account_data(user: &super::UserAuthInfo, username: String, db: &oj_rc_database::Database) -> Result<(), oj_rc_database::sea_orm::DbErr> {
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 {
display_name: username,
password: if let super::UserAuthInfo::Email { password, .. } | super::UserAuthInfo::Username { password, .. } = &user {
password.to_owned()
} else {
"".to_owned()
},
email: None,
steam_id: if let super::ExtraUserInfo::Steam { id } = &user.extra { Some(*id) } else { None },
steam_id: if let super::UserAuthInfo::Steam { id } = &user { Some(*id) } else { None },
};
register_new_user(&reg_info, db).await?;
Ok(())
}
pub async fn setup_new_user(user: &super::UserInfo, db: &oj_rc_database::Database) -> Result<(), oj_rc_database::sea_orm::DbErr> {
pub async fn setup_new_user(user: &super::UserAuthInfo, username: String, db: &oj_rc_database::Database) -> Result<(), oj_rc_database::sea_orm::DbErr> {
/*let ref_path = new_dir.as_ref().parent().unwrap().join(REFERENCE_DIR);
if !ref_path.exists() {
log::debug!("Initialising reference directory {}", ref_path.display());
@@ -27,7 +27,7 @@ pub async fn setup_new_user(user: &super::UserInfo, db: &oj_rc_database::Databas
}
log::debug!("Copying reference directory for new user: {}", new_dir.as_ref().display());
so::copy_dir_all(ref_path, new_dir)?;*/
build_new_account_data(user, db).await?;
build_new_account_data(user, username, db).await?;
Ok(())
}

View File

@@ -11,7 +11,7 @@ mod inventory;
pub use inventory::{UnlockedParts, UnlockOverride};
mod traits;
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, 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, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser};
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, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser};
pub mod intercom;
pub use intercom::generate_token as generate_intercom_token;

View File

@@ -6,23 +6,35 @@ pub struct UserToken {
pub refresh_token: String,
}
pub struct UserInfo {
pub payload: libfj::robocraft::TokenPayload,
/*pub struct UserInfo {
//pub payload: libfj::robocraft::TokenPayload,
pub extra: ExtraUserInfo,
}
}*/
pub enum ExtraUserInfo {
pub enum UserAuthInfo {
Steam {
id: u64,
},
Username {
username: String,
password: String,
},
Email {
email: String,
password: String,
}
}
impl UserAuthInfo {
pub(super) fn display_id(&self) -> String {
match self {
Self::Steam { id } => format!("steamID:{}", id),
Self::Username { username, .. } => format!("username:{}", username),
Self::Email { email, .. } => format!("email:{}", email),
}
}
}
pub enum UserId {
SteamId(u64),
Email(String),
@@ -55,7 +67,7 @@ pub trait UserProvider<C> {
#[async_trait::async_trait]
pub trait UserAuthenticator {
async fn login(&self, info: UserInfo) -> Result<UserLoginInfo, AuthError>;
async fn login(&self, info: UserAuthInfo) -> Result<UserLoginInfo, AuthError>;
async fn user_exists(&self, user: UserId) -> Result<bool, String>;
async fn register(&self, info: RegistrationInfo) -> Result<i32, String>;
}