67 lines
3.7 KiB
Markdown
67 lines
3.7 KiB
Markdown
# ADR 0008: scoped join attempts and one-time connection tickets
|
|
|
|
- Status: Accepted
|
|
- Date: 2026-07-16
|
|
- Tracking: #10
|
|
|
|
## Decision
|
|
|
|
Join creation is an unauthenticated public operation because v1 does not treat a
|
|
Rendezvous caller as game identity. The HTTP source address is normalized and
|
|
converted to a process-keyed opaque subject for idempotency and bounded policy
|
|
accounting; raw addresses and the derived subject are never returned or logged.
|
|
A successful request means only that this network client may try to connect to
|
|
this active session. It does not reserve capacity or grant gameplay admission.
|
|
|
|
Creation validates the v1 contract, caller idempotency key, enabled tenant policy,
|
|
exact gameplay protocol, listing scope, live lease, and fresh authenticated host
|
|
presence in one atomic store operation. A listing advertised as full remains
|
|
joinable because its player count is advisory and the game host owns the final
|
|
capacity, identity, ban, and admission decision.
|
|
|
|
Each attempt derives independent host-punch, client-punch, and connection-ticket
|
|
credentials plus opaque attempt and mediation IDs from a process-ephemeral HMAC
|
|
key, the client subject, the complete canonical request fingerprint, a fresh salt,
|
|
and a purpose/role label. Credentials are 32-byte base64url values (43 characters),
|
|
below both the 192-character Rendezvous capability ceiling and LiteNetLib's
|
|
256-character NAT token ceiling. State retains keyed credential fingerprints,
|
|
derivation inputs, and salt—not issued plaintext. All diagnostic string
|
|
representations redact credentials and derivation material.
|
|
|
|
The client receives only its punch capability. A host polls its own listing with
|
|
the lease token in `X-Rendezvous-Lease-Token` and receives only host-role
|
|
capabilities through a signed, listing-bound, five-minute cursor. Replaying an
|
|
identical join request returns the same live attempt; changing the request under
|
|
the same owner/key conflicts. A client may cancel with its punch capability in
|
|
`X-Rendezvous-Client-Punch-Capability`; cancellation atomically removes the
|
|
attempt. Listing deletion, expiry, revocation, or process restart removes every
|
|
associated attempt and credential fingerprint.
|
|
|
|
Endpoint binding remains role- and capability-specific. The first endpoint
|
|
observed for a role wins atomically; an exact UDP duplicate is idempotent, while
|
|
endpoint or role substitution is rejected. An introduction is consumable once
|
|
only after both roles bind, so concurrent attempts for the same listing cannot
|
|
cross-wire.
|
|
|
|
The connection ticket is distinct from both punch capabilities and is reproduced
|
|
only after introduction succeeds. Its window begins at that moment and lasts at
|
|
most 20 seconds without outliving the 30-second attempt. The server has an atomic
|
|
fingerprint-consumption seam for mediator tests and revocation. On the game host,
|
|
the SDK's bounded `ConnectionTicketValidator` stores a process-keyed digest,
|
|
accepts an exact ticket once under a lock, rejects altered/cross-attempt/expired/
|
|
revoked/replayed tickets, and zeroes retained digests and key material on disposal.
|
|
Issue #11 carries the ticket in the authenticated introduction; issue #12 wires
|
|
authorization and consumption into the caller-owned LiteNetLib coordinator.
|
|
|
|
## Consequences
|
|
|
|
- A join attempt is transport authorization, never proof of player identity or a
|
|
game slot.
|
|
- Network-address-derived subjects are process-local abuse/idempotency scopes,
|
|
not stable user identifiers; stronger authenticated player scopes require a
|
|
future game-owned identity contract.
|
|
- Cancellation after a ticket has reached a host must also revoke that host's
|
|
local validator entry; coordinator wiring owns that race in issue #12.
|
|
- Capability and ticket plaintext never enter browser results, state snapshots,
|
|
logs, metrics, or generated string representations.
|