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:
Claude committed 2026-09-28 06:57:07 +00:00
1 parent 1b1bb984bc
commit 99b45141fc
29 files changed
+2436 -400

No files matched your search

+40 -10
View File
@@ -175,23 +175,48 @@ pub enum RegistrationMode {
pub struct AuthConfig {
pub panel_accounts: bool,
pub registration: RegistrationMode,
pub microsoft: bool,
pub microsoft_client_id: Option<String>,
pub offline_local: bool,
/// Absolute URL of the panel's Yggdrasil (authlib-injector) API root.
/// Filled in by the panel; the launcher falls back to `{panel}/api/yggdrasil`.
pub yggdrasil_url: Option<String>,
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
panel_accounts: true,
registration: RegistrationMode::Closed,
microsoft: false,
microsoft_client_id: None,
offline_local: true,
}
Self { panel_accounts: true, registration: RegistrationMode::Closed, offline_local: true, yggdrasil_url: None }
}
}
/// Yggdrasil session tokens handed to the launcher at sign-in. The access
/// token is what the game (via authlib-injector) uses to join servers.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct YggdrasilTokens {
pub access_token: String,
pub client_token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(default)]
pub struct CapeInfo {
pub id: i64,
pub name: String,
pub url: String,
}
/// A player's in-game identity: UUID, skin and cape.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(default)]
pub struct PlayerProfile {
pub uuid: String,
pub name: String,
pub skin_url: Option<String>,
/// "classic" (Steve arms) or "slim" (Alex arms).
pub skin_model: String,
pub cape: Option<CapeInfo>,
/// Capes this player is allowed to pick.
pub available_capes: Vec<CapeInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginRequest {
pub username: String,
@@ -210,7 +235,8 @@ pub struct RegisterRequest {
pub struct PublicUser {
pub id: i64,
pub username: String,
/// Offline-mode UUID (dashed), identical to what an offline server computes.
/// The player's UUID (dashed). New accounts get the offline-mode UUID for
/// their name, so offline and authenticated servers agree.
pub uuid: String,
pub role: String,
pub groups: Vec<String>,
@@ -218,11 +244,15 @@ pub struct PublicUser {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthResponse {
/// Panel session token (manifest, skins, account API).
pub token: String,
pub user: PublicUser,
/// Set when the account exists but is waiting for admin approval.
#[serde(default)]
pub pending: bool,
/// Game session for authlib-injector; absent for pending accounts.
#[serde(default)]
pub yggdrasil: Option<YggdrasilTokens>,
}
// ---------------------------------------------------------------------------