Skip to content

Publish training packs to PsyNet from rocket-sense#264

Open
colonelpanic8 wants to merge 1 commit into
masterfrom
training-publish
Open

Publish training packs to PsyNet from rocket-sense#264
colonelpanic8 wants to merge 1 commit into
masterfrom
training-publish

Conversation

@colonelpanic8

Copy link
Copy Markdown
Contributor

Adds server-side publishing of Rocket League custom training packs to PsyNet, so the browser stats player can publish a pack (via rocket-sense's existing auth) and get back the public share code — no game and no on-machine agent needed. Builds on the psynet + rlru crates (v0.1.34, crates.io) that already solved the PsyNet publish RPC and Epic token flow.

Endpoints (all /api/v1, bearer-authed)

  • GET /me/epic-link → link status
  • POST /me/epic-link/start{ login_url } (Epic OAuth)
  • POST /me/epic-link/complete { code } → links the Epic account (stores tokens)
  • DELETE /me/epic-link → unlink (idempotent)
  • POST /training-packs/publish { name, description?, training_type, difficulty, map_name, tags[], rounds[{time_limit, serialized_archetypes[]}] }{ code, tm_guid }

Publish flow: load the user's stored Epic refresh token → mint an EOS access token (rlru::auth, EGS-first with EOS fallback, mirroring AuthManager::restore_or_refresh) → PsyNetClient::auth_playerpsynet::save_training_data → return the assigned share code. The server mints the pack GUID and derives NumRounds; clients can't spoof either.

Credential storage

New epic_account_links table (migration 0091). Epic refresh tokens stored as XChaCha20-Poly1305 ciphertext keyed by a new ROCKET_SENSE_EPIC_TOKEN_ENCRYPTION_KEY server secret (32 bytes, hex or base64; server fails fast if misconfigured), with row-specific AAD binding (user, epic account, token kind) so ciphertexts can't be replayed across rows/columns. Both the durable EGS and short-lived EOS refresh tokens are stored; rotated tokens are persisted on refresh.

Deviation from the original brief (correctness fix)

EpicClient::authenticate_with_code returns an EGS token, not EOS — the EOS token requires the exchange chain. Link-complete runs the full chain and stores both tokens accordingly.

Tests / gates

14 unit tests (DTO lowering + validation, incl. tags as string array under PascalCase keys; the token cipher — roundtrip, AAD/key/tamper/version/truncation rejection, random nonces; key parsing; migration presence). cargo build/fmt --check/clippy --workspace --all-targets -D warnings clean. No live-Postgres integration tests (need DATABASE_URL; not runnable headless) — unit-level only, per the repo idiom.

Deliberately NOT changed (see docs/training-publish-followups.md)

This PR does not alter existing auth. Two pre-existing concerns become more sensitive now that a token gates publishing-as-the-user and warrant a separate decision: rocket-sense's non-expiring app JWTs, and the global permissive CORS layer. Feature-scoped follow-ups (key rotation/re-encryption, per-user publish rate limit, publish audit log) are listed there too.

Note: rocket-sense now stores users' Epic refresh tokens and acts as them against PsyNet (PsyNet has no publish-only scope) — a deliberate custodianship tradeoff.

The matching browser "Publish" UI (stat-evaluation-player calling these endpoints) and the .tem → payload exporter are follow-ups.

🤖 Generated with Claude Code

Lets the browser stats player publish a custom training pack to
Psyonix's PsyNet and get back the public share code, using the
published psynet/rlru crates.

- POST /api/v1/me/epic-link/start returns Epic's login URL; POST
  /me/epic-link/complete exchanges the pasted authorization code for
  Epic tokens (EGS -> exchange code -> EOS, mirroring
  rlru::auth::AuthManager) and links the Epic account to the current
  user; GET/DELETE /me/epic-link report and remove the link.
- POST /api/v1/training-packs/publish validates the pack payload
  (ordinals, non-empty name/map/rounds), mints the TM_Guid and derives
  NumRounds server-side, refreshes an EOS session from the stored
  tokens (EGS-first with EOS fallback, persisting rotations), then
  auth_player + Training/AddTrainingData over the PsyNet socket and
  returns { code, tm_guid }.
- New epic_account_links table (migration 0091): one link per user,
  refresh tokens stored as XChaCha20-Poly1305 ciphertext keyed by the
  new ROCKET_SENSE_EPIC_TOKEN_ENCRYPTION_KEY server secret with
  row-specific AAD binding (user, epic account, token kind).
- Unit tests cover DTO lowering/validation, the token cipher
  (roundtrip, AAD/key/tamper rejection, random nonces), key parsing,
  and migration presence. Security follow-ups (JWT expiry, global
  CORS) recorded in docs/training-publish-followups.md rather than
  changed here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant