mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Move Robocraft auth server to actix_web for better error responses #31
This commit is contained in:
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -1495,6 +1495,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2595,7 +2596,6 @@ dependencies = [
|
||||
"jsonwebtoken",
|
||||
"libfj",
|
||||
"log",
|
||||
"oj_rc_core",
|
||||
"rocket",
|
||||
"rocket_dyn_templates",
|
||||
"serde",
|
||||
@@ -3346,6 +3346,25 @@ dependencies = [
|
||||
"zerocopy 0.8.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rc_auth"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-web",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"git-version",
|
||||
"handlebars",
|
||||
"hex",
|
||||
"libfj",
|
||||
"log",
|
||||
"oj_rc_core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rc_multiplayer"
|
||||
version = "0.3.0"
|
||||
|
||||
@@ -14,6 +14,7 @@ readme = "README.md"
|
||||
[workspace]
|
||||
members = [
|
||||
"auth", "cdn",
|
||||
"rc_auth",
|
||||
"polariton_auth",
|
||||
"rc_services", "rc_services_room",
|
||||
"rc_microtransactions",
|
||||
|
||||
@@ -9,9 +9,8 @@ authors.workspace = true
|
||||
|
||||
[features]
|
||||
steam = ["steamworks"]
|
||||
robocraft = ["oj_rc_core"]
|
||||
cardlife = []
|
||||
default = ["robocraft", "cardlife"]
|
||||
default = ["cardlife"]
|
||||
|
||||
[dependencies]
|
||||
rocket.workspace = true
|
||||
@@ -24,6 +23,5 @@ steamworks = { version = "0.11", optional = true }
|
||||
hex = "0.4"
|
||||
jsonwebtoken = "9"
|
||||
clap.workspace = true
|
||||
oj_rc_core = { version = "*", optional = true, path = "../rc_core" }
|
||||
serde.workspace = true
|
||||
git-version.workspace = true
|
||||
|
||||
@@ -3,10 +3,6 @@ use clap::Parser;
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct CliArgs {
|
||||
#[cfg(feature = "robocraft")]
|
||||
/// Robocraft user data root
|
||||
#[arg(long, default_value_t = {"../data/robocraft".to_string()})]
|
||||
pub data_robocraft: String,
|
||||
/// Robocraft asset data root
|
||||
#[arg(long, default_value_t = {"../assets/robocraft".to_string()})]
|
||||
pub assets_robocraft: String,
|
||||
@@ -19,13 +15,9 @@ impl CliArgs {
|
||||
|
||||
pub async fn preloaded(self) -> Config {
|
||||
Config {
|
||||
#[cfg(feature = "robocraft")]
|
||||
robocraft: crate::robocraft::RcConfig::from_args(&self).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
#[cfg(feature = "robocraft")]
|
||||
pub robocraft: crate::robocraft::RcConfig,
|
||||
}
|
||||
|
||||
@@ -30,9 +30,6 @@ fn get_steam_id_from_ticket(ticket: &[u8]) -> u64 {
|
||||
#[cfg(all(feature = "steam", feature = "cardlife"))]
|
||||
const STEAM_ID: &str = "920690"; // Cardlife steam app id
|
||||
|
||||
#[cfg(all(feature = "steam", feature = "robocraft"))]
|
||||
const STEAM_ID: &str = "301520"; // Robocraft steam app id
|
||||
|
||||
#[cfg(feature = "steam")]
|
||||
static STEAM_SERVER: std::sync::OnceLock<steamworks::Server> = std::sync::OnceLock::new();
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ mod common;
|
||||
|
||||
#[cfg(feature = "cardlife")]
|
||||
mod cardlife;
|
||||
#[cfg(feature = "robocraft")]
|
||||
mod robocraft;
|
||||
|
||||
#[rocket::get("/")]
|
||||
fn index() -> String {
|
||||
@@ -27,12 +25,7 @@ async fn rocket() -> _ {
|
||||
|
||||
#[cfg(feature = "cardlife")]
|
||||
{builder = builder.attach(cardlife::stage());}
|
||||
#[cfg(feature = "robocraft")]
|
||||
{builder = builder.attach(robocraft::stage());}
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "steam", feature = "robocraft", feature = "cardlife"))]
|
||||
compile_error!("Feature \"steam\" cannot work with features \"cardlife\" and \"robocraft\" at the same time");
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
use rocket::{post, routes};
|
||||
|
||||
#[post("/", data = "<body>")]
|
||||
pub fn debug_endpoint(body: String) -> String {
|
||||
log::info!("got body: `{}`", body);
|
||||
body.to_string()
|
||||
}
|
||||
|
||||
pub fn stage() -> rocket::fairing::AdHoc {
|
||||
rocket::fairing::AdHoc::on_ignite("Robocraft debug", |rocket| async {
|
||||
rocket.mount("/", routes![debug_endpoint])
|
||||
})
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
mod debug;
|
||||
mod username;
|
||||
mod steam;
|
||||
mod email;
|
||||
mod registration;
|
||||
|
||||
pub fn stage() -> rocket::fairing::AdHoc {
|
||||
rocket::fairing::AdHoc::on_ignite("robocraft", |rocket| async {
|
||||
rocket.attach(username::stage())
|
||||
.attach(email::stage())
|
||||
.attach(steam::stage())
|
||||
.attach(debug::stage())
|
||||
.attach(registration::stage())
|
||||
.register("/", rocket::catchers![unauthorized])
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct RcConfig {
|
||||
pub data: std::path::PathBuf,
|
||||
pub account_provider: oj_rc_core::UserImpl,
|
||||
pub assets: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl RcConfig {
|
||||
pub async fn from_args(args: &crate::common::cli::CliArgs) -> Self {
|
||||
let conf = oj_rc_core::persist::config::ConfigImpl::load(&args.assets_robocraft).expect("Bad config data");
|
||||
Self {
|
||||
account_provider: oj_rc_core::UserImpl::load(&args.data_robocraft, &conf).await.expect("Invalid Robocraft user data"),
|
||||
data: args.data_robocraft.clone().into(),
|
||||
assets: args.assets_robocraft.clone().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use rocket::{catch, serde::json::Json};
|
||||
|
||||
#[catch(401)]
|
||||
fn unauthorized() -> Json<libfj::robocraft::ErrorInfo> {
|
||||
Json(libfj::robocraft::ErrorInfo {
|
||||
error_code: "204".to_owned(),
|
||||
error_message: "Invalid username, password, or SteamID".to_owned(),
|
||||
})
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
use rocket::{http::Status, post, routes, serde::json::Json, State};
|
||||
|
||||
#[post("/authenticate/steam/game", data = "<body>")]
|
||||
pub async fn steam_auth(body: Json<libfj::robocraft::SteamAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
||||
let steam_id = crate::common::steam_utils::authenticate_steam_ticket(&body.steam_ticket)
|
||||
.map_err(|_| Status { code: 401 })?;
|
||||
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 response = config.robocraft.account_provider.login(user_info).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to authenticate {} steam user {}: {}", body.target, steam_id, e);
|
||||
Status { code: 401 }
|
||||
})?;
|
||||
Ok(Json(response.response))
|
||||
}
|
||||
|
||||
pub fn stage() -> rocket::fairing::AdHoc {
|
||||
rocket::fairing::AdHoc::on_ignite("Robocraft Steam", |rocket| async {
|
||||
rocket.mount("/", routes![steam_auth])
|
||||
})
|
||||
}
|
||||
24
rc_auth/Cargo.toml
Normal file
24
rc_auth/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "rc_auth"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-web.workspace = true
|
||||
actix-files.workspace = true
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
tokio = { version = "1.43", features = [ "rt-multi-thread" ] }
|
||||
clap.workspace = true
|
||||
oj_rc_core = { version = "*", path = "../rc_core" }
|
||||
libfj.workspace = true
|
||||
git-version.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
hex = "0.4"
|
||||
|
||||
handlebars = { version = "5", features = ["dir_source"] }
|
||||
3
rc_auth/run_debug.sh
Executable file
3
rc_auth/run_debug.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
RUST_LOG=debug cargo run
|
||||
31
rc_auth/src/cli.rs
Normal file
31
rc_auth/src/cli.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
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 = 8001)]
|
||||
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 async fn preloaded(self) -> crate::robocraft::RcConfig {
|
||||
crate::robocraft::RcConfig::from_args(&self).await
|
||||
}
|
||||
}
|
||||
60
rc_auth/src/main.rs
Normal file
60
rc_auth/src/main.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
mod cli;
|
||||
mod robocraft;
|
||||
|
||||
use actix_web::{App, HttpServer, Responder};
|
||||
|
||||
#[actix_web::get("/")]
|
||||
async fn index() -> 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 cli_args2 = actix_web::web::Data::new(cli_args.clone());
|
||||
let rc_preloaded = actix_web::web::Data::new(cli_args.clone().preloaded().await);
|
||||
|
||||
let mut handlebars = handlebars::Handlebars::new();
|
||||
handlebars
|
||||
.register_templates_directory(
|
||||
std::path::PathBuf::from(&cli_args.assets_robocraft).parent().expect("Bad robocraft asset path").join("templates"),
|
||||
handlebars::DirectorySourceOptions {
|
||||
tpl_extension: ".html.hbs".to_owned(),
|
||||
hidden: false,
|
||||
temporary: false,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let handlebars_ref = actix_web::web::Data::new(handlebars);
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.app_data(cli_args2.clone())
|
||||
.app_data(rc_preloaded.clone())
|
||||
.app_data(handlebars_ref.clone())
|
||||
.service(index)
|
||||
.service(robocraft::registration::form_submit)
|
||||
.service(robocraft::registration::form_load)
|
||||
.service(robocraft::registration::favicon)
|
||||
.service(robocraft::email::email_password_auth)
|
||||
.service(robocraft::steam::steam_auth)
|
||||
.service(robocraft::username::user_password_auth)
|
||||
/*.service(robocraft::live_data::live_data_json)
|
||||
.service(robocraft::user_avatar::get)
|
||||
.service(robocraft::clan_avatar::get)
|
||||
.service(robocraft::brawl_data::get)
|
||||
.service(robocraft::campaign_data::get)
|
||||
.service(robocraft::factory::arc::get)
|
||||
.service(robocraft::favicon::get)*/
|
||||
})
|
||||
.bind((cli_args.ip, cli_args.port))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
use rocket::{post, routes, serde::json::Json, http::Status, State};
|
||||
use actix_web::{post, web::{Data, Json}};
|
||||
|
||||
#[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> {
|
||||
#[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> {
|
||||
log::info!("Authenticating {} user {}", body.target, body.display_name);
|
||||
let payload = libfj::robocraft::TokenPayload {
|
||||
public_id: body.display_name.clone(),
|
||||
@@ -16,16 +16,10 @@ pub async fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthentic
|
||||
payload,
|
||||
extra: oj_rc_core::persist::user::ExtraUserInfo::Email { password: body.password.clone() },
|
||||
};
|
||||
let response = config.robocraft.account_provider.login(user_info).await
|
||||
let response = config.account_provider.login(user_info).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to authenticate {} user {}: {}", body.target, body.display_name, e);
|
||||
Status { code: 401 }
|
||||
log::error!("Failed to authenticate {} user {}: {}", body.target, body.display_name, e.message);
|
||||
super::ErrorTy::from_err(e)
|
||||
})?;
|
||||
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])
|
||||
})
|
||||
}
|
||||
63
rc_auth/src/robocraft/mod.rs
Normal file
63
rc_auth/src/robocraft/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
pub mod email;
|
||||
pub mod registration;
|
||||
pub mod steam;
|
||||
pub mod username;
|
||||
|
||||
pub struct RcConfig {
|
||||
//pub data: std::path::PathBuf,
|
||||
pub account_provider: oj_rc_core::UserImpl,
|
||||
pub assets: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl RcConfig {
|
||||
pub async fn from_args(args: &crate::cli::CliArgs) -> Self {
|
||||
let conf = oj_rc_core::persist::config::ConfigImpl::load(&args.assets_robocraft).expect("Bad config data");
|
||||
Self {
|
||||
account_provider: oj_rc_core::UserImpl::load(&args.data_robocraft, &conf).await.expect("Invalid Robocraft user data"),
|
||||
//data: args.data_robocraft.clone().into(),
|
||||
assets: args.assets_robocraft.clone().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(self) struct ErrorTy {
|
||||
json: libfj::robocraft::ErrorInfo,
|
||||
}
|
||||
|
||||
impl ErrorTy {
|
||||
pub fn from_err(error: oj_rc_core::persist::user::AuthError) -> Self {
|
||||
Self {
|
||||
json: libfj::robocraft::ErrorInfo {
|
||||
error_code: error.code.to_str(),
|
||||
error_message: error.message,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl actix_web::error::ResponseError for ErrorTy {
|
||||
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||
actix_web::http::StatusCode::UNAUTHORIZED
|
||||
}
|
||||
|
||||
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
|
||||
actix_web::HttpResponse::with_body(
|
||||
self.status_code(),
|
||||
serde_json::to_string(&self.json).unwrap(),
|
||||
).map_into_boxed_body()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for ErrorTy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
//use core::fmt::Write;
|
||||
write!(f, "({}) {}", self.json.error_code, self.json.error_message)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for ErrorTy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ErrorTy")
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
use rocket::{post, get, form::{Form, FromForm}, routes, http::Status, State};
|
||||
use rocket_dyn_templates::{Template, context};
|
||||
use serde::Serialize;
|
||||
use actix_web::{get, post, web::{Data, Form, Html}, Responder};
|
||||
//use rocket_dyn_templates::{Template, context};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
const FORM_NAME: &str = "rc_register";
|
||||
const FORM_NAME_SUCCESS: &str = "rc_register_success";
|
||||
@@ -12,7 +12,7 @@ const VALID_CHARS: &[char] = &[
|
||||
'_',
|
||||
];
|
||||
|
||||
#[derive(FromForm, Serialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RegisterForm {
|
||||
display_name: String,
|
||||
password: String,
|
||||
@@ -29,6 +29,14 @@ struct Context {
|
||||
source_url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ContextSuccess {
|
||||
display_name: String,
|
||||
id: i32,
|
||||
version: String,
|
||||
source_url: String,
|
||||
}
|
||||
|
||||
fn version_string() -> String {
|
||||
let name = env!("CARGO_PKG_NAME");
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
@@ -46,35 +54,37 @@ fn all_valid_chars(s: &str) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn registration_ok(form: RegisterForm) -> Template {
|
||||
Template::render(FORM_NAME, Context {
|
||||
fn registration_ok(form: RegisterForm, renderer: &handlebars::Handlebars<'_>) -> 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 registration_err(form: RegisterForm, error: String) -> Template {
|
||||
Template::render(FORM_NAME, Context {
|
||||
fn registration_err(form: RegisterForm, error: String , renderer: &handlebars::Handlebars<'_>) -> 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)
|
||||
}
|
||||
|
||||
#[post("/register", data = "<form>")]
|
||||
async fn form_submit(form: Form<RegisterForm>, config: &State<crate::common::cli::Config>) -> Result<Template, Status> {
|
||||
#[post("/register")]
|
||||
pub async fn form_submit(form: Form<RegisterForm>, config: Data<super::RcConfig>, handlebars_ref: Data<handlebars::Handlebars<'_>>) -> Result<Html, actix_web::error::Error> {
|
||||
// password confirmation validation
|
||||
if form.password != form.password_c {
|
||||
return Ok(registration_err(form.into_inner(), "Passwords do not match".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Passwords do not match".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
if form.password.len() < 8 {
|
||||
return Ok(registration_err(form.into_inner(), "Password too short (minimum 8 characters)".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Password too short (minimum 8 characters)".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
if form.password.len() > 128 {
|
||||
return Ok(registration_err(form.into_inner(), "Password too long (maximum 128 characters)".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Password too long (maximum 128 characters)".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
|
||||
// email validation
|
||||
@@ -84,16 +94,16 @@ async fn form_submit(form: Form<RegisterForm>, config: &State<crate::common::cli
|
||||
actual_email = None;
|
||||
} else {
|
||||
if !email.contains('@') {
|
||||
return Ok(registration_err(form.into_inner(), "Email must contain @".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Email must contain @".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
let email_exists = config.robocraft.account_provider.user_exists(oj_rc_core::persist::user::UserId::Email(email.to_owned()))
|
||||
let email_exists = config.account_provider.user_exists(oj_rc_core::persist::user::UserId::Email(email.to_owned()))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to check if user email {} exists: {}", email, e);
|
||||
Status { code: 500 }
|
||||
actix_web::error::ErrorInternalServerError(e)
|
||||
})?;
|
||||
if email_exists {
|
||||
return Ok(registration_err(form.into_inner(), "Email already registered".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Email already registered".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
actual_email = Some(email.to_owned());
|
||||
}
|
||||
@@ -109,19 +119,19 @@ async fn form_submit(form: Form<RegisterForm>, config: &State<crate::common::cli
|
||||
} else {
|
||||
let steam_id = match steam_id.parse() {
|
||||
Ok(id) => id,
|
||||
Err(_e) => return Ok(registration_err(form.into_inner(), "Invalid SteamID (not an integer)".to_owned())),
|
||||
Err(_e) => return Ok(registration_err(form.into_inner(), "Invalid SteamID (not an integer)".to_owned(), &*handlebars_ref)),
|
||||
};
|
||||
if steam_id >= 7656120_0000000000 || steam_id < 7656119_0000000000 {
|
||||
return Ok(registration_err(form.into_inner(), "Invalid SteamID (should be like 7656119XXXXXXXXXX)".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Invalid SteamID (should be like 7656119XXXXXXXXXX)".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
let steam_exists = config.robocraft.account_provider.user_exists(oj_rc_core::persist::user::UserId::SteamId(steam_id))
|
||||
let steam_exists = config.account_provider.user_exists(oj_rc_core::persist::user::UserId::SteamId(steam_id))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to check if user steam id {} exists: {}", steam_id, e);
|
||||
Status { code: 500 }
|
||||
actix_web::error::ErrorInternalServerError(e)
|
||||
})?;
|
||||
if steam_exists {
|
||||
return Ok(registration_err(form.into_inner(), "SteamID already registered".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "SteamID already registered".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
actual_steam_id = Some(steam_id);
|
||||
}
|
||||
@@ -131,25 +141,25 @@ async fn form_submit(form: Form<RegisterForm>, config: &State<crate::common::cli
|
||||
|
||||
// username validation
|
||||
if form.display_name.len() < 4 {
|
||||
return Ok(registration_err(form.into_inner(), "Username too short (minimum 4 characters)".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Username too short (minimum 4 characters)".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
if form.display_name.len() > 32 {
|
||||
return Ok(registration_err(form.into_inner(), "Username too long (maximum 32 characters)".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Username too long (maximum 32 characters)".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
if !all_valid_chars(&form.display_name.to_lowercase()) {
|
||||
return Ok(registration_err(form.into_inner(), "Invalid username (only alphanumerics and _ allowed)".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Invalid username (only alphanumerics and _ allowed)".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
let username_exists = config.robocraft.account_provider.user_exists(oj_rc_core::persist::user::UserId::Username(form.display_name.to_owned()))
|
||||
let username_exists = config.account_provider.user_exists(oj_rc_core::persist::user::UserId::Username(form.display_name.to_owned()))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to check if user name {} exists: {}", form.display_name, e);
|
||||
Status { code: 500 }
|
||||
actix_web::error::ErrorInternalServerError(e)
|
||||
})?;
|
||||
if username_exists {
|
||||
return Ok(registration_err(form.into_inner(), "Username already registered".to_owned()));
|
||||
return Ok(registration_err(form.into_inner(), "Username already registered".to_owned(), &*handlebars_ref));
|
||||
}
|
||||
|
||||
let user_id = match config.robocraft.account_provider.register(oj_rc_core::persist::user::RegistrationInfo {
|
||||
let user_id = match config.account_provider.register(oj_rc_core::persist::user::RegistrationInfo {
|
||||
display_name: form.display_name.clone(),
|
||||
password: form.password.clone(),
|
||||
email: actual_email,
|
||||
@@ -157,40 +167,31 @@ async fn form_submit(form: Form<RegisterForm>, config: &State<crate::common::cli
|
||||
}).await {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return Ok(registration_err(form.into_inner(), format!("Registration failed: {}", e)));
|
||||
return Ok(registration_err(form.into_inner(), format!("Registration failed: {}", e), &*handlebars_ref));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Template::render(FORM_NAME_SUCCESS, context! {
|
||||
Ok(Html::new(handlebars_ref.render(FORM_NAME_SUCCESS, &ContextSuccess {
|
||||
display_name: form.display_name.clone(),
|
||||
id: user_id,
|
||||
version: version_string(),
|
||||
source_url: env!("CARGO_PKG_REPOSITORY").to_owned(),
|
||||
}))
|
||||
}).unwrap()))
|
||||
}
|
||||
|
||||
#[get("/register")]
|
||||
async fn form_load() -> Template {
|
||||
pub async fn form_load(handlebars_ref: Data<handlebars::Handlebars<'_>>) -> Html {
|
||||
registration_ok(RegisterForm {
|
||||
display_name: "".to_owned(),
|
||||
password: "".to_owned(),
|
||||
password_c: "".to_owned(),
|
||||
email: None,
|
||||
steam_id: None,
|
||||
})
|
||||
}, &*handlebars_ref)
|
||||
}
|
||||
|
||||
#[get("/robocraft/favicon")]
|
||||
pub async fn favicon(config: &State<crate::common::cli::Config>) -> Result<rocket::fs::NamedFile, std::io::Error> {
|
||||
let path = config.robocraft.assets.join("favicon.jpg");
|
||||
rocket::fs::NamedFile::open(path).await
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn stage() -> rocket::fairing::AdHoc {
|
||||
rocket::fairing::AdHoc::on_ignite("Robocraft Steam", |rocket| async {
|
||||
rocket.mount("/", routes![form_load, form_submit, favicon])
|
||||
.attach(Template::fairing())
|
||||
})
|
||||
pub async fn favicon(config: Data<super::RcConfig>) -> impl Responder {
|
||||
let path = config.assets.join("favicon.jpg");
|
||||
actix_files::NamedFile::open_async(path).await
|
||||
}
|
||||
65
rc_auth/src/robocraft/steam.rs
Normal file
65
rc_auth/src/robocraft/steam.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
use actix_web::{post, web::{Data, Json}};
|
||||
|
||||
fn authenticate_steam_ticket(hex_ticket: &str) -> Result<u64, ()> {
|
||||
get_steam_id_from_ticket_hex(hex_ticket)
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to parse steamId: {}", e);
|
||||
()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_steam_id_from_ticket_hex(hex_ticket: &str) -> Result<u64, hex::FromHexError> {
|
||||
let decoded_ticket = hex::decode(hex_ticket)?;
|
||||
if decoded_ticket.len() < 72 {
|
||||
Err(hex::FromHexError::InvalidStringLength)
|
||||
} else {
|
||||
Ok(get_steam_id_from_ticket(&decoded_ticket))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_steam_id_from_ticket(ticket: &[u8]) -> u64 {
|
||||
get_u64_with_offset(&ticket, 12 /* also at 64 ??? */) // should be 76600000000000000 > number > 76500000000000000
|
||||
}
|
||||
|
||||
fn get_u64_with_offset(arr: &[u8], start: usize) -> u64 {
|
||||
let arr_actual: [u8; 8] = [
|
||||
arr[start],
|
||||
arr[start+1],
|
||||
arr[start+2],
|
||||
arr[start+3],
|
||||
arr[start+4],
|
||||
arr[start+5],
|
||||
arr[start+6],
|
||||
arr[start+7],
|
||||
];
|
||||
u64::from_le_bytes(arr_actual)
|
||||
}
|
||||
|
||||
#[post("/authenticate/steam/game")]
|
||||
pub async fn steam_auth(body: Json<libfj::robocraft::SteamAuthenticationPayload>, config: Data<super::RcConfig>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, super::ErrorTy> {
|
||||
let steam_id = authenticate_steam_ticket(&body.steam_ticket)
|
||||
.map_err(|_| super::ErrorTy::from_err(oj_rc_core::persist::user::AuthError {
|
||||
message: "Bad SteamId".to_owned(),
|
||||
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 response = config.account_provider.login(user_info).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to authenticate {} steam user {}: {}", body.target, steam_id, e.message);
|
||||
super::ErrorTy::from_err(e)
|
||||
})?;
|
||||
Ok(Json(response.response))
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
use rocket::{post, routes, serde::json::Json, http::Status, State};
|
||||
use actix_web::{post, web::{Data, Json}};
|
||||
|
||||
#[post("/authenticate/robocraft/game", data = "<body>")]
|
||||
pub async fn user_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
||||
#[post("/authenticate/robocraft/game")]
|
||||
pub async fn user_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: Data<super::RcConfig>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, super::ErrorTy> {
|
||||
log::info!("Authenticating {} user {}", body.target, body.display_name);
|
||||
let payload = libfj::robocraft::TokenPayload {
|
||||
public_id: body.display_name.clone(),
|
||||
@@ -16,16 +16,10 @@ pub async fn user_password_auth(body: Json<libfj::robocraft::EmailUserAuthentica
|
||||
payload,
|
||||
extra: oj_rc_core::persist::user::ExtraUserInfo::Username { password: body.password.clone() },
|
||||
};
|
||||
let response = config.robocraft.account_provider.login(user_info).await
|
||||
let response = config.account_provider.login(user_info).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to authenticate {} user {}: {}", body.target, body.display_name, e);
|
||||
Status { code: 401 }
|
||||
log::error!("Failed to authenticate {} user {}: {}", body.target, body.display_name, e.message);
|
||||
super::ErrorTy::from_err(e)
|
||||
})?;
|
||||
Ok(Json(response.response))
|
||||
}
|
||||
|
||||
pub fn stage() -> rocket::fairing::AdHoc {
|
||||
rocket::fairing::AdHoc::on_ignite("Robocraft Username/Password", |rocket| async {
|
||||
rocket.mount("/", routes![user_password_auth])
|
||||
})
|
||||
}
|
||||
@@ -101,3 +101,23 @@ impl LobbyReasonCode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u16)] // doesn't really matter
|
||||
#[derive(Debug)]
|
||||
pub enum AuthErrorCode {
|
||||
Unknown = 0,
|
||||
InvalidDisplayName = 122,
|
||||
AccountBlocked202 = 202,
|
||||
PasswordInvalidated = 203,
|
||||
BadCredentials = 204,
|
||||
DisplayeNameAlreadyInUse = 210,
|
||||
AccountBlocked301 = 301,
|
||||
AccountUnconfirmed = 302,
|
||||
UnlinkedSteamAccount = 303,
|
||||
}
|
||||
|
||||
impl AuthErrorCode {
|
||||
pub fn to_str(self) -> String {
|
||||
(self as u16).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,21 +63,36 @@ impl AccountProvider {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
||||
async fn authenticate(&self, token: super::UserToken) -> Result<Box<dyn super::User<C> + Send + Sync>, String> {
|
||||
async fn authenticate(&self, token: super::UserToken) -> Result<Box<dyn super::User<C> + Send + Sync>, super::AuthError> {
|
||||
//let new_root = self.root.join(&token.uuid);
|
||||
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| 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())? {
|
||||
jsonwebtoken::decode::<libfj::robocraft::TokenPayload>(&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 {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::Unknown,
|
||||
})? {
|
||||
user_info
|
||||
} else {
|
||||
return Err("User not found".to_owned());
|
||||
return Err(super::AuthError {
|
||||
message: "User not found".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
};
|
||||
let user_perms = if let Some(user_perms) = self.db.perms_by_user_id(user_info.id).await.map_err(|e| e.to_string())? {
|
||||
let user_perms = if let Some(user_perms) = self.db.perms_by_user_id(user_info.id).await.map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::Unknown
|
||||
})? {
|
||||
user_perms
|
||||
} else {
|
||||
return Err("User permissions not found".to_owned());
|
||||
return Err(super::AuthError {
|
||||
message: "User permissions not found".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
};
|
||||
//let account_info = AccountInfo::load(&new_root).map_err(|e| e.to_string())?;
|
||||
Ok(Box::new(UserData {
|
||||
@@ -90,16 +105,28 @@ impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
||||
//Err("Unable to authenticate".to_string())
|
||||
}
|
||||
|
||||
async fn multiplayer_authenticate(&self, user: String) -> Result<Box<dyn super::User<C> + Send + Sync>, String> {
|
||||
let user_info = if let Some(user_info) = self.db.user_by_display_name(user).await.map_err(|e| e.to_string())? {
|
||||
async fn multiplayer_authenticate(&self, user: String) -> Result<Box<dyn super::User<C> + Send + Sync>, super::AuthError> {
|
||||
let user_info = if let Some(user_info) = self.db.user_by_display_name(user).await.map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::Unknown,
|
||||
})? {
|
||||
user_info
|
||||
} else {
|
||||
return Err("User not found".to_owned());
|
||||
return Err(super::AuthError {
|
||||
message: "User not found".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
};
|
||||
let user_perms = if let Some(user_perms) = self.db.perms_by_user_id(user_info.id).await.map_err(|e| e.to_string())? {
|
||||
let user_perms = if let Some(user_perms) = self.db.perms_by_user_id(user_info.id).await.map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::Unknown,
|
||||
})? {
|
||||
user_perms
|
||||
} else {
|
||||
return Err("User permissions not found".to_owned());
|
||||
return Err(super::AuthError {
|
||||
message: "User permissions not found".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
};
|
||||
Ok(Box::new(UserData {
|
||||
account: user_info,
|
||||
@@ -113,14 +140,17 @@ impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
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, 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,
|
||||
}.map_err(|e| e.to_string())?;
|
||||
}.map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
})?;
|
||||
let mut user_info = if let Some(user_info) = user_opt {
|
||||
is_new_user = false;
|
||||
user_info
|
||||
@@ -128,11 +158,20 @@ impl super::UserAuthenticator for AccountProvider {
|
||||
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| e.to_string())?;
|
||||
self.db.user_by_display_name(info.payload.display_name.clone()).await.map_err(|e| e.to_string())?.unwrap()
|
||||
super::setup_new_user(&info, &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 {
|
||||
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);
|
||||
return Err(format!("User does not exist"));
|
||||
return Err(super::AuthError {
|
||||
message: "User not found".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
}
|
||||
};
|
||||
let override_password = user_info.password.is_empty() && user_info.steam_id.is_none();
|
||||
@@ -141,10 +180,16 @@ impl super::UserAuthenticator for AccountProvider {
|
||||
let id_str = id.to_string();
|
||||
if let Some(expected_steam_id) = user_info.steam_id {
|
||||
if expected_steam_id != id_str {
|
||||
return Err("SteamID does not match".to_owned())
|
||||
return Err(super::AuthError {
|
||||
message: "SteamID does not match".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err("SteamID not supported for this user".to_owned());
|
||||
return Err(super::AuthError {
|
||||
message: "SteamID not supported for this user".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
}
|
||||
},
|
||||
super::ExtraUserInfo::Email { password }
|
||||
@@ -153,17 +198,46 @@ impl super::UserAuthenticator for AccountProvider {
|
||||
let argon2_algo = argon2::Argon2::default();
|
||||
if override_password {
|
||||
let salt = argon2::password_hash::SaltString::generate(&mut argon2::password_hash::rand_core::OsRng);
|
||||
let password_hash = argon2_algo.hash_password(password.as_bytes(), &salt).map_err(|e| e.to_string())?.to_string();
|
||||
let password_hash = argon2_algo.hash_password(password.as_bytes(), &salt).map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::Unknown,
|
||||
})?.to_string();
|
||||
user_info.password = password_hash;
|
||||
}
|
||||
if !user_info.password.is_empty() {
|
||||
let expected = argon2::password_hash::PasswordHash::new(&user_info.password).map_err(|e| e.to_string())?;
|
||||
argon2_algo.verify_password(password.as_bytes(), &expected).map_err(|e| e.to_string())?;
|
||||
let expected = argon2::password_hash::PasswordHash::new(&user_info.password).map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::Unknown,
|
||||
})?;
|
||||
argon2_algo.verify_password(password.as_bytes(), &expected).map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::Unknown,
|
||||
})?;
|
||||
} else {
|
||||
return Err("Password not supported for this user".to_owned())
|
||||
return Err(super::AuthError {
|
||||
message: "Password not supported for this user".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if user is banned
|
||||
if let Some(perms) = self.db.perms_by_user_id(user_info.id).await.map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::Unknown,
|
||||
})? {
|
||||
if perms.banned {
|
||||
return Err(super::AuthError {
|
||||
message: "User is banned".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::AccountBlocked301,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(super::AuthError {
|
||||
message: "User has no permissions".to_owned(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
});
|
||||
}
|
||||
// authentication has now definitely succeeded
|
||||
// build token
|
||||
let header = jsonwebtoken::Header {
|
||||
|
||||
@@ -11,7 +11,7 @@ mod inventory;
|
||||
pub use inventory::UnlockedParts;
|
||||
|
||||
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, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent};
|
||||
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, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError};
|
||||
|
||||
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
||||
|
||||
|
||||
@@ -41,16 +41,21 @@ pub struct RegistrationInfo {
|
||||
pub steam_id: Option<u64>,
|
||||
}
|
||||
|
||||
pub struct AuthError {
|
||||
pub message: String,
|
||||
pub code: crate::data::error_codes::AuthErrorCode,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait UserProvider<C> {
|
||||
async fn authenticate(&self, user: UserToken) -> Result<Box<dyn User<C> + Send + Sync>, String>;
|
||||
async fn authenticate(&self, user: UserToken) -> Result<Box<dyn User<C> + Send + Sync>, AuthError>;
|
||||
|
||||
async fn multiplayer_authenticate(&self, user: String) -> Result<Box<dyn User<C> + Send + Sync>, String>;
|
||||
async fn multiplayer_authenticate(&self, user: String) -> Result<Box<dyn User<C> + Send + Sync>, AuthError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait UserAuthenticator {
|
||||
async fn login(&self, info: UserInfo) -> Result<UserLoginInfo, String>;
|
||||
async fn login(&self, info: UserInfo) -> Result<UserLoginInfo, AuthError>;
|
||||
async fn user_exists(&self, user: UserId) -> Result<bool, String>;
|
||||
async fn register(&self, info: RegistrationInfo) -> Result<i32, String>;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ impl <C: Clone + Send + 'static> UserState<C> {
|
||||
true
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to authenticate {}: {}", splits[0], e);
|
||||
log::error!("Failed to authenticate {}: ({:?}) {}", splits[0], e.code, e.message);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user