From 0f310896d71a2e028ab3c67ed3845eca0bc96073 Mon Sep 17 00:00:00 2001 From: alemi Date: Mon, 20 Nov 2023 02:08:24 +0100 Subject: [PATCH] feat: add user-agent arg, moved actions in file --- src/admin/mod.rs | 1 + src/admin/user.rs | 63 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 16 ++++++++-- src/routes/register.rs | 69 +++++++++--------------------------------- 4 files changed, 91 insertions(+), 58 deletions(-) create mode 100644 src/admin/mod.rs create mode 100644 src/admin/user.rs diff --git a/src/admin/mod.rs b/src/admin/mod.rs new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src/admin/mod.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/src/admin/user.rs b/src/admin/user.rs new file mode 100644 index 0000000..89a51b8 --- /dev/null +++ b/src/admin/user.rs @@ -0,0 +1,63 @@ +use std::collections::HashSet; + +use reqwest::header::USER_AGENT; +use sea_orm::{DatabaseConnection, DbErr, EntityTrait, ActiveValue::NotSet, Set}; + +use crate::{entities::{user, property}, proto}; + + +pub async fn user_fetch_skin(db: &DatabaseConnection, user: &user::Model, agent: &str) -> Result<(), DbErr> { + tracing::info!("Updating skin for user {} ({})", user.name, user.uuid); + let url = format!("https://sessionserver.mojang.com/session/minecraft/profile/{}?unsigned=false", user.uuid.simple()); + let res = reqwest::Client::new() + .get(url.clone()) //TODO: needs trimmed uuid, is it trimmed by default? + .header(USER_AGENT, agent) + .send().await + .map_err(|_| DbErr::Custom(format!("could not fetch profile for user {}", user.uuid)))? + .text().await + .expect("invalid body on response"); + + let doc = serde_json::from_str::(&res) + .map_err(|_| DbErr::Json(format!("invalid texture response: {}", res)))?; + + let mut skin = proto::Property::default_skin(); + + for s in doc.properties.expect("missing properties field") { + if s.name == "textures" { + skin = s; + break; + } + } + + property::Entity::insert( + property::ActiveModel { + id: NotSet, + name: Set("textures".into()), + user_id: Set(user.id), + value: Set(skin.value), + signature: Set(skin.signature), + } + ).exec(db).await?; + + Ok(()) +} + +pub async fn user_fetch_skin_all(db: &DatabaseConnection, agent: &str) -> Result<(), DbErr> { + let mut users_with_skins = HashSet::new(); + + for skin in property::Entity::find().all(db).await? { + users_with_skins.insert(skin.user_id); + } + + for user in user::Entity::find().all(db).await? { + if users_with_skins.contains(&user.id) { + continue; + } + + if let Err(e) = user_fetch_skin(db, &user, agent).await { + tracing::error!("could not fetch skin for '{}': {}", user.name, e); + } + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 287999d..7293233 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod proto; mod entities; mod routes; +mod admin; mod persistence; use std::{collections::HashMap, sync::Arc}; @@ -8,7 +9,7 @@ use std::{collections::HashMap, sync::Arc}; use chrono::{DateTime, Utc, Duration}; use clap::{Parser, Subcommand}; use axum::{Router, routing::{get, post}, response::IntoResponse, Json, http::StatusCode}; -use routes::register::fill_missing_skins; +use admin::user::user_fetch_skin_all; use sea_orm::{DatabaseConnection, Database}; use tokio::sync::Mutex; use tracing_subscriber::filter::filter_fn; @@ -31,6 +32,10 @@ struct CliArgs { /// Action to run #[clap(subcommand)] action: CliAction, + + /// User agent to send when fetching minecraft data + #[arg(short, long)] + user_agent: Option, } #[derive(Subcommand, Clone, Debug)] @@ -88,6 +93,7 @@ pub struct AppState { token_duration: u32, time_window: u32, fallback: bool, + user_agent: String, } #[tokio::main] @@ -102,12 +108,16 @@ async fn main() -> Result<(), Box> { let db = Database::connect(cfg.database.clone()).await?; + let user_agent = cfg.user_agent.unwrap_or( + format!("yggdrasil/{}", env!("CARGO_PKG_VERSION")) + ); + match cfg.action { CliAction::Clean { token_lifetime } => { purge_expired_tokens(&db, Duration::hours(token_lifetime.into())).await?; }, CliAction::Skins { } => { - fill_missing_skins(&db).await?; + user_fetch_skin_all(&db, &user_agent).await?; }, CliAction::Serve { bind_addr, token_duration, time_window, fallback } => { let secret = load_secret(&db).await?; @@ -128,7 +138,7 @@ async fn main() -> Result<(), Box> { // CUSTOM .route("/register/unmigrated", post(register_unmigrated)) .fallback(fallback_route) - .with_state(AppState { store, db, token_duration, time_window, fallback, secret }); + .with_state(AppState { store, db, token_duration, time_window, fallback, secret, user_agent }); info!(target: "MAIN", "serving Yggdrasil on {}", &addr); diff --git a/src/routes/register.rs b/src/routes/register.rs index f253911..a8627c8 100644 --- a/src/routes/register.rs +++ b/src/routes/register.rs @@ -1,58 +1,12 @@ -use std::{time::Duration, collections::HashSet}; +use std::time::Duration; use axum::{extract::State, Json}; -use reqwest::StatusCode; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, Set, ActiveValue::NotSet, DatabaseConnection, DbErr}; +use reqwest::{StatusCode, header::USER_AGENT}; +use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, Set, ActiveValue::NotSet}; use tracing::info; use crate::{AppState, proto::{self, Response}, entities}; -// TODO shouldn't be here probably -pub async fn fill_missing_skins(db: &DatabaseConnection) -> Result<(), DbErr> { - let mut users_with_skins = HashSet::new(); - - for skin in entities::property::Entity::find().all(db).await? { - users_with_skins.insert(skin.user_id); - } - - for user in entities::user::Entity::find().all(db).await? { - if users_with_skins.contains(&user.id) { - continue; - } - - info!("Updating skin for user {} ({})", user.name, user.uuid); - let url = format!("https://sessionserver.mojang.com/session/minecraft/profile/{}?unsigned=false", user.uuid.simple()); - let res = reqwest::Client::new().get(url.clone()) //TODO: needs trimmed uuid, is it trimmed by default? - .json(&()).send() - .await.map_err(|_| DbErr::Custom(format!("could not fetch profile for user {}", user.uuid)))? - .text().await.expect("invalid body on response"); - - let doc = serde_json::from_str::(&res) - .map_err(|_| DbErr::Json("invalid texture response".into()))?; - - let mut skin = proto::Property::default_skin(); - - for s in doc.properties.expect("missing properties field") { - if s.name == "textures" { - skin = s; - break; - } - } - - entities::property::Entity::insert( - entities::property::ActiveModel { - id: NotSet, - name: Set("textures".into()), - user_id: Set(user.id), - value: Set(skin.value), - signature: Set(skin.signature), - } - ).exec(db).await?; - } - - Ok(()) -} - //TODO: replace format! with axum's own pub async fn register_unmigrated(State(state): State, Json(payload): Json) -> Response { info!(target: "REGISTER", "[UNMIGRATED] called with {:?}", payload); @@ -60,11 +14,13 @@ pub async fn register_unmigrated(State(state): State, Json(payload): J let form = proto::RefreshRequest { accessToken: payload.token.accessToken, clientToken: payload.token.clientToken, - selectedProfile: Some(payload.token.selectedProfile.clone()), + selectedProfile: None, requestUser: Some(true), }; - let response = reqwest::Client::new().post("https://authserver.mojang.com/refresh") + let response = reqwest::Client::new() + .post("https://authserver.mojang.com/refresh") + .header(USER_AGENT, &state.user_agent) .json(&form) .send().await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(proto::Error::simple(format!("mojang error : {:?}", e)))))? @@ -99,10 +55,13 @@ pub async fn register_unmigrated(State(state): State, Json(payload): J tokio::time::sleep(Duration::from_millis(500)).await; // avoid errors fetching skin due to timings let url = format!("https://sessionserver.mojang.com/session/minecraft/profile/{}?unsigned=false", uuid.simple()); - let res = reqwest::Client::new().get(url.clone()) //TODO: needs trimmed uuid, is it trimmed by default? - .json(&()).send() - .await.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, Json(proto::Error::simple("internal server error"))))? - .text().await.expect("invalid body on response"); + let res = reqwest::Client::new() + .get(url.clone()) //TODO: needs trimmed uuid, is it trimmed by default? + .header(USER_AGENT, &state.user_agent) + .send().await + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, Json(proto::Error::simple("internal server error"))))? + .text().await + .expect("invalid body on response"); info!(target:"REGISTER", "Mojang response to texture fetch [{}]: {}", url, &res); let doc = serde_json::from_str::(&res)