diff --git a/Cargo.toml b/Cargo.toml index 700d99c..b47808f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ clap = { version = "4", features = ["derive"] } tokio = { version = "1.24.2", features = ["full"] } sea-orm = { version = "0.10", features = [ "sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls", "macros" ] } axum = "0.6.2" +reqwest = { version = "0.11", features = ["json"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tracing-subscriber = "0.3" diff --git a/src/main.rs b/src/main.rs index d7f1423..9b75628 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ use tracing::{info, metadata::LevelFilter}; use crate::routes::{ auth::{authenticate, validate, refresh}, - session::{join, has_joined, profile}, + session::{join, has_joined_wrapper, profile}, }; /// Reimplementation of legacy auth server for minecraft @@ -33,6 +33,10 @@ struct ConfigArgs { /// Valid time for join requests, in seconds #[arg(short, long, default_value_t = 10)] time_window: u32, + + /// Enable join request fallback to Microsoft + #[arg(long)] + fallback: bool, } #[derive(Clone)] @@ -76,14 +80,14 @@ async fn main() -> Result<(), Box> { .route("/auth/refresh", post(refresh)) //in: token out: refreshed token // SESSION .route("/session/minecraft/join", post(join)) - .route("/session/minecraft/hasJoined", get(has_joined)) + .route("/session/minecraft/hasJoined", get(has_joined_wrapper)) .route("/session/minecraft/profile/:user_id", get(profile)) // CUSTOM .route("/register", post(register_tmp)) .fallback(fallback_route) .with_state(AppState { store, db, cfg }); - info!(target: "MAIN", "serving app..."); + info!(target: "MAIN", "serving Yggdrasil on {}", &addr); axum::Server::bind(&addr) .serve(app.into_make_service()) diff --git a/src/routes/session.rs b/src/routes/session.rs index 7e83205..1037c7d 100644 --- a/src/routes/session.rs +++ b/src/routes/session.rs @@ -29,10 +29,36 @@ pub async fn join(State(state): State, Json(payload): Json, Query(query): Query>) -> Result, StatusCode> { - let username = query.get("username").unwrap().clone(); - let server_id = query.get("serverId").unwrap(); - info!(target: "SESSION", "[HAS_JOINED] called with user:{} server:{}", username, server_id); +pub async fn has_joined_wrapper(State(state): State, Query(query): Query>) -> Result, StatusCode> { + let username = query.get("username").ok_or(StatusCode::UNPROCESSABLE_ENTITY)?; + let server_id = query.get("serverId").ok_or(StatusCode::UNPROCESSABLE_ENTITY)?; + let user_ip = query.get("ip"); + + match has_joined_local(&state, username, server_id, user_ip).await { + Ok(r) => Ok(r), + Err(e) => { + if state.cfg.fallback { + Ok(has_joined_microsoft(&state, username, server_id, user_ip).await?) + } else { + Err(e) + } + } + } +} + +pub async fn has_joined_microsoft(_state: &AppState, username: &String, server_id: &String, _user_ip: Option<&String>) -> Result, StatusCode> { + Ok( + Json( // TODO don't do it with a format!!! + reqwest::get(format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={}&serverId={}", username, server_id)) + .await.map_err(|_| StatusCode::SERVICE_UNAVAILABLE)? + .json::() + .await.map_err(|_| StatusCode::UNAUTHORIZED)? + ) + ) +} + +pub async fn has_joined_local(state: &AppState, username: &String, server_id: &String, user_ip: Option<&String>) -> Result, StatusCode> { + info!(target: "SESSION", "[HAS_JOINED] called with user:{} server:{} ip:{:?}", username, server_id, user_ip); let user = entities::user::Entity::find().filter( entities::user::Column::Name.eq(username.clone())