mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
fix(multiplayer): validate allocated roster shape
This commit is contained in:
@@ -122,14 +122,23 @@ func configure_join_authorisations(tokens: Array, context: Dictionary, signing_k
|
||||
|
||||
func assigned_player_slots() -> Array:
|
||||
var result: Array = []
|
||||
var seen_identities := {}
|
||||
var seen_slots := {}
|
||||
for token in _allowed_join_authorisations.keys():
|
||||
var claims := _join_claims(String(token))
|
||||
if claims.is_empty():
|
||||
continue
|
||||
return []
|
||||
var identity := str(claims.get("PlayerID", ""))
|
||||
var team := int(claims.get("Team", -1))
|
||||
var slot := int(claims.get("Slot", -1))
|
||||
if identity.is_empty() or team < 0 or team >= TEAM_COUNT or slot < 0 or slot > 5 or slot / 3 != team or seen_identities.has(identity) or seen_slots.has(slot):
|
||||
return []
|
||||
seen_identities[identity] = true
|
||||
seen_slots[slot] = true
|
||||
result.append({
|
||||
"player_identity": str(claims.get("PlayerID", "")),
|
||||
"team": int(claims.get("Team", -1)),
|
||||
"slot": int(claims.get("Slot", -1)),
|
||||
"player_identity": identity,
|
||||
"team": team,
|
||||
"slot": slot,
|
||||
})
|
||||
result.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return int(a["slot"]) < int(b["slot"]))
|
||||
return result
|
||||
|
||||
@@ -78,7 +78,7 @@ func _ready() -> void:
|
||||
"server_id": String(config.get_value("server-id")),
|
||||
"protocol": str(NetCodec.PROTOCOL_VERSION),
|
||||
"protocol_version": NetCodec.PROTOCOL_VERSION,
|
||||
}, signing_key):
|
||||
}, signing_key) or MatchNet.assigned_player_slots().size() != roster_tokens.size():
|
||||
printerr("cosmic-clash-server: refusing to start with invalid join-authorisations-file")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
@@ -89,6 +89,19 @@ func test_allocated_join_authorisation_rejects_inconsistent_team_and_slot() -> v
|
||||
assert_true(not match_net._valid_join_authorisation(token), "a slot assigned to team 1 cannot claim team 0")
|
||||
|
||||
|
||||
func test_assigned_roster_rejects_duplicate_identity_or_slot_shape() -> void:
|
||||
var claims := {
|
||||
"MatchID": "match-1", "ServerID": "server-1", "PlayerID": "player-1",
|
||||
"SteamID": "steam-1", "Slot": 0, "Team": 0, "Protocol": "1",
|
||||
"Generation": 1, "ExpiresAt": "2099-08-31T12:00:00Z",
|
||||
}
|
||||
var first := Marshalls.raw_to_base64(JSON.stringify({"Authorisation": claims, "Signature": "one"}).to_utf8_buffer())
|
||||
var duplicate := Marshalls.raw_to_base64(JSON.stringify({"Authorisation": claims, "Signature": "two"}).to_utf8_buffer())
|
||||
var match_net := MatchNet.new()
|
||||
assert_true(match_net.configure_join_authorisations([first, duplicate], {"match_id": "match-1", "server_id": "server-1", "protocol": "1", "protocol_version": 1}), "duplicate fixture configures for structural inspection")
|
||||
assert_eq(match_net.assigned_player_slots().size(), 0, "duplicate identity/slot roster fails closed")
|
||||
|
||||
|
||||
func test_allocated_join_authorisation_verifies_canonical_hmac() -> void:
|
||||
# This envelope is generated from server/domain.JoinAuthorisationBytes with
|
||||
# HMAC-SHA256(test-key), proving the Godot verifier agrees with the Go
|
||||
|
||||
@@ -1404,3 +1404,5 @@ Allocated supervisor launch arguments now have a direct regression guard: author
|
||||
Read-only authenticated queue, proposal, assignment, legacy profile, and ranked-profile routes now emit lifecycle-safe observability events for successful, rejected, and not-found reads. An API regression exercises all five real HTTP routes and verifies the event set; event fields remain free of credentials. This closes the local read-route portion of task 8.44; metrics/traces export, dashboards, and alert routing remain operational work.
|
||||
|
||||
Allocated join admission now retains and applies the signed assignment’s authoritative team and global slot: peer order can no longer rebalance a valid allocation, and inconsistent team/slot claims are rejected before roster admission. The server exposes the verified assignment list for allocation-aware startup and uses the per-team spawn index derived from the assigned slot. Go verification is clean; Godot execution remains blocked by the documented macOS pre-test crash.
|
||||
|
||||
Allocated boot now also validates the complete signed roster shape before opening the gameplay endpoint: malformed claims, duplicate player identities, duplicate slots, and team/global-slot mismatches fail closed rather than leaving a partially usable server. The Godot `--check-only` attempt still reaches the known macOS renderer/ZSTD crash before script parsing, so this startup guard remains statically reviewed and covered by the existing signed-claim tests pending a working Godot runtime.
|
||||
|
||||
Reference in New Issue
Block a user