feat!: improved CLI: separated actions from backend

This commit is contained in:
əlemi 2023-05-09 02:11:00 +02:00
parent 5c5e878cea
commit 28b32c8135
Signed by: alemi
GPG key ID: A4895B84D311642C

View file

@ -2,12 +2,14 @@ mod proto;
mod entities; mod entities;
mod routes; mod routes;
mod persistence; mod persistence;
mod maintenance;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use chrono::{DateTime, Utc, Duration}; use chrono::{DateTime, Utc, Duration};
use clap::Parser; 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 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;
@ -23,29 +25,48 @@ use crate::{routes::{
/// Reimplementation of legacy auth server for minecraft /// Reimplementation of legacy auth server for minecraft
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
struct ConfigArgs { struct CliArgs {
/// Connection string for database /// Connection string for database
database: String, database: String,
/// Address to bind web application onto /// Action to run
#[arg(short, long, default_value = "127.0.0.1:26656")] #[clap(subcommand)]
bind_addr: String, action: CliAction,
}
/// How long an access token stays valid, in seconds #[derive(Subcommand, Clone, Debug)]
#[arg(long, default_value_t = 3600)] enum CliAction {
token_duration: u32, /// run yggdrasil backend service
Serve {
/// Address to bind web application onto
#[arg(short, long, default_value = "127.0.0.1:26656")]
bind_addr: String,
/// How long an access token is refreshable, in hours /// How long an access token stays valid, in seconds
#[arg(long, default_value_t = 168)] #[arg(long, default_value_t = 3600)]
token_lifetime: u32, token_duration: u32,
/// Valid time for join requests, in seconds /// Valid time for join requests, in seconds
#[arg(short, long, default_value_t = 10)] #[arg(short, long, default_value_t = 10)]
time_window: u32, time_window: u32,
/// Enable join request fallback to Microsoft
#[arg(long)]
fallback: bool,
},
/// remove expired tokens
Clean {
/// How long an access token is refreshable, in hours
#[arg(long, default_value_t = 168)]
token_lifetime: u32,
},
/// fetch missing skins
Skins {
}
/// Enable join request fallback to Microsoft
#[arg(long)]
fallback: bool,
} }
#[derive(Clone)] #[derive(Clone)]
@ -64,7 +85,7 @@ impl JoinAttempt {
pub struct AppState { pub struct AppState {
store: Arc<Mutex<HashMap<Uuid, JoinAttempt>>>, store: Arc<Mutex<HashMap<Uuid, JoinAttempt>>>,
db: DatabaseConnection, db: DatabaseConnection,
cfg: ConfigArgs, cfg: CliArgs,
secret: String, secret: String,
} }
@ -76,37 +97,45 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.with(tracing_subscriber::fmt::layer()) .with(tracing_subscriber::fmt::layer())
.init(); .init();
let cfg = ConfigArgs::parse(); let cfg = CliArgs::parse();
let db = Database::connect(cfg.database.clone()).await?; let db = Database::connect(cfg.database.clone()).await?;
purge_expired_tokens(&db, Duration::hours(cfg.token_lifetime.into())).await?; match cfg.action {
CliAction::Clean { token_lifetime } => {
purge_expired_tokens(&db, Duration::hours(token_lifetime.into())).await?;
},
CliAction::Skins { } => {
fill_missing_skins(&db).await?;
},
CliAction::Serve { bind_addr, token_duration, time_window, fallback } => {
let secret = load_secret(&db).await?;
let secret = load_secret(&db).await?; let store = Arc::new(Mutex::new(HashMap::new())); // TODO do this as an Actor
let store = Arc::new(Mutex::new(HashMap::new())); // TODO do this as an Actor let addr = bind_addr.parse()?;
let addr = cfg.bind_addr.parse()?; let app = Router::new()
// AUTH
.route("/auth/authenticate", post(authenticate)) //in: username, pass; out: token
.route("/auth/validate", post(validate)) //in: token; out: boolean valid
.route("/auth/refresh", post(refresh)) //in: token out: refreshed token
// SESSION
.route("/session/minecraft/join", post(join))
.route("/session/minecraft/hasJoined", get(has_joined_wrapper))
.route("/session/minecraft/profile/:user_id", get(profile))
// CUSTOM
.route("/register/unmigrated", post(register_unmigrated))
.fallback(fallback_route)
.with_state(AppState { store, db, cfg, secret });
let app = Router::new() info!(target: "MAIN", "serving Yggdrasil on {}", &addr);
// AUTH
.route("/auth/authenticate", post(authenticate)) //in: username, pass; out: token
.route("/auth/validate", post(validate)) //in: token; out: boolean valid
.route("/auth/refresh", post(refresh)) //in: token out: refreshed token
// SESSION
.route("/session/minecraft/join", post(join))
.route("/session/minecraft/hasJoined", get(has_joined_wrapper))
.route("/session/minecraft/profile/:user_id", get(profile))
// CUSTOM
.route("/register/unmigrated", post(register_unmigrated))
.fallback(fallback_route)
.with_state(AppState { store, db, cfg, secret });
info!(target: "MAIN", "serving Yggdrasil on {}", &addr); axum::Server::bind(&addr)
.serve(app.into_make_service())
axum::Server::bind(&addr) .await?;
.serve(app.into_make_service()) },
.await?; }
Ok(()) Ok(())
} }