Complete private authentication, server integrations and activity reporting

Add Fabric/Forge version builds and Paper integration, preserve permanent player identities across renames, harden session authorization, and surface privacy-conscious launcher/server activity in the panel.
This commit is contained in:
SCOPEDD committed 2026-09-28 13:31:17 -04:00
commit a1f19e86c6
74 files changed
+2108 -95

No files matched your search

+35 -8
View File
@@ -67,7 +67,7 @@ async fn find_user(state: &AppState, username: &str) -> AppResult<Option<UserRow
}
/// Panel token + a fresh game session for authlib-injector.
async fn signed_in(state: &AppState, user: &UserRow) -> AppResult<AuthResponse> {
pub(crate) async fn signed_in(state: &AppState, user: &UserRow) -> AppResult<AuthResponse> {
let (access_token, client_token) = yggdrasil::issue_token(state, user.id, None).await?;
Ok(AuthResponse {
token: state.keys.issue(user)?,
@@ -98,6 +98,7 @@ pub async fn login(State(state): State<AppState>, Json(req): Json<LoginRequest>)
return Err(AppError::forbidden("account sign-in is currently disabled"));
}
sqlx::query("UPDATE users SET last_login = ? WHERE id = ?").bind(crate::db::now()).bind(user.id).execute(&state.db).await?;
super::activity::record(&state, &user, "auth", "login", None).await?;
Ok(Json(signed_in(&state, &user).await?))
}
@@ -134,16 +135,41 @@ pub async fn me(State(state): State<AppState>, AuthUser(user): AuthUser) -> AppR
pub async fn event(
State(state): State<AppState>,
MaybeUser(user): MaybeUser,
AuthUser(user): AuthUser,
ClientIp(ip): ClientIp,
Json(ev): Json<LaunchEvent>,
) -> AppResult<Json<serde_json::Value>> {
let kind = if ev.kind == "launch" { "launch" } else { "other" };
let kind = ev.kind.as_str();
if !matches!(
kind,
"launch"
| "launcher_open"
| "launcher_close"
| "settings_changed"
| "account_selected"
| "logout"
| "install_start"
| "install_complete"
| "repair_start"
| "repair_complete"
| "launch_failed"
| "launch_cancelled"
| "game_exit"
| "game_crash"
| "game_killed"
| "instance_deleted"
| "cache_cleared"
| "update_installed"
| "folder_opened"
| "link_opened"
) {
return Err(AppError::bad_request("unknown launcher event"));
}
// Remember where signed-in players launch from, so game servers can
// require "joined through the launcher" (see game server settings).
if let (Some(u), Some(ip), "launch") = (&user, &ip, kind) {
if let (Some(ip), "launch") = (&ip, kind) {
sqlx::query("INSERT INTO launcher_sessions (user_id, ip, created_at) VALUES (?, ?, ?)")
.bind(u.id)
.bind(user.id)
.bind(ip)
.bind(crate::db::now())
.execute(&state.db)
@@ -151,10 +177,11 @@ pub async fn event(
let cutoff = (chrono::Utc::now() - chrono::Duration::days(2)).to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
sqlx::query("DELETE FROM launcher_sessions WHERE created_at < ?").bind(cutoff).execute(&state.db).await?;
}
let name = user.map(|u| u.username).or(ev.username).map(|n| n.chars().take(32).collect::<String>());
sqlx::query("INSERT INTO events (instance_id, username, kind, created_at) VALUES (?, ?, ?, ?)")
// Identity comes exclusively from the verified bearer token.
sqlx::query("INSERT INTO events (instance_id, username, uuid, kind, created_at) VALUES (?, ?, ?, ?, ?)")
.bind(ev.instance_id.chars().take(64).collect::<String>())
.bind(name)
.bind(&user.username)
.bind(&user.uuid)
.bind(kind)
.bind(crate::db::now())
.execute(&state.db)