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

+81
View File
@@ -0,0 +1,81 @@
mod common;
use common::*;
#[tokio::test]
async fn renaming_preserves_uuid_stats_and_reserves_names() {
let t = setup().await;
let admin = t.login("admin", "supersecret").await;
let (_, created) = t.call("POST", "/api/admin/users", Some(&admin), Some(json!({"username":"Steve", "password":"password123"}))).await;
let id = created["id"].as_i64().unwrap();
let uuid = t.uuid("Steve").await;
let (_, auth) = t.call("POST", "/api/v1/auth/login", None, Some(json!({"username":"Steve", "password":"password123"}))).await;
let old_token = auth["token"].as_str().unwrap();
let old_game_token = auth["yggdrasil"]["access_token"].as_str().unwrap();
let (s, _) = t.call("PUT", "/api/v1/account/username", Some(old_token), Some(json!({"username":"Alex", "password":"wrong"}))).await;
assert_eq!(s, StatusCode::UNAUTHORIZED);
let (_, server) = t.call("POST", "/api/admin/servers", Some(&admin), Some(json!({"name":"Survival"}))).await;
let server_id = server["server"]["id"].as_i64().unwrap();
t.call(
"POST",
"/api/server/v1/sync",
server["token"].as_str(),
Some(json!({"stats":[{"uuid":uuid,"name":"Steve","playtime_secs":123}]})),
)
.await;
let (s, renamed) = t
.call(
"PUT",
"/api/v1/account/username",
Some(old_token),
Some(json!({"username":"Alex", "password":"password123", "uuid":"spoofed"})),
)
.await;
assert_eq!(s, StatusCode::OK, "{renamed}");
assert_eq!(renamed["user"]["uuid"], uuid);
assert_eq!(renamed["user"]["id"], id);
assert_eq!(renamed["user"]["username"], "Alex");
let (s, _) = t.call("GET", "/api/v1/auth/me", Some(old_token), None).await;
assert_eq!(s, StatusCode::UNAUTHORIZED);
let (s, _) = t.call("POST", "/api/yggdrasil/authserver/validate", None, Some(json!({"accessToken":old_game_token}))).await;
assert_eq!(s, StatusCode::FORBIDDEN);
let (s, _) = t.call("POST", "/api/admin/users", Some(&admin), Some(json!({"username":"steve", "password":"password123"}))).await;
assert_eq!(s, StatusCode::CONFLICT);
let (_, details) = t.call("GET", &format!("/api/admin/servers/{server_id}"), Some(&admin), None).await;
assert_eq!(details["leaderboard"][0]["uuid"], uuid);
assert_eq!(details["leaderboard"][0]["playtime_secs"], 123);
assert!(sqlx::query("UPDATE users SET uuid=? WHERE id=?")
.bind(uuid::Uuid::new_v4().to_string())
.bind(id)
.execute(&t.db)
.await
.is_err());
let fresh = renamed["token"].as_str().unwrap();
let (_, profile) = t.call("GET", "/api/v1/account/profile", Some(fresh), None).await;
assert_eq!(profile["uuid"], uuid);
// Deleting an account never releases its historical identity to a new user.
t.call("DELETE", &format!("/api/admin/users/{id}"), Some(&admin), None).await;
let (s, _) = t.call("POST", "/api/admin/users", Some(&admin), Some(json!({"username":"Alex", "password":"password123"}))).await;
assert_eq!(s, StatusCode::CONFLICT);
}
#[tokio::test]
async fn activity_cannot_impersonate_another_account() {
let t = setup().await;
let admin = t.login("admin", "supersecret").await;
let body = json!({"kind":"launcher_open","instance_id":"", "username":"SomeoneElse"});
assert_eq!(t.call("POST", "/api/v1/launcher/events", None, Some(body.clone())).await.0, StatusCode::UNAUTHORIZED);
assert_eq!(t.call("POST", "/api/v1/launcher/events", Some(&admin), Some(body)).await.0, StatusCode::OK);
let (_, rows) = t.call("GET", "/api/admin/activity?source=launcher", Some(&admin), None).await;
assert_eq!(rows[0]["name"], "admin");
assert_eq!(rows[0]["uuid"], t.uuid("admin").await);
assert_eq!(t.call("GET", "/api/admin/activity", None, None).await.0, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn new_accounts_are_random_and_old_uuids_stay_fixed() {
let t = setup().await;
let id = t.uuid("admin").await;
assert_eq!(uuid::Uuid::parse_str(&id).unwrap().get_version_num(), 4);
assert_ne!(id, scopenet_shared::offline_uuid("admin"));
assert!(sqlx::query("UPDATE users SET uuid='' WHERE username='admin'").execute(&t.db).await.is_err());
}