mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 17:43:42 +00:00
feat: expose validated assignment endpoints
This commit is contained in:
+15
-1
@@ -11,6 +11,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -58,6 +59,7 @@ type AssignmentView struct {
|
||||
ProtocolVersion int `json:"protocol_version"`
|
||||
Transport string `json:"transport"`
|
||||
JoinAuthorisation string `json:"join_authorisation"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
// Revision is routing metadata for the event stream, not part of the v1
|
||||
// assignment response. Keeping it alongside the durable view prevents the
|
||||
// REST recovery boundary from emitting a synthetic revision zero.
|
||||
@@ -509,7 +511,7 @@ func (s *Service) assignment(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusNotFound, "not_found")
|
||||
return
|
||||
}
|
||||
if view.ServerID == "" || view.Slot < 0 || view.Slot > 5 || view.ProtocolVersion < 1 || (view.Transport != "enet" && view.Transport != "steam_sdr") || view.JoinAuthorisation == "" || view.ExpiresAt.IsZero() || !now.Before(view.ExpiresAt) {
|
||||
if view.ServerID == "" || view.Slot < 0 || view.Slot > 5 || view.ProtocolVersion < 1 || (view.Transport != "enet" && view.Transport != "steam_sdr") || view.JoinAuthorisation == "" || !validAssignmentEndpoint(view.Endpoint) || view.ExpiresAt.IsZero() || !now.Before(view.ExpiresAt) {
|
||||
writeError(w, http.StatusServiceUnavailable, "assignment_unavailable")
|
||||
return
|
||||
}
|
||||
@@ -517,6 +519,18 @@ func (s *Service) assignment(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, view)
|
||||
}
|
||||
|
||||
func validAssignmentEndpoint(endpoint string) bool {
|
||||
if endpoint == "" || strings.ContainsAny(endpoint, "/?#") {
|
||||
return false
|
||||
}
|
||||
host, portText, err := net.SplitHostPort(endpoint)
|
||||
if err != nil || host == "" {
|
||||
return false
|
||||
}
|
||||
port, err := strconv.Atoi(portText)
|
||||
return err == nil && port >= 1 && port <= 65535
|
||||
}
|
||||
|
||||
func assignmentChangedEvent(view AssignmentView, now time.Time) ControlPlaneEvent {
|
||||
return ControlPlaneEvent{Event: "assignment_changed", Revision: view.Revision, ResourceID: view.MatchID, OccurredAt: now, MatchID: view.MatchID, ServerID: view.ServerID, PlayerID: view.PlayerID}
|
||||
}
|
||||
|
||||
@@ -939,7 +939,7 @@ func TestAssignmentRecoveryIsPlayerScopedAndRejectsExpiredOrMismatchedViews(t *t
|
||||
}
|
||||
current := now
|
||||
service := &Service{Sessions: sessions, Now: func() time.Time { return current }, Assignment: func(_ context.Context, _ string, matchID string, _ time.Time) (AssignmentView, error) {
|
||||
return AssignmentView{MatchID: matchID, ServerID: "server-1", PlayerID: "player-a", Slot: 2, ExpiresAt: now.Add(time.Minute), ProtocolVersion: 1, Transport: "enet", JoinAuthorisation: "signed-join"}, nil
|
||||
return AssignmentView{MatchID: matchID, ServerID: "server-1", PlayerID: "player-a", Slot: 2, ExpiresAt: now.Add(time.Minute), ProtocolVersion: 1, Transport: "enet", Endpoint: "127.0.0.1:30001", JoinAuthorisation: "signed-join"}, nil
|
||||
}}
|
||||
server := httptest.NewServer(service.Handler())
|
||||
defer server.Close()
|
||||
@@ -999,3 +999,16 @@ func TestAssignmentEventUsesAuthoritativeRevisionWithoutChangingResponseShape(t
|
||||
t.Fatalf("assignment response leaked event revision: %s", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignmentEndpointValidationRejectsAmbiguousOrUnsafeEndpoints(t *testing.T) {
|
||||
for _, endpoint := range []string{"", "127.0.0.1", "127.0.0.1:0", "127.0.0.1:70000", "https://127.0.0.1:1", "127.0.0.1:1/path"} {
|
||||
if validAssignmentEndpoint(endpoint) {
|
||||
t.Fatalf("unsafe endpoint accepted: %q", endpoint)
|
||||
}
|
||||
}
|
||||
for _, endpoint := range []string{"127.0.0.1:1", "example.invalid:65535", "[2001:db8::1]:31001"} {
|
||||
if !validAssignmentEndpoint(endpoint) {
|
||||
t.Fatalf("valid endpoint rejected: %q", endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func AssignmentProviderFromStore(db *sql.DB) AssignmentProvider {
|
||||
ProtocolVersion: assignment.ProtocolVersion,
|
||||
Transport: assignment.Transport,
|
||||
JoinAuthorisation: assignment.JoinAuthorisation,
|
||||
Endpoint: assignment.Endpoint,
|
||||
Revision: assignment.Revision,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user