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:
commit
a1f19e86c6
74 files changed
+2108
-95
No files matched your search
@@ -27,7 +27,7 @@ async fn admin_only_routes_are_guarded() {
|
||||
let admin = t.login("admin", "supersecret").await;
|
||||
let (s, v) = t.call("POST", "/api/admin/users", Some(&admin), Some(json!({"username": "Steve", "password": "password123"}))).await;
|
||||
assert_eq!(s, StatusCode::OK, "{v}");
|
||||
assert_eq!(v["uuid"], scopenet_shared::offline_uuid("Steve"));
|
||||
assert_eq!(uuid::Uuid::parse_str(v["uuid"].as_str().unwrap()).unwrap().get_version_num(), 4);
|
||||
|
||||
let player = t.login("steve", "password123").await; // usernames are case-insensitive
|
||||
let (s, _) = t.call("GET", "/api/admin/users", Some(&player), None).await;
|
||||
|
||||
@@ -17,6 +17,7 @@ pub fn test_keys() -> Arc<Keys> {
|
||||
|
||||
pub struct TestApp {
|
||||
pub router: axum::Router,
|
||||
pub db: sqlx::SqlitePool,
|
||||
_dir: tempfile::TempDir,
|
||||
}
|
||||
|
||||
@@ -32,14 +33,18 @@ pub async fn setup() -> TestApp {
|
||||
curseforge_api_key: None,
|
||||
max_upload_mb: 64,
|
||||
public_url: Some("https://panel.test".into()),
|
||||
trusted_proxies: vec!["127.0.0.1".parse().unwrap()],
|
||||
};
|
||||
let pool = db::connect_memory().await.unwrap();
|
||||
let state = build_state_with_keys(cfg, pool, test_keys()).await.unwrap();
|
||||
bootstrap_admin(&state).await.unwrap();
|
||||
TestApp { router: app(state), _dir: dir }
|
||||
TestApp { db: state.db.clone(), router: app(state), _dir: dir }
|
||||
}
|
||||
|
||||
impl TestApp {
|
||||
pub async fn uuid(&self, name: &str) -> String {
|
||||
sqlx::query_scalar("SELECT uuid FROM users WHERE username = ?").bind(name).fetch_one(&self.db).await.unwrap()
|
||||
}
|
||||
pub async fn call(&self, method: &str, uri: &str, token: Option<&str>, body: Option<Value>) -> (StatusCode, Value) {
|
||||
let mut req = Request::builder().method(method).uri(uri);
|
||||
if let Some(t) = token {
|
||||
@@ -52,7 +57,10 @@ impl TestApp {
|
||||
self.send(req).await
|
||||
}
|
||||
|
||||
pub async fn send(&self, req: Request<Body>) -> (StatusCode, Value) {
|
||||
pub async fn send(&self, mut req: Request<Body>) -> (StatusCode, Value) {
|
||||
if req.extensions().get::<axum::extract::ConnectInfo<std::net::SocketAddr>>().is_none() {
|
||||
req.extensions_mut().insert(axum::extract::ConnectInfo("127.0.0.1:12345".parse::<std::net::SocketAddr>().unwrap()));
|
||||
}
|
||||
let resp = self.router.clone().oneshot(req).await.unwrap();
|
||||
let status = resp.status();
|
||||
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX).await.unwrap();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -20,8 +20,8 @@ async fn create_server(t: &TestApp, admin: &str, body: Value) -> (i64, String) {
|
||||
(v["server"]["id"].as_i64().unwrap(), token)
|
||||
}
|
||||
|
||||
fn steve() -> String {
|
||||
scopenet_shared::offline_uuid("Steve")
|
||||
async fn steve(t: &TestApp) -> String {
|
||||
t.uuid("Steve").await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -80,7 +80,7 @@ async fn login_rules() {
|
||||
let (_, open) = create_server(&t, &admin, json!({"name": "Open"})).await;
|
||||
let v = login(open.clone(), "00000000-0000-0000-0000-000000000001".into(), "Stranger", None).await;
|
||||
assert_eq!(v["allowed"], true, "{v}");
|
||||
let v = login(open.clone(), steve().replace('-', ""), "Steve", None).await;
|
||||
let v = login(open.clone(), steve(&t).await.replace('-', ""), "Steve", None).await;
|
||||
assert_eq!(v["allowed"], true);
|
||||
assert_eq!(v["account"]["username"], "Steve");
|
||||
|
||||
@@ -94,20 +94,20 @@ async fn login_rules() {
|
||||
let (s, v) = t.call("POST", "/api/admin/servers", Some(&admin), Some(json!({"name": "Staff", "access": "groups"}))).await;
|
||||
assert_eq!(s, StatusCode::BAD_REQUEST, "{v}");
|
||||
let (_, staff) = create_server(&t, &admin, json!({"name": "Staff", "access": "groups", "allowed_groups": ["builders"]})).await;
|
||||
let v = login(staff.clone(), steve(), "Steve", None).await;
|
||||
let v = login(staff.clone(), steve(&t).await, "Steve", None).await;
|
||||
assert_eq!(v["allowed"], false);
|
||||
let admin_uuid = scopenet_shared::offline_uuid("admin");
|
||||
let admin_uuid = t.uuid("admin").await;
|
||||
let v = login(staff.clone(), admin_uuid, "admin", None).await;
|
||||
assert_eq!(v["allowed"], true, "{v}");
|
||||
t.call("POST", "/api/admin/groups", Some(&admin), Some(json!({"name": "builders"}))).await;
|
||||
let (s, v) = t.call("PATCH", &format!("/api/admin/users/{steve_id}"), Some(&admin), Some(json!({"groups": ["builders"]}))).await;
|
||||
assert_eq!(s, StatusCode::OK, "{v}");
|
||||
let v = login(staff.clone(), steve(), "Steve", None).await;
|
||||
let v = login(staff.clone(), steve(&t).await, "Steve", None).await;
|
||||
assert_eq!(v["allowed"], true, "{v}");
|
||||
|
||||
// Require launcher: a launch from the same IP is needed.
|
||||
let (_, strict) = create_server(&t, &admin, json!({"name": "Strict", "require_launcher": true})).await;
|
||||
let v = login(strict.clone(), steve(), "Steve", Some("203.0.113.9")).await;
|
||||
let v = login(strict.clone(), steve(&t).await, "Steve", Some("203.0.113.9")).await;
|
||||
assert_eq!(v["allowed"], false);
|
||||
assert!(v["message"].as_str().unwrap().contains("launcher"));
|
||||
let player = t.login("Steve", "password123").await;
|
||||
@@ -121,9 +121,9 @@ async fn login_rules() {
|
||||
.unwrap();
|
||||
let (s, v) = t.send(req).await;
|
||||
assert_eq!(s, StatusCode::OK, "{v}");
|
||||
let v = login(strict.clone(), steve(), "Steve", Some("203.0.113.9")).await;
|
||||
let v = login(strict.clone(), steve(&t).await, "Steve", Some("203.0.113.9")).await;
|
||||
assert_eq!(v["allowed"], true, "{v}");
|
||||
let v = login(strict.clone(), steve(), "Steve", Some("198.51.100.1")).await;
|
||||
let v = login(strict.clone(), steve(&t).await, "Steve", Some("198.51.100.1")).await;
|
||||
assert_eq!(v["allowed"], false);
|
||||
|
||||
// Disabled accounts see the reason.
|
||||
@@ -134,7 +134,7 @@ async fn login_rules() {
|
||||
Some(json!({"status": "disabled", "status_reason": "Griefing"})),
|
||||
)
|
||||
.await;
|
||||
let v = login(open, steve(), "Steve", None).await;
|
||||
let v = login(open, steve(&t).await, "Steve", None).await;
|
||||
assert_eq!(v["allowed"], false);
|
||||
assert!(v["message"].as_str().unwrap().contains("Griefing"));
|
||||
}
|
||||
@@ -148,11 +148,11 @@ async fn sync_tracks_players_stats_and_kicks() {
|
||||
|
||||
let sync = json!({
|
||||
"tps": 19.8,
|
||||
"online": [{"uuid": steve().replace('-', ""), "name": "Steve"}],
|
||||
"stats": [{"uuid": steve(), "name": "Steve", "playtime_secs": 30, "joins": 1, "blocks_broken": 12, "deaths": 1}],
|
||||
"online": [{"uuid": steve(&t).await.replace('-', ""), "name": "Steve"}],
|
||||
"stats": [{"uuid": steve(&t).await, "name": "Steve", "playtime_secs": 30, "joins": 1, "blocks_broken": 12, "deaths": 1}],
|
||||
"events": [
|
||||
{"uuid": steve(), "name": "Steve", "kind": "join"},
|
||||
{"uuid": steve(), "name": "Steve", "kind": "death", "detail": "Steve fell from a high place"}
|
||||
{"uuid": steve(&t).await, "name": "Steve", "kind": "join"},
|
||||
{"uuid": steve(&t).await, "name": "Steve", "kind": "death", "detail": "Steve fell from a high place"}
|
||||
]
|
||||
});
|
||||
let (s, v) = t.call("POST", "/api/server/v1/sync", Some(&token), Some(sync)).await;
|
||||
@@ -164,7 +164,7 @@ async fn sync_tracks_players_stats_and_kicks() {
|
||||
"POST",
|
||||
"/api/server/v1/sync",
|
||||
Some(&token),
|
||||
Some(json!({"tps": 20.0, "online": [{"uuid": steve(), "name": "Steve"}], "stats": [{"uuid": steve(), "name": "Steve", "playtime_secs": 30, "blocks_broken": 3}]})),
|
||||
Some(json!({"tps": 20.0, "online": [{"uuid": steve(&t).await, "name": "Steve"}], "stats": [{"uuid": steve(&t).await, "name": "Steve", "playtime_secs": 30, "blocks_broken": 3}]})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(s, StatusCode::OK);
|
||||
@@ -201,8 +201,9 @@ async fn sync_tracks_players_stats_and_kicks() {
|
||||
Some(json!({"status": "disabled", "status_reason": "Cheating"})),
|
||||
)
|
||||
.await;
|
||||
let (_, v) = t.call("POST", "/api/server/v1/sync", Some(&token), Some(json!({"online": [{"uuid": steve(), "name": "Steve"}]}))).await;
|
||||
assert_eq!(v["kick"][0]["uuid"], steve());
|
||||
let (_, v) =
|
||||
t.call("POST", "/api/server/v1/sync", Some(&token), Some(json!({"online": [{"uuid": steve(&t).await, "name": "Steve"}]}))).await;
|
||||
assert_eq!(v["kick"][0]["uuid"], steve(&t).await);
|
||||
assert!(v["kick"][0]["message"].as_str().unwrap().contains("Cheating"));
|
||||
|
||||
// Leaving empties the online list; deleting the server removes its data.
|
||||
@@ -214,3 +215,53 @@ async fn sync_tracks_players_stats_and_kicks() {
|
||||
let (s, _) = t.call("POST", "/api/server/v1/sync", Some(&token), Some(json!({}))).await;
|
||||
assert_eq!(s, StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unknown_uuid_cannot_claim_a_members_name_or_skip_launcher_check() {
|
||||
let t = setup().await;
|
||||
let (admin, _) = admin_and_player(&t).await;
|
||||
let (_, token) = create_server(&t, &admin, json!({"name":"Private", "access":"members"})).await;
|
||||
let (_, verdict) =
|
||||
t.call("POST", "/api/server/v1/login", Some(&token), Some(json!({"uuid":uuid::Uuid::new_v4(),"name":"Steve"}))).await;
|
||||
assert_eq!(verdict["allowed"], false);
|
||||
let (_, strict) = create_server(&t, &admin, json!({"name":"Launcher", "require_launcher":true})).await;
|
||||
for body in [json!({"uuid":steve(&t).await,"name":"Steve"}), json!({"uuid":uuid::Uuid::new_v4(),"name":"Stranger","ip":"203.0.113.9"})]
|
||||
{
|
||||
let (_, verdict) = t.call("POST", "/api/server/v1/login", Some(&strict), Some(body)).await;
|
||||
assert_eq!(verdict["allowed"], false);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn retry_does_not_duplicate_stats_or_events() {
|
||||
let t = setup().await;
|
||||
let (admin, _) = admin_and_player(&t).await;
|
||||
let (id, token) = create_server(&t, &admin, json!({"name":"Retry"})).await;
|
||||
let body = json!({"batch_id":uuid::Uuid::new_v4(), "stats":[{"uuid":steve(&t).await,"name":"Steve","playtime_secs":30}],"events":[{"kind":"join","name":"Steve","uuid":steve(&t).await}]});
|
||||
for _ in 0..2 {
|
||||
assert_eq!(t.call("POST", "/api/server/v1/sync", Some(&token), Some(body.clone())).await.0, StatusCode::OK);
|
||||
}
|
||||
let (_, detail) = t.call("GET", &format!("/api/admin/servers/{id}"), Some(&admin), None).await;
|
||||
assert_eq!(detail["leaderboard"][0]["playtime_secs"], 30);
|
||||
assert_eq!(detail["events"].as_array().unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn untrusted_forwarded_header_cannot_forge_a_launcher_ip() {
|
||||
let t = setup().await;
|
||||
let (admin, _) = admin_and_player(&t).await;
|
||||
let token = t.login("Steve", "password123").await;
|
||||
let mut request = Request::post("/api/v1/launcher/events")
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.header("content-type", "application/json")
|
||||
.header("x-forwarded-for", "203.0.113.9")
|
||||
.body(Body::from(json!({"kind":"launch","instance_id":"survival"}).to_string()))
|
||||
.unwrap();
|
||||
request.extensions_mut().insert(axum::extract::ConnectInfo("198.51.100.1:12345".parse::<std::net::SocketAddr>().unwrap()));
|
||||
assert_eq!(t.send(request).await.0, StatusCode::OK);
|
||||
let (_, strict) = create_server(&t, &admin, json!({"name":"Strict", "require_launcher":true})).await;
|
||||
let (_, verdict) = t
|
||||
.call("POST", "/api/server/v1/login", Some(&strict), Some(json!({"uuid":steve(&t).await,"name":"Steve","ip":"203.0.113.9"})))
|
||||
.await;
|
||||
assert_eq!(verdict["allowed"], false);
|
||||
}
|
||||
@@ -42,8 +42,8 @@ async fn with_player(t: &TestApp) -> String {
|
||||
admin
|
||||
}
|
||||
|
||||
fn steve_id() -> String {
|
||||
scopenet_shared::offline_uuid("Steve").replace('-', "")
|
||||
async fn steve_id(t: &TestApp) -> String {
|
||||
t.uuid("Steve").await.replace('-', "")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -82,7 +82,7 @@ async fn full_authlib_flow() {
|
||||
.await;
|
||||
assert_eq!(s, StatusCode::OK, "{auth}");
|
||||
assert_eq!(auth["clientToken"], "ct1");
|
||||
assert_eq!(auth["selectedProfile"]["id"], steve_id());
|
||||
assert_eq!(auth["selectedProfile"]["id"], steve_id(&t).await);
|
||||
assert_eq!(auth["selectedProfile"]["name"], "Steve");
|
||||
assert_eq!(auth["availableProfiles"].as_array().unwrap().len(), 1);
|
||||
assert!(auth["user"]["id"].is_string());
|
||||
@@ -140,7 +140,7 @@ async fn full_authlib_flow() {
|
||||
"POST",
|
||||
&format!("{Y}/sessionserver/session/minecraft/join"),
|
||||
None,
|
||||
Some(json!({"accessToken": token, "selectedProfile": steve_id(), "serverId": "-4b1d2f"})),
|
||||
Some(json!({"accessToken": token, "selectedProfile": steve_id(&t).await, "serverId": "-4b1d2f"})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(s, StatusCode::NO_CONTENT);
|
||||
@@ -157,7 +157,7 @@ async fn full_authlib_flow() {
|
||||
let (s, joined) =
|
||||
t.call("GET", &format!("{Y}/sessionserver/session/minecraft/hasJoined?username=Steve&serverId=-4b1d2f"), None, None).await;
|
||||
assert_eq!(s, StatusCode::OK, "{joined}");
|
||||
assert_eq!(joined["id"], steve_id());
|
||||
assert_eq!(joined["id"], steve_id(&t).await);
|
||||
let textures = joined["properties"].as_array().unwrap().iter().find(|p| p["name"] == "textures").unwrap();
|
||||
let value = textures["value"].as_str().unwrap();
|
||||
assert!(
|
||||
@@ -176,13 +176,13 @@ async fn full_authlib_flow() {
|
||||
assert_eq!(s, StatusCode::NO_CONTENT, "wrong server id");
|
||||
|
||||
// Profile lookups.
|
||||
let (_, unsigned) = t.call("GET", &format!("{Y}/sessionserver/session/minecraft/profile/{}", steve_id()), None, None).await;
|
||||
let (_, unsigned) = t.call("GET", &format!("{Y}/sessionserver/session/minecraft/profile/{}", steve_id(&t).await), None, None).await;
|
||||
assert!(unsigned["properties"][0].get("signature").is_none());
|
||||
let (_, signed) =
|
||||
t.call("GET", &format!("{Y}/sessionserver/session/minecraft/profile/{}?unsigned=false", steve_id()), None, None).await;
|
||||
t.call("GET", &format!("{Y}/sessionserver/session/minecraft/profile/{}?unsigned=false", steve_id(&t).await), None, None).await;
|
||||
assert!(signed["properties"][0]["signature"].is_string());
|
||||
let (_, found) = t.call("POST", &format!("{Y}/api/profiles/minecraft"), None, Some(json!(["steve", "nobody"]))).await;
|
||||
assert_eq!(found, json!([{"id": steve_id(), "name": "Steve"}]));
|
||||
assert_eq!(found, json!([{"id": steve_id(&t).await, "name": "Steve"}]));
|
||||
|
||||
// Refresh rotates the token.
|
||||
let (s, refreshed) =
|
||||
@@ -209,7 +209,7 @@ async fn launcher_login_returns_game_session() {
|
||||
let (s, v) = t.call("POST", "/api/v1/auth/login", None, Some(json!({"username": "Steve", "password": "password123"}))).await;
|
||||
assert_eq!(s, StatusCode::OK);
|
||||
let token = v["yggdrasil"]["access_token"].as_str().unwrap();
|
||||
assert_eq!(v["user"]["uuid"], scopenet_shared::offline_uuid("Steve"));
|
||||
assert_eq!(v["user"]["uuid"], t.uuid("Steve").await);
|
||||
let (s, _) = t.call("POST", &format!("{Y}/authserver/validate"), None, Some(json!({"accessToken": token}))).await;
|
||||
assert_eq!(s, StatusCode::NO_CONTENT);
|
||||
}
|
||||
@@ -255,7 +255,7 @@ async fn chat_certificates_are_signed_like_mojang() {
|
||||
// Rebuild the V2 payload the way Minecraft does and check the signature.
|
||||
let body: String = pem.lines().filter(|l| !l.starts_with("-----")).map(|l| l.trim()).collect();
|
||||
let der = b64(&body);
|
||||
let uuid = uuid::Uuid::parse_str(&scopenet_shared::offline_uuid("Steve")).unwrap();
|
||||
let uuid = uuid::Uuid::parse_str(&t.uuid("Steve").await).unwrap();
|
||||
let mut payload = uuid.as_bytes().to_vec();
|
||||
payload.extend_from_slice(&expires.timestamp_millis().to_be_bytes());
|
||||
payload.extend_from_slice(&der);
|
||||
@@ -278,7 +278,7 @@ async fn avatars_and_skin_validation() {
|
||||
let t = setup().await;
|
||||
with_player(&t).await;
|
||||
let panel = t.login("Steve", "password123").await;
|
||||
let (s, _) = t.call("GET", &format!("/api/v1/avatar/{}", steve_id()), None, None).await;
|
||||
let (s, _) = t.call("GET", &format!("/api/v1/avatar/{}", steve_id(&t).await), None, None).await;
|
||||
assert_eq!(s, StatusCode::NOT_FOUND, "no skin yet");
|
||||
|
||||
let (ct, body) = multipart(&[], ("bad.png", b"GIF89a not a png"));
|
||||
@@ -297,7 +297,7 @@ async fn avatars_and_skin_validation() {
|
||||
.body(Body::from(body))
|
||||
.unwrap();
|
||||
assert_eq!(t.send(req).await.0, StatusCode::OK);
|
||||
for id in [steve_id(), "Steve".to_string()] {
|
||||
for id in [steve_id(&t).await, "Steve".to_string()] {
|
||||
let resp =
|
||||
t.router.clone().oneshot(Request::get(format!("/api/v1/avatar/{id}?size=32")).body(Body::empty()).unwrap()).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
Reference in new issue
Block a user