fix: prevent concurrent join token reuse

This commit is contained in:
Josh Creek
2026-09-01 08:41:38 +01:00
parent 68d832f7bc
commit 4b01b7fc88
3 changed files with 19 additions and 1 deletions
+15
View File
@@ -54,6 +54,7 @@ var local_player_name := "Player"
var join_authorisation := ""
var require_join_authorisation := false
var _allowed_join_authorisations: Dictionary = {}
var _active_join_peers: Dictionary = {} # opaque authorisation -> peer_id
var _join_authorisation_context: Dictionary = {}
# Test hook (tests/match_net_smoke.gd): set false before connecting to
@@ -89,6 +90,7 @@ func _on_disconnected_from_server() -> void:
func _on_shutting_down() -> void:
roster.clear()
_allowed_join_authorisations.clear()
_active_join_peers.clear()
_join_authorisation_context.clear()
require_join_authorisation = false
@@ -118,6 +120,10 @@ func _on_peer_disconnected(peer_id: int) -> void:
func _remove_player(peer_id: int) -> void:
for token in _active_join_peers.keys():
if int(_active_join_peers[token]) == peer_id:
_active_join_peers.erase(token)
break
if not roster.has(peer_id):
return
roster.erase(peer_id)
@@ -185,6 +191,9 @@ func _hello(protocol_version: int, tick_hz: int, player_name: String, supplied_j
if require_join_authorisation and not _valid_join_authorisation(supplied_join_authorisation):
await _reject(peer_id, "join authorisation rejected")
return
if require_join_authorisation and _active_join_peers.has(supplied_join_authorisation):
await _reject(peer_id, "join authorisation already in use")
return
if player_name.length() > MAX_INPUT_LENGTH:
await _reject(peer_id, "player name too long")
return
@@ -199,6 +208,8 @@ func _hello(protocol_version: int, tick_hz: int, player_name: String, supplied_j
var team := _pick_balanced_team()
roster[peer_id] = PlayerInfo.new(peer_id, clean_name, team, false)
if require_join_authorisation:
_active_join_peers[supplied_join_authorisation] = peer_id
player_joined.emit(peer_id, clean_name) # local: the broadcast below is call_remote, never loops back to the server itself
_welcome.rpc_id(peer_id)
_player_joined.rpc(peer_id, clean_name, team, false) # broadcast, includes the new peer itself
@@ -229,6 +240,10 @@ func _valid_join_authorisation(token: String) -> bool:
and expiry > Time.get_unix_time_from_system()
func is_join_authorisation_active(token: String) -> bool:
return not token.is_empty() and _active_join_peers.has(token)
# Strips control/formatting characters (so a name can't corrupt a log line
# or blow out UI layout with e.g. embedded newlines) and clamps to display
# length. Input is already bounded to MAX_INPUT_LENGTH by the caller before
+3
View File
@@ -54,3 +54,6 @@ func test_allocated_join_authorisation_is_allowlisted_and_bound_to_server() -> v
wrong_claims["ServerID"] = "other-server"
var wrong_token := Marshalls.raw_to_base64(JSON.stringify({"Authorisation": wrong_claims, "Signature": "trusted-signature"}).to_utf8_buffer())
assert_true(not match_net._valid_join_authorisation(wrong_token), "wrong server claim is rejected")
assert_true(not match_net.is_join_authorisation_active(token), "validated token is not active before admission")
match_net._active_join_peers[token] = 42
assert_true(match_net.is_join_authorisation_active(token), "active token is visible to the duplicate-admission guard")