mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add real email-based authentication, fix RC account auth to try all 3 login options when it doesn't know #20
This commit is contained in:
31
auth/src/robocraft/email.rs
Normal file
31
auth/src/robocraft/email.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use rc_core::UserAuthenticator;
|
||||||
|
use rocket::{post, routes, serde::json::Json, http::Status, State};
|
||||||
|
|
||||||
|
#[post("/authenticate/email/game", data = "<body>")]
|
||||||
|
pub async fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
||||||
|
log::info!("Authenticating {} user {}", body.target, body.display_name);
|
||||||
|
let payload = libfj::robocraft::TokenPayload {
|
||||||
|
public_id: body.display_name.clone(),
|
||||||
|
display_name: body.display_name.clone(),
|
||||||
|
robocraft_name: body.display_name.clone(),
|
||||||
|
email_address: body.email_address.clone(),
|
||||||
|
email_verified: true,
|
||||||
|
flags: Vec::new(),
|
||||||
|
};
|
||||||
|
let user_info = rc_core::persist::user::UserInfo {
|
||||||
|
payload,
|
||||||
|
extra: rc_core::persist::user::ExtraUserInfo::Email { password: body.password.clone() },
|
||||||
|
};
|
||||||
|
let response = config.robocraft.account_provider.login(user_info).await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to authenticate {} user {}: {}", body.target, body.display_name, e);
|
||||||
|
Status { code: 401 }
|
||||||
|
})?;
|
||||||
|
Ok(Json(response.response))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
|
rocket::fairing::AdHoc::on_ignite("Robocraft Username/Password", |rocket| async {
|
||||||
|
rocket.mount("/", routes![email_password_auth])
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
mod debug;
|
mod debug;
|
||||||
mod username;
|
mod username;
|
||||||
mod steam;
|
mod steam;
|
||||||
|
mod email;
|
||||||
|
|
||||||
pub fn stage() -> rocket::fairing::AdHoc {
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
rocket::fairing::AdHoc::on_ignite("robocraft", |rocket| async {
|
rocket::fairing::AdHoc::on_ignite("robocraft", |rocket| async {
|
||||||
rocket.attach(username::stage())
|
rocket.attach(username::stage())
|
||||||
|
.attach(email::stage())
|
||||||
.attach(steam::stage())
|
.attach(steam::stage())
|
||||||
.attach(debug::stage())
|
.attach(debug::stage())
|
||||||
.register("/", rocket::catchers![unauthorized])
|
.register("/", rocket::catchers![unauthorized])
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use rc_core::UserAuthenticator;
|
|||||||
use rocket::{post, routes, serde::json::Json, http::Status, State};
|
use rocket::{post, routes, serde::json::Json, http::Status, State};
|
||||||
|
|
||||||
#[post("/authenticate/robocraft/game", data = "<body>")]
|
#[post("/authenticate/robocraft/game", data = "<body>")]
|
||||||
pub async fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
pub async fn user_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
||||||
log::info!("Authenticating {} user {}", body.target, body.display_name);
|
log::info!("Authenticating {} user {}", body.target, body.display_name);
|
||||||
let payload = libfj::robocraft::TokenPayload {
|
let payload = libfj::robocraft::TokenPayload {
|
||||||
public_id: body.display_name.clone(),
|
public_id: body.display_name.clone(),
|
||||||
@@ -14,7 +14,7 @@ pub async fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthentic
|
|||||||
};
|
};
|
||||||
let user_info = rc_core::persist::user::UserInfo {
|
let user_info = rc_core::persist::user::UserInfo {
|
||||||
payload,
|
payload,
|
||||||
extra: rc_core::persist::user::ExtraUserInfo::Standalone { password: body.password.clone() },
|
extra: rc_core::persist::user::ExtraUserInfo::Username { password: body.password.clone() },
|
||||||
};
|
};
|
||||||
let response = config.robocraft.account_provider.login(user_info).await
|
let response = config.robocraft.account_provider.login(user_info).await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -25,7 +25,7 @@ pub async fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthentic
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn stage() -> rocket::fairing::AdHoc {
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
rocket::fairing::AdHoc::on_ignite("Robocraft Email/Password", |rocket| async {
|
rocket::fairing::AdHoc::on_ignite("Robocraft Username/Password", |rocket| async {
|
||||||
rocket.mount("/", routes![email_password_auth])
|
rocket.mount("/", routes![user_password_auth])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
|||||||
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| e.to_string())?;
|
jsonwebtoken::decode::<libfj::robocraft::TokenPayload>(&token.token, &secret, &validation).map_err(|e| e.to_string())?;
|
||||||
let user_info = if let Some(user_info) = self.db.user_by_public_id(token.uuid.clone()).await.map_err(|e| e.to_string())? {
|
let user_info = if let Some(user_info) = self.db.user_by_any_unique_id(token.uuid.clone()).await.map_err(|e| e.to_string())? {
|
||||||
user_info
|
user_info
|
||||||
} else {
|
} else {
|
||||||
return Err("User not found".to_owned());
|
return Err("User not found".to_owned());
|
||||||
@@ -62,14 +62,19 @@ impl super::UserAuthenticator for AccountProvider {
|
|||||||
async fn login(&self, info: super::UserInfo) -> Result<super::UserLoginInfo, String> {
|
async fn login(&self, info: super::UserInfo) -> Result<super::UserLoginInfo, String> {
|
||||||
//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 mut user_info = if let Some(user_info) = self.db.user_by_public_id(info.payload.public_id.clone()).await.map_err(|e| e.to_string())? {
|
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,
|
||||||
|
}.map_err(|e| e.to_string())?;
|
||||||
|
let mut user_info = if let Some(user_info) = user_opt {
|
||||||
is_new_user = false;
|
is_new_user = false;
|
||||||
user_info
|
user_info
|
||||||
} else {
|
} else {
|
||||||
is_new_user = true;
|
is_new_user = true;
|
||||||
log::info!("New user {}", info.payload.public_id);
|
log::info!("New user {}", info.payload.public_id);
|
||||||
super::setup_new_user(&info, &self.db).await.map_err(|e| e.to_string())?;
|
super::setup_new_user(&info, &self.db).await.map_err(|e| e.to_string())?;
|
||||||
self.db.user_by_public_id(info.payload.public_id.clone()).await.map_err(|e| e.to_string())?.unwrap()
|
self.db.user_by_display_name(info.payload.display_name.clone()).await.map_err(|e| e.to_string())?.unwrap()
|
||||||
};
|
};
|
||||||
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.extra {
|
||||||
@@ -83,7 +88,8 @@ impl super::UserAuthenticator for AccountProvider {
|
|||||||
return Err("SteamID not supported for this user".to_owned());
|
return Err("SteamID not supported for this user".to_owned());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
super::ExtraUserInfo::Standalone { password } => {
|
super::ExtraUserInfo::Email { password }
|
||||||
|
| super::ExtraUserInfo::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 {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub async fn setup_new_user(user: &super::UserInfo, db: &rc_database::Database)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn default_user_data(user: &super::UserInfo) -> rc_database::schema::user::ActiveModel {
|
fn default_user_data(user: &super::UserInfo) -> rc_database::schema::user::ActiveModel {
|
||||||
let password = if let super::ExtraUserInfo::Standalone { password } = &user.extra {
|
let password = if let super::ExtraUserInfo::Email { password } | super::ExtraUserInfo::Username { password } = &user.extra {
|
||||||
//password.to_owned()
|
//password.to_owned()
|
||||||
use argon2::password_hash::PasswordHasher;
|
use argon2::password_hash::PasswordHasher;
|
||||||
let argon2_algo = argon2::Argon2::default();
|
let argon2_algo = argon2::Argon2::default();
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ pub enum ExtraUserInfo {
|
|||||||
Steam {
|
Steam {
|
||||||
id: u64,
|
id: u64,
|
||||||
},
|
},
|
||||||
Standalone {
|
Username {
|
||||||
|
password: String,
|
||||||
|
},
|
||||||
|
Email {
|
||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,41 @@ impl Database {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_by_public_id(&self, public_id: String) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
pub async fn user_by_display_name(&self, public_id: String) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
||||||
crate::schema::user::Entity::find()
|
crate::schema::user::Entity::find()
|
||||||
.filter(crate::schema::user::Column::PublicId.eq(public_id))
|
.filter(crate::schema::user::Column::DisplayName.eq(public_id))
|
||||||
.one(&self.orm)
|
.one(&self.orm)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn user_by_steam_id(&self, steam_id: u64) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::user::Entity::find()
|
||||||
|
.filter(crate::schema::user::Column::SteamId.eq(Some(steam_id)))
|
||||||
|
.one(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn user_by_email(&self, email: String) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::user::Entity::find()
|
||||||
|
.filter(crate::schema::user::Column::Email.eq(email))
|
||||||
|
.one(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn user_by_any_unique_id(&self, id: String) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
||||||
|
if let Ok(steam_id) = id.parse::<u64>() {
|
||||||
|
if let Some(res) = self.user_by_steam_id(steam_id).await? {
|
||||||
|
return Ok(Some(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if id.contains('@') {
|
||||||
|
if let Some(res) = self.user_by_email(id.clone()).await? {
|
||||||
|
return Ok(Some(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.user_by_display_name(id.clone()).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert_user(&self, entity: crate::schema::user::ActiveModel) -> Result<crate::schema::user::Model, sea_orm::DbErr> {
|
pub async fn insert_user(&self, entity: crate::schema::user::ActiveModel) -> Result<crate::schema::user::Model, sea_orm::DbErr> {
|
||||||
entity.insert(&self.orm).await
|
entity.insert(&self.orm).await
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user