94 lines
4.8 KiB
Markdown
94 lines
4.8 KiB
Markdown
# ADR 0005: authenticated session lease and presence lifecycle
|
|
|
|
- Status: Accepted
|
|
- Date: 2026-07-16
|
|
- Tracking: #7
|
|
|
|
## Context
|
|
|
|
A host needs to publish a player-facing session without letting an HTTP request
|
|
claim a public endpoint or remain visible after the gameplay socket disappears.
|
|
Registration retries must be safe, credentials must remain opaque, and policy or
|
|
ownership checks cannot race state mutation.
|
|
|
|
## Decision
|
|
|
|
The four host HTTP operations require `Authorization: Bearer <publisher credential>`.
|
|
The signed principal supplies the authoritative game, environment, publisher trust
|
|
mode, subject, and allowed regions. Request fields never widen that scope. Creation
|
|
and update apply the enabled `GamePolicy` to exact protocol, region, visibility,
|
|
bounded display/build/capacity values, and the allowlisted metadata schema.
|
|
|
|
Capacity reported by a host is advisory directory information. Rendezvous bounds
|
|
and publishes it but never treats it as final admission authority; the game host
|
|
still decides identity, bans, reserved slots, and whether a connection may join.
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> AwaitingPresence: authorized register
|
|
AwaitingPresence --> Listed: valid host UDP presence
|
|
Listed --> AwaitingPresence: presence deadline passes
|
|
AwaitingPresence --> AwaitingPresence: lease renew or data update
|
|
Listed --> Listed: lease renew, data update, or presence refresh
|
|
AwaitingPresence --> Removed: lease expiry or delete
|
|
Listed --> Removed: lease expiry or delete
|
|
Removed --> [*]
|
|
```
|
|
|
|
Registration returns a listing ID, lease ID/token, host-presence handle/capability,
|
|
lease expiry, a 30-second renewal suggestion, and a 10-second presence-refresh
|
|
suggestion. The authoritative ceilings remain 60 seconds for the lease and 20
|
|
seconds for presence. Timing suggestions are server-controlled, not client-selected.
|
|
|
|
The lease token and presence capability are 256-bit opaque values derived with
|
|
HMAC-SHA256 from an in-memory per-process secret, a purpose label, the publisher
|
|
subject, the idempotency key, a canonical request fingerprint, and a random
|
|
per-registration derivation salt. Opaque IDs use separate purpose labels. Exact
|
|
retries read the retained non-secret salt and therefore reproduce the original
|
|
response without retaining plaintext credentials. Once the bounded idempotency
|
|
record expires, a new salt rotates IDs and capabilities so an old token cannot
|
|
regain authority. Metadata order is canonicalized before fingerprinting. The store
|
|
retains the salt and only a second keyed fingerprint of each token. Restart rotates
|
|
the derivation secret while the matching ephemeral state disappears.
|
|
|
|
Renew, update, and delete require both the same publisher subject and the lease
|
|
capability. Cross-owner or wrong-capability access returns the same not-found shape.
|
|
Update may change display name, build label, advisory capacity, and metadata only;
|
|
game, environment, region, protocol, visibility, trust mode, and opaque IDs remain
|
|
canonical. Delete is idempotent and does not reveal whether another publisher owns
|
|
the supplied ID.
|
|
|
|
### UDP presence
|
|
|
|
Only a structurally valid frozen `HostPresence` envelope or native LiteNetLib
|
|
host-presence request with the issued capability can refresh presence. The public
|
|
endpoint is the UDP packet's observed source on the host's gameplay socket; the
|
|
HTTP API never accepts one. The bounded local candidate comes from the authenticated
|
|
packet. Invalid or unknown inputs receive no response. ADR 0009 defines the later
|
|
attempt-role use of frozen `ClientPresence` and native host/client requests.
|
|
Presence expiry demotes public visibility but keeps the lease, so the same handle
|
|
can restore visibility without changing session identity.
|
|
|
|
Public listing responses contain bounded listing data only. They never contain
|
|
public/local endpoints, lease tokens, presence capabilities, fingerprints, store
|
|
keys, or canonical player identity.
|
|
|
|
## Failure semantics
|
|
|
|
- malformed or policy-invalid fields return a stable typed `InvalidRequest`;
|
|
- an unsupported gameplay protocol returns `IncompatibleProtocol`;
|
|
- missing/invalid publisher authentication returns `AuthenticationRequired`;
|
|
- cross-scope authorization returns `Forbidden` without resource disclosure;
|
|
- wrong owner/capability or expired state returns the tenant-hidden `NotFound`;
|
|
- idempotency reuse with changed input returns `Conflict`;
|
|
- publisher/global exhaustion returns `CapacityExceeded`; and
|
|
- drain or loss of atomic state returns `ServiceUnavailable` and authorizes no join.
|
|
|
|
## Consequences
|
|
|
|
- HTTP registration alone can never make a public session browseable.
|
|
- Plaintext session capabilities are returned to the intended host but are not
|
|
retained, logged, included in public listing DTOs, or exported as metrics.
|
|
- Re-registration after restart is the recovery path; there is no durable session
|
|
identity or gameplay state in Rendezvous.
|