Panel: Yggdrasil auth server (authlib-injector), skins and capes
- Yggdrasil API per the authlib-injector spec: metadata with signing key and skin domains, authenticate/refresh/validate/invalidate/signout, join/hasJoined, profile lookup, texture upload, and the minecraftservices endpoints (chat certificates, publickeys, attributes, blocklist) - 4096-bit signing key generated once into the data volume; textures and chat certificates signed SHA1withRSA (verified with Java's own crypto) - Skins/capes stored content-addressed after validation and re-encoding; cape library with public/group/private visibility; head avatars API - Launcher login returns a game session; launcher sessions recorded for launcher-only servers; authlib-injector download mirror - Schema v2: player UUIDs (offline UUID backfilled), skins, capes, tokens, sessions, chat keys, game server tables - Remove Microsoft sign-in from the engine and panel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
This commit is contained in:
29 files changed
+2436
-400
No files matched your search
+19
-7
@@ -4,10 +4,13 @@ pub mod auth;
|
||||
pub mod config;
|
||||
pub mod db;
|
||||
pub mod error;
|
||||
pub mod net;
|
||||
pub mod packs;
|
||||
pub mod routes;
|
||||
pub mod state;
|
||||
pub mod store;
|
||||
pub mod textures;
|
||||
pub mod yggdrasil;
|
||||
|
||||
use axum::http::{header, HeaderValue};
|
||||
use axum::Router;
|
||||
@@ -38,9 +41,17 @@ pub fn jwt_secret(cfg: &config::Config) -> anyhow::Result<Vec<u8>> {
|
||||
}
|
||||
|
||||
pub async fn build_state(cfg: config::Config, db: sqlx::SqlitePool) -> anyhow::Result<AppState> {
|
||||
let path = cfg.signing_key_path();
|
||||
let ygg = tokio::task::spawn_blocking(move || yggdrasil::keys::Keys::load_or_create(&path)).await??;
|
||||
build_state_with_keys(cfg, db, Arc::new(ygg)).await
|
||||
}
|
||||
|
||||
/// Like [`build_state`] with a given auth-server key (tests reuse one key).
|
||||
pub async fn build_state_with_keys(cfg: config::Config, db: sqlx::SqlitePool, ygg: Arc<yggdrasil::keys::Keys>) -> anyhow::Result<AppState> {
|
||||
let secret = jwt_secret(&cfg)?;
|
||||
Ok(AppState {
|
||||
db,
|
||||
ygg,
|
||||
keys: Arc::new(auth::Keys::new(&secret)),
|
||||
http: scopenet_core::http::client(),
|
||||
login_guard: Arc::new(auth::LoginGuard::default()),
|
||||
@@ -62,13 +73,9 @@ pub async fn bootstrap_admin(state: &AppState) -> anyhow::Result<()> {
|
||||
(hex::encode(bytes), true)
|
||||
}
|
||||
};
|
||||
let hash = auth::hash_password(&password).map_err(|e| anyhow::anyhow!(e.message))?;
|
||||
sqlx::query("INSERT INTO users (username, password_hash, role, status, created_at) VALUES (?, ?, 'admin', 'active', ?)")
|
||||
.bind(&state.cfg.admin_username)
|
||||
.bind(hash)
|
||||
.bind(db::now())
|
||||
.execute(&state.db)
|
||||
.await?;
|
||||
auth::create_user(state, &state.cfg.admin_username, &password, None, "admin", "active")
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e.message))?;
|
||||
if generated {
|
||||
tracing::warn!("============================================================");
|
||||
tracing::warn!(" Created admin account '{}' with password: {password}", state.cfg.admin_username);
|
||||
@@ -91,6 +98,11 @@ pub fn app(state: AppState) -> Router {
|
||||
.nest_service("/files", ServeDir::new(state.cfg.files_dir()))
|
||||
.nest_service("/uploads", tower::ServiceBuilder::new().layer(long_cache).service(ServeDir::new(state.cfg.uploads_dir())))
|
||||
.fallback_service(spa)
|
||||
// Lets authlib-injector users enter just the panel URL (API Location Indication).
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
header::HeaderName::from_static("x-authlib-injector-api-location"),
|
||||
HeaderValue::from_static("/api/yggdrasil/"),
|
||||
))
|
||||
.layer(CompressionLayer::new())
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.with_state(state)
|
||||
|
||||
Reference in new issue
Block a user