mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
fix(multiplayer): validate timestamp calendar
This commit is contained in:
@@ -305,7 +305,31 @@ static func is_valid_rfc3339_timestamp(value: String) -> bool:
|
|||||||
if value.is_empty():
|
if value.is_empty():
|
||||||
return false
|
return false
|
||||||
var timestamp_pattern := RegEx.create_from_string("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$")
|
var timestamp_pattern := RegEx.create_from_string("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$")
|
||||||
return timestamp_pattern.search(value) != null
|
if timestamp_pattern.search(value) == null:
|
||||||
|
return false
|
||||||
|
var year := int(value.substr(0, 4))
|
||||||
|
var month := int(value.substr(5, 2))
|
||||||
|
var day := int(value.substr(8, 2))
|
||||||
|
var hour := int(value.substr(11, 2))
|
||||||
|
var minute := int(value.substr(14, 2))
|
||||||
|
var second := int(value.substr(17, 2))
|
||||||
|
if month < 1 or month > 12 or hour > 23 or minute > 59 or second > 59:
|
||||||
|
return false
|
||||||
|
var days_in_month := [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||||
|
var leap_year := year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
|
||||||
|
if leap_year:
|
||||||
|
days_in_month[1] = 29
|
||||||
|
if day < 1 or day > days_in_month[month - 1]:
|
||||||
|
return false
|
||||||
|
var timezone_index := value.find("+", 19)
|
||||||
|
if timezone_index < 0:
|
||||||
|
timezone_index = value.find("-", 19)
|
||||||
|
if timezone_index >= 0:
|
||||||
|
var offset_hour := int(value.substr(timezone_index + 1, 2))
|
||||||
|
var offset_minute := int(value.substr(timezone_index + 4, 2))
|
||||||
|
if offset_hour > 23 or offset_minute > 59:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
static func is_valid_resource_id(value: String) -> bool:
|
static func is_valid_resource_id(value: String) -> bool:
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ func test_session_expiry_is_checked_at_the_boundary_and_fails_closed() -> void:
|
|||||||
assert_true(ControlPlaneClient.is_session_expired("1970-01-01T00:16:40Z", 1000), "session expires at the exact boundary")
|
assert_true(ControlPlaneClient.is_session_expired("1970-01-01T00:16:40Z", 1000), "session expires at the exact boundary")
|
||||||
assert_true(ControlPlaneClient.is_session_expired("not-a-timestamp", 1000), "malformed non-empty expiry fails closed")
|
assert_true(ControlPlaneClient.is_session_expired("not-a-timestamp", 1000), "malformed non-empty expiry fails closed")
|
||||||
assert_true(ControlPlaneClient.is_valid_rfc3339_timestamp("2026-08-31T12:00:00.123Z"), "fractional RFC3339 timestamp is accepted")
|
assert_true(ControlPlaneClient.is_valid_rfc3339_timestamp("2026-08-31T12:00:00.123Z"), "fractional RFC3339 timestamp is accepted")
|
||||||
|
assert_true(not ControlPlaneClient.is_valid_rfc3339_timestamp("2026-02-30T12:00:00Z"), "impossible calendar date is rejected")
|
||||||
|
assert_true(not ControlPlaneClient.is_valid_rfc3339_timestamp("2026-13-01T12:00:00Z"), "impossible month is rejected")
|
||||||
assert_true(not ControlPlaneClient.is_valid_rfc3339_timestamp("2026-08-31 12:00:00Z"), "space-separated timestamp is rejected")
|
assert_true(not ControlPlaneClient.is_valid_rfc3339_timestamp("2026-08-31 12:00:00Z"), "space-separated timestamp is rejected")
|
||||||
assert_true(not ControlPlaneClient.is_valid_rfc3339_timestamp("2026-08-31T12:00:00"), "timezone-less timestamp is rejected")
|
assert_true(not ControlPlaneClient.is_valid_rfc3339_timestamp("2026-08-31T12:00:00"), "timezone-less timestamp is rejected")
|
||||||
var valid_session := {"player_id": "player_1234567890", "access_token": "session-id:opaque-token", "expires_at": "2099-08-31T12:00:00Z"}
|
var valid_session := {"player_id": "player_1234567890", "access_token": "session-id:opaque-token", "expires_at": "2099-08-31T12:00:00Z"}
|
||||||
|
|||||||
@@ -1617,6 +1617,8 @@ The public `/api/v1` route adapters now reject non-opaque queue, proposal, assig
|
|||||||
|
|
||||||
The public queue adapter also rejects an explicitly supplied short or unsafe `ticket_id`; omitted IDs continue to be deterministically server-assigned for idempotent retries.
|
The public queue adapter also rejects an explicitly supplied short or unsafe `ticket_id`; omitted IDs continue to be deterministically server-assigned for idempotent retries.
|
||||||
|
|
||||||
|
RFC3339 validation now checks both wire syntax and calendar parseability, rejecting impossible dates before they can become epoch metadata.
|
||||||
|
|
||||||
Signed MatchNet claims now also require exact JSON string/integer types for every identity, protocol, expiry, slot, team, and generation field; string-number coercion is rejected before canonical signature verification.
|
Signed MatchNet claims now also require exact JSON string/integer types for every identity, protocol, expiry, slot, team, and generation field; string-number coercion is rejected before canonical signature verification.
|
||||||
|
|
||||||
Presentation progress: a shared `Game/themes/cosmic_clash_theme.tres` now gives the menu, lobby, matchmaking, and settings surfaces consistent button, input, option, and label styling. The custom-font portion of `TODO.md` remains open until a distributable font asset is selected.
|
Presentation progress: a shared `Game/themes/cosmic_clash_theme.tres` now gives the menu, lobby, matchmaking, and settings surfaces consistent button, input, option, and label styling. The custom-font portion of `TODO.md` remains open until a distributable font asset is selected.
|
||||||
|
|||||||
Reference in New Issue
Block a user