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

View File

@@ -3,29 +3,20 @@ use actix_web::{post, web::{Data, Json}};
#[post("/authenticate/email/game")] #[post("/authenticate/email/game")]
pub async fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: Data<super::RcConfig>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, super::ErrorTy> { pub async fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: Data<super::RcConfig>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, 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 { 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, code: oj_rc_core::data::error_codes::AuthErrorCode::BadCredentials,
})); }));
} }
let display_name = body.display_name.clone().unwrap(); log::info!("Authenticating {} email user {}", body.target, body.email_address);
log::info!("Authenticating {} email user {}", body.target, display_name); let user_info = oj_rc_core::persist::user::UserAuthInfo::Email {
let payload = libfj::robocraft::TokenPayload { email: body.email_address.clone(),
public_id: display_name.clone(), password: body.password.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() },
}; };
let response = config.account_provider.login(user_info).await let response = config.account_provider.login(user_info).await
.map_err(|e| { .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) super::ErrorTy::from_err(e)
})?; })?;
Ok(Json(response.response)) Ok(Json(response.response))

View File

@@ -44,18 +44,7 @@ pub async fn steam_auth(body: Json<libfj::robocraft::SteamAuthenticationPayload>
code: oj_rc_core::data::error_codes::AuthErrorCode::BadCredentials, code: oj_rc_core::data::error_codes::AuthErrorCode::BadCredentials,
}))?; }))?;
log::info!("Authenticating {} steam user {}", body.target, steam_id); log::info!("Authenticating {} steam user {}", body.target, steam_id);
let payload = libfj::robocraft::TokenPayload { let user_info = oj_rc_core::persist::user::UserAuthInfo::Steam { id: steam_id };
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 response = config.account_provider.login(user_info).await let response = config.account_provider.login(user_info).await
.map_err(|e| { .map_err(|e| {
log::error!("Failed to authenticate {} steam user {}: {}", body.target, steam_id, e.message); log::error!("Failed to authenticate {} steam user {}: {}", body.target, steam_id, e.message);

View File

@@ -11,17 +11,9 @@ pub async fn user_password_auth(body: Json<libfj::robocraft::EmailUserAuthentica
} }
let display_name = body.display_name.clone().unwrap(); let display_name = body.display_name.clone().unwrap();
log::info!("Authenticating {} user {}", body.target, display_name); log::info!("Authenticating {} user {}", body.target, display_name);
let payload = libfj::robocraft::TokenPayload { let user_info = oj_rc_core::persist::user::UserAuthInfo::Username {
public_id: display_name.clone(), username: display_name.clone(),
display_name: display_name.clone(), password: body.password.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::Username { password: body.password.clone() },
}; };
let response = config.account_provider.login(user_info).await let response = config.account_provider.login(user_info).await
.map_err(|e| { .map_err(|e| {

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 factory;
pub mod cubes; 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(), database: self.settings.server.database.clone(),
auto_signup: self.settings.server.auto_signup, auto_signup: self.settings.server.auto_signup,
queue_mode: super::QueueChangeMode::from_persist(self.settings.server.queue_mode.clone()), 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(), cdn_url: self.settings.server.cdn_url.trim_end_matches('/').to_owned(),
auth_url: self.settings.server.auth_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(), 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 database: String,
pub auto_signup: bool, pub auto_signup: bool,
pub queue_mode: QueueChangeMode, pub queue_mode: QueueChangeMode,
pub domain: String,
pub cdn_url: String, pub cdn_url: String,
pub auth_url: String, pub auth_url: String,
pub intercom_url: String, pub intercom_url: String,

View File

@@ -84,6 +84,8 @@ pub struct ServerSettings {
pub auto_signup: bool, pub auto_signup: bool,
#[serde(default)] #[serde(default)]
pub queue_mode: QueueMode, pub queue_mode: QueueMode,
#[serde(default = "default_domain_root")]
pub domain: String,
#[serde(default = "default_cdn_root_url")] #[serde(default = "default_cdn_root_url")]
pub cdn_url: String, pub cdn_url: String,
#[serde(default = "default_auth_root_url")] #[serde(default = "default_auth_root_url")]
@@ -119,6 +121,7 @@ fn default_server_conf() -> ServerSettings {
database: default_db_conn(), database: default_db_conn(),
auto_signup: false, auto_signup: false,
queue_mode: QueueMode::Notify, queue_mode: QueueMode::Notify,
domain: default_domain_root(),
cdn_url: default_cdn_root_url(), cdn_url: default_cdn_root_url(),
auth_url: default_auth_root_url(), auth_url: default_auth_root_url(),
intercom_url: default_intercom_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 { fn default_cdn_root_url() -> String {
"http://127.0.0.1:8010".to_owned() "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>>, fake_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
filler_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>, filler_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
auto_signups: bool, auto_signups: bool,
domain: std::sync::Arc<String>,
cdn: std::sync::Arc<String>, cdn: std::sync::Arc<String>,
auth: std::sync::Arc<String>, auth: std::sync::Arc<String>,
intercom: 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)), 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)), filler_players: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::filler_players(conf)),
auto_signups: server_settings.auto_signup, auto_signups: server_settings.auto_signup,
domain: std::sync::Arc::new(server_settings.domain),
cdn: std::sync::Arc::new(server_settings.cdn_url), cdn: std::sync::Arc::new(server_settings.cdn_url),
auth: std::sync::Arc::new(server_settings.auth_url), auth: std::sync::Arc::new(server_settings.auth_url),
intercom: std::sync::Arc::new(server_settings.intercom_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 secret = jsonwebtoken::DecodingKey::from_secret(&self.secret);
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256); let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
validation.set_required_spec_claims::<&str>(&[]); 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(), message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::BadCredentials, 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(), message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::Unknown, 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, 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())?; //let account_info = AccountInfo::load(&new_root).map_err(|e| e.to_string())?;
Ok(Box::new(UserData { Ok(Box::new(UserData {
account: user_info, account: user_info,
@@ -171,13 +176,13 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
#[async_trait::async_trait] #[async_trait::async_trait]
impl super::UserAuthenticator for AccountProvider { 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 new_root = self.root.join(&info.payload.public_id);
let is_new_user; let is_new_user;
let user_opt = match &info.extra { let user_opt = match &info {
super::ExtraUserInfo::Steam { id } => self.db.user_by_steam_id(*id).await, super::UserAuthInfo::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::UserAuthInfo::Email { email, .. } => self.db.user_by_email(email.to_owned()).await,
super::ExtraUserInfo::Username { .. } => self.db.user_by_display_name(info.payload.display_name.clone()).await, super::UserAuthInfo::Username { username, .. } => self.db.user_by_display_name(username.to_owned()).await,
}.map_err(|e| super::AuthError { }.map_err(|e| super::AuthError {
message: e.to_string(), message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::BadCredentials, code: crate::data::error_codes::AuthErrorCode::BadCredentials,
@@ -188,17 +193,23 @@ impl super::UserAuthenticator for AccountProvider {
} else { } else {
is_new_user = true; is_new_user = true;
if self.auto_signups { if self.auto_signups {
log::info!("New user {}", info.payload.public_id); log::info!("New user {}", info.display_id());
super::setup_new_user(&info, &self.db).await.map_err(|e| super::AuthError { 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(), message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::Unknown, 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(), message: e.to_string(),
code: crate::data::error_codes::AuthErrorCode::Unknown, code: crate::data::error_codes::AuthErrorCode::Unknown,
})?.unwrap() })?.unwrap()
} else { } 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 { return Err(super::AuthError {
message: "User not found".to_owned(), message: "User not found".to_owned(),
code: crate::data::error_codes::AuthErrorCode::BadCredentials, 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(); let override_password = user_info.password.is_empty() && user_info.steam_id.is_none();
match info.extra { match &info {
super::ExtraUserInfo::Steam { id } => { super::UserAuthInfo::Steam { id } => {
let id_str = id.to_string(); let id_str = id.to_string();
if let Some(expected_steam_id) = user_info.steam_id { if let Some(expected_steam_id) = user_info.steam_id {
if expected_steam_id != id_str { if expected_steam_id != id_str {
@@ -223,8 +234,8 @@ impl super::UserAuthenticator for AccountProvider {
}); });
} }
}, },
super::ExtraUserInfo::Email { password } super::UserAuthInfo::Email { password, .. }
| super::ExtraUserInfo::Username { password } => { | super::UserAuthInfo::Username { password, .. } => {
use argon2::password_hash::PasswordHasher; use argon2::password_hash::PasswordHasher;
let argon2_algo = argon2::Argon2::default(); let argon2_algo = argon2::Argon2::default();
if override_password { if override_password {
@@ -276,12 +287,32 @@ impl super::UserAuthenticator for AccountProvider {
alg: jsonwebtoken::Algorithm::HS256, alg: jsonwebtoken::Algorithm::HS256,
..Default::default() ..Default::default()
}; };
let mut payload = info.payload;
payload.public_id = user_info.public_id; let login_method = match &info {
payload.display_name = user_info.display_name.clone(); super::UserAuthInfo::Steam { .. } => crate::auth::LoginMethod::Steam,
payload.robocraft_name = user_info.display_name; super::UserAuthInfo::Email { .. } => crate::auth::LoginMethod::Email,
payload.email_address = user_info.email; super::UserAuthInfo::Username { .. } => crate::auth::LoginMethod::Username,
payload.email_verified = true; };
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 secret = jsonwebtoken::EncodingKey::from_secret(&self.secret);
let token = jsonwebtoken::encode(&header, &payload, &secret) let token = jsonwebtoken::encode(&header, &payload, &secret)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {

View File

@@ -4,22 +4,22 @@ fn current_unix_time() -> i64 {
chrono::Utc::now().timestamp() 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 { let reg_info = super::RegistrationInfo {
display_name: user.payload.display_name.clone(), display_name: username,
password: if let super::ExtraUserInfo::Email { password } | super::ExtraUserInfo::Username { password } = &user.extra { password: if let super::UserAuthInfo::Email { password, .. } | super::UserAuthInfo::Username { password, .. } = &user {
password.to_owned() password.to_owned()
} else { } else {
"".to_owned() "".to_owned()
}, },
email: None, 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?; register_new_user(&reg_info, db).await?;
Ok(()) 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); /*let ref_path = new_dir.as_ref().parent().unwrap().join(REFERENCE_DIR);
if !ref_path.exists() { if !ref_path.exists() {
log::debug!("Initialising reference directory {}", ref_path.display()); 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()); log::debug!("Copying reference directory for new user: {}", new_dir.as_ref().display());
so::copy_dir_all(ref_path, new_dir)?;*/ so::copy_dir_all(ref_path, new_dir)?;*/
build_new_account_data(user, db).await?; build_new_account_data(user, username, db).await?;
Ok(()) Ok(())
} }

View File

@@ -11,7 +11,7 @@ mod inventory;
pub use inventory::{UnlockedParts, UnlockOverride}; pub use inventory::{UnlockedParts, UnlockOverride};
mod traits; 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 mod intercom;
pub use intercom::generate_token as generate_intercom_token; pub use intercom::generate_token as generate_intercom_token;

View File

@@ -6,23 +6,35 @@ pub struct UserToken {
pub refresh_token: String, pub refresh_token: String,
} }
pub struct UserInfo { /*pub struct UserInfo {
pub payload: libfj::robocraft::TokenPayload, //pub payload: libfj::robocraft::TokenPayload,
pub extra: ExtraUserInfo, pub extra: ExtraUserInfo,
} }*/
pub enum ExtraUserInfo { pub enum UserAuthInfo {
Steam { Steam {
id: u64, id: u64,
}, },
Username { Username {
username: String,
password: String, password: String,
}, },
Email { Email {
email: String,
password: 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 { pub enum UserId {
SteamId(u64), SteamId(u64),
Email(String), Email(String),
@@ -55,7 +67,7 @@ pub trait UserProvider<C> {
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait UserAuthenticator { 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 user_exists(&self, user: UserId) -> Result<bool, String>;
async fn register(&self, info: RegistrationInfo) -> Result<i32, String>; async fn register(&self, info: RegistrationInfo) -> Result<i32, String>;
} }