Publish training packs to PsyNet from rocket-sense#264
Open
colonelpanic8 wants to merge 1 commit into
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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+rlrucrates (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 statusPOST /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, mirroringAuthManager::restore_or_refresh) →PsyNetClient::auth_player→psynet::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_linkstable (migration0091). Epic refresh tokens stored as XChaCha20-Poly1305 ciphertext keyed by a newROCKET_SENSE_EPIC_TOKEN_ENCRYPTION_KEYserver 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_codereturns 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.
tagsas 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 warningsclean. No live-Postgres integration tests (needDATABASE_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 → payloadexporter are follow-ups.🤖 Generated with Claude Code