feat: add user-agent arg, moved actions in file
This commit is contained in:
parent
1808e98450
commit
0f310896d7
4 changed files with 91 additions and 58 deletions
1
src/admin/mod.rs
Normal file
1
src/admin/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod user;
|
63
src/admin/user.rs
Normal file
63
src/admin/user.rs
Normal file
|
@ -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::<proto::SessionUser>(&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(())
|
||||||
|
}
|
16
src/main.rs
16
src/main.rs
|
@ -1,6 +1,7 @@
|
||||||
mod proto;
|
mod proto;
|
||||||
mod entities;
|
mod entities;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
mod admin;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
|
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
@ -8,7 +9,7 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
use chrono::{DateTime, Utc, Duration};
|
use chrono::{DateTime, Utc, Duration};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use axum::{Router, routing::{get, post}, response::IntoResponse, Json, http::StatusCode};
|
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 sea_orm::{DatabaseConnection, Database};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing_subscriber::filter::filter_fn;
|
use tracing_subscriber::filter::filter_fn;
|
||||||
|
@ -31,6 +32,10 @@ struct CliArgs {
|
||||||
/// Action to run
|
/// Action to run
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
action: CliAction,
|
action: CliAction,
|
||||||
|
|
||||||
|
/// User agent to send when fetching minecraft data
|
||||||
|
#[arg(short, long)]
|
||||||
|
user_agent: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Clone, Debug)]
|
#[derive(Subcommand, Clone, Debug)]
|
||||||
|
@ -88,6 +93,7 @@ pub struct AppState {
|
||||||
token_duration: u32,
|
token_duration: u32,
|
||||||
time_window: u32,
|
time_window: u32,
|
||||||
fallback: bool,
|
fallback: bool,
|
||||||
|
user_agent: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -102,12 +108,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
let db = Database::connect(cfg.database.clone()).await?;
|
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 {
|
match cfg.action {
|
||||||
CliAction::Clean { token_lifetime } => {
|
CliAction::Clean { token_lifetime } => {
|
||||||
purge_expired_tokens(&db, Duration::hours(token_lifetime.into())).await?;
|
purge_expired_tokens(&db, Duration::hours(token_lifetime.into())).await?;
|
||||||
},
|
},
|
||||||
CliAction::Skins { } => {
|
CliAction::Skins { } => {
|
||||||
fill_missing_skins(&db).await?;
|
user_fetch_skin_all(&db, &user_agent).await?;
|
||||||
},
|
},
|
||||||
CliAction::Serve { bind_addr, token_duration, time_window, fallback } => {
|
CliAction::Serve { bind_addr, token_duration, time_window, fallback } => {
|
||||||
let secret = load_secret(&db).await?;
|
let secret = load_secret(&db).await?;
|
||||||
|
@ -128,7 +138,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// CUSTOM
|
// CUSTOM
|
||||||
.route("/register/unmigrated", post(register_unmigrated))
|
.route("/register/unmigrated", post(register_unmigrated))
|
||||||
.fallback(fallback_route)
|
.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);
|
info!(target: "MAIN", "serving Yggdrasil on {}", &addr);
|
||||||
|
|
||||||
|
|
|
@ -1,58 +1,12 @@
|
||||||
use std::{time::Duration, collections::HashSet};
|
use std::time::Duration;
|
||||||
|
|
||||||
use axum::{extract::State, Json};
|
use axum::{extract::State, Json};
|
||||||
use reqwest::StatusCode;
|
use reqwest::{StatusCode, header::USER_AGENT};
|
||||||
use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, Set, ActiveValue::NotSet, DatabaseConnection, DbErr};
|
use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, Set, ActiveValue::NotSet};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{AppState, proto::{self, Response}, entities};
|
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::<proto::SessionUser>(&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
|
//TODO: replace format! with axum's own
|
||||||
pub async fn register_unmigrated(State(state): State<AppState>, Json(payload): Json<proto::RegisterRequest>) -> Response<proto::RegisterResponse> {
|
pub async fn register_unmigrated(State(state): State<AppState>, Json(payload): Json<proto::RegisterRequest>) -> Response<proto::RegisterResponse> {
|
||||||
info!(target: "REGISTER", "[UNMIGRATED] called with {:?}", payload);
|
info!(target: "REGISTER", "[UNMIGRATED] called with {:?}", payload);
|
||||||
|
@ -60,11 +14,13 @@ pub async fn register_unmigrated(State(state): State<AppState>, Json(payload): J
|
||||||
let form = proto::RefreshRequest {
|
let form = proto::RefreshRequest {
|
||||||
accessToken: payload.token.accessToken,
|
accessToken: payload.token.accessToken,
|
||||||
clientToken: payload.token.clientToken,
|
clientToken: payload.token.clientToken,
|
||||||
selectedProfile: Some(payload.token.selectedProfile.clone()),
|
selectedProfile: None,
|
||||||
requestUser: Some(true),
|
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)
|
.json(&form)
|
||||||
.send().await
|
.send().await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(proto::Error::simple(format!("mojang error : {:?}", e)))))?
|
.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<AppState>, Json(payload): J
|
||||||
tokio::time::sleep(Duration::from_millis(500)).await; // avoid errors fetching skin due to timings
|
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 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?
|
let res = reqwest::Client::new()
|
||||||
.json(&()).send()
|
.get(url.clone()) //TODO: needs trimmed uuid, is it trimmed by default?
|
||||||
.await.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, Json(proto::Error::simple("internal server error"))))?
|
.header(USER_AGENT, &state.user_agent)
|
||||||
.text().await.expect("invalid body on response");
|
.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);
|
info!(target:"REGISTER", "Mojang response to texture fetch [{}]: {}", url, &res);
|
||||||
let doc = serde_json::from_str::<proto::SessionUser>(&res)
|
let doc = serde_json::from_str::<proto::SessionUser>(&res)
|
||||||
|
|
Loading…
Reference in a new issue