diff --git a/rc_auth/src/robocraft/email.rs b/rc_auth/src/robocraft/email.rs index a7f700f..4b9af31 100644 --- a/rc_auth/src/robocraft/email.rs +++ b/rc_auth/src/robocraft/email.rs @@ -3,29 +3,20 @@ use actix_web::{post, web::{Data, Json}}; #[post("/authenticate/email/game")] pub async fn email_password_auth(body: Json, config: Data) -> Result, super::ErrorTy> { - if body.display_name.is_none() { + if body.email_address.is_empty() { return Err(super::ErrorTy::from_err(oj_rc_core::persist::user::AuthError { - message: "Missing display_name".to_owned(), + message: "Missing email_address".to_owned(), code: oj_rc_core::data::error_codes::AuthErrorCode::BadCredentials, })); } - let display_name = body.display_name.clone().unwrap(); - log::info!("Authenticating {} email user {}", body.target, display_name); - let payload = libfj::robocraft::TokenPayload { - public_id: display_name.clone(), - display_name: display_name.clone(), - robocraft_name: display_name.clone(), - email_address: body.email_address.clone(), - email_verified: true, - flags: Vec::new(), - }; - let user_info = oj_rc_core::persist::user::UserInfo { - payload, - extra: oj_rc_core::persist::user::ExtraUserInfo::Email { password: body.password.clone() }, + log::info!("Authenticating {} email user {}", body.target, body.email_address); + let user_info = oj_rc_core::persist::user::UserAuthInfo::Email { + email: body.email_address.clone(), + password: body.password.clone() }; let response = config.account_provider.login(user_info).await .map_err(|e| { - log::error!("Failed to authenticate {} user {}: {}", body.target, display_name, e.message); + log::error!("Failed to authenticate {} email user {}: {}", body.target, body.email_address, e.message); super::ErrorTy::from_err(e) })?; Ok(Json(response.response)) diff --git a/rc_auth/src/robocraft/steam.rs b/rc_auth/src/robocraft/steam.rs index c76937b..bacf888 100644 --- a/rc_auth/src/robocraft/steam.rs +++ b/rc_auth/src/robocraft/steam.rs @@ -44,18 +44,7 @@ pub async fn steam_auth(body: Json code: oj_rc_core::data::error_codes::AuthErrorCode::BadCredentials, }))?; log::info!("Authenticating {} steam user {}", body.target, steam_id); - let payload = libfj::robocraft::TokenPayload { - public_id: steam_id.to_string(), - display_name: steam_id.to_string(), - robocraft_name: steam_id.to_string(), - email_address: format!("{}.rc.steam@ngram.ca", steam_id), - email_verified: true, - flags: Vec::new(), - }; - let user_info = oj_rc_core::persist::user::UserInfo { - payload, - extra: oj_rc_core::persist::user::ExtraUserInfo::Steam { id: steam_id }, - }; + let user_info = oj_rc_core::persist::user::UserAuthInfo::Steam { id: steam_id }; let response = config.account_provider.login(user_info).await .map_err(|e| { log::error!("Failed to authenticate {} steam user {}: {}", body.target, steam_id, e.message); diff --git a/rc_auth/src/robocraft/username.rs b/rc_auth/src/robocraft/username.rs index 1928df8..955bb71 100644 --- a/rc_auth/src/robocraft/username.rs +++ b/rc_auth/src/robocraft/username.rs @@ -11,17 +11,9 @@ pub async fn user_password_auth(body: Json super::ConfigProvider 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(), diff --git a/rc_core/src/persist/config/traits.rs b/rc_core/src/persist/config/traits.rs index 6e14ed2..d3b718f 100644 --- a/rc_core/src/persist/config/traits.rs +++ b/rc_core/src/persist/config/traits.rs @@ -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, diff --git a/rc_core/src/persist/settings.rs b/rc_core/src/persist/settings.rs index 750b1f5..f75c558 100644 --- a/rc_core/src/persist/settings.rs +++ b/rc_core/src/persist/settings.rs @@ -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() } diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 99b0534..5f138b3 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -10,6 +10,7 @@ pub struct AccountProvider { fake_players: std::sync::Arc>, filler_players: std::sync::Arc>, auto_signups: bool, + domain: std::sync::Arc, cdn: std::sync::Arc, auth: std::sync::Arc, intercom: std::sync::Arc, @@ -33,6 +34,7 @@ impl AccountProvider { fake_players: std::sync::Arc::new(>::fake_players(conf)), filler_players: std::sync::Arc::new(>::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 super::UserProvider 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::(&token.token, &secret, &validation).map_err(|e| super::AuthError { + let token_data = jsonwebtoken::decode::(&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 super::UserProvider 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 super::UserProvider for AccountProvider { #[async_trait::async_trait] impl super::UserAuthenticator for AccountProvider { - async fn login(&self, info: super::UserInfo) -> Result { + async fn login(&self, info: super::UserAuthInfo) -> Result { //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| { diff --git a/rc_core/src/persist/user/initial_data.rs b/rc_core/src/persist/user/initial_data.rs index 2baa2b9..377ee01 100644 --- a/rc_core/src/persist/user/initial_data.rs +++ b/rc_core/src/persist/user/initial_data.rs @@ -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(®_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(()) } diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs index 7b2bdb8..8cda28a 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, 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; diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 4bbc18c..95ef9ce 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -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 { #[async_trait::async_trait] pub trait UserAuthenticator { - async fn login(&self, info: UserInfo) -> Result; + async fn login(&self, info: UserAuthInfo) -> Result; async fn user_exists(&self, user: UserId) -> Result; async fn register(&self, info: RegistrationInfo) -> Result; }