Smoke stack integration#248
Open
itpick wants to merge 20 commits into
Open
Conversation
Adds a complete password-reset flow on the master server:
API
POST /account/api/forgot-password (email) → anti-enumeration response,
issues a token, sends an email.
POST /account/api/reset-password (token, newPassword) → consumes the
token, updates the password, invalidates existing sessions.
Backend
- Models/Database/PasswordResetToken.cs: single-use hex token, TTL index.
- Services/Scoped/PasswordResetService.cs: issue, look-up, atomic consume.
- Services/Scoped/EmailService.cs: MailKit SMTP transport with optional
console logging for dev.
- Models/Settings/MailSettings.cs: SMTP config, no-op if Host/Port empty.
Web
- pages/ForgotPassword.vue: email input, anti-enumeration response.
- pages/ResetPassword.vue: token from query, SHA-512-hashes new password
client-side (matching Login/Register pattern), submits.
- Routes added; "Forgot password?" link on Login.
Security notes
- Anti-enumeration: forgot-password ALWAYS returns the same response,
independent of whether the email matches an account. EmailService and
PasswordResetService don't throw to callers either; the response shape
is stable.
- Tokens are SHA-512 hex (32 random bytes), single-use, 1h TTL via mongo
TTL index + explicit IsValid() check on consume.
- Reset invalidates active sessions, forcing re-login with the new
password (same pattern as ChangePassword).
Dev wiring
- Smoke docker-compose adds mailpit (port 8025 UI); api SMTP env points
at mailpit:1025 with FromAddress=noreply@ut4-hub.local.
The previous chore commit accidentally added the debug logging instead of removing it. This commit deletes the DEBUG-prefixed lines from the password grant path.
The CloudFiles admin page only supported uploading a whole file via a
file-picker. For the ⓘ-icon-driven MCP files (announcement, news,
storage, playlists, rulesets) admins almost always want to tweak a
single string or array entry, not reupload a fresh JSON file. That
made any minor announcement change a multi-step download/edit/upload
ceremony.
Add an inline editor:
- AdminService.getCloudFileText(filename) pulls the current bytes as
text via the existing admin/mcp_files/{filename} endpoint.
- EditCloudFile.vue gets a two-tab UX: 'Edit contents' (default) and
'Upload file' (existing behaviour). The edit tab preloads the
current contents into a monospace textarea, surfaces inline JSON
validation errors, and offers a 'Pretty-print JSON' button.
- Submit uses a Blob with the original filename so the existing
upsertCloudFile pipeline is unchanged.
Non-JSON files still save fine: the validator only warns; the server
never required JSON.
Verified: vue-tsc --noEmit clean; vite production build clean.
Add POST /ut/api/matchmaking/quickplay which returns one running game-server suitable for an immediate Quick-Play join. Selection is server-side so the master server can steer Quick-Play players onto a small curated pool of always-on, bot-filled servers — humans displace bots on the same instance instead of spawning thin empty matches. Selection rules: - non-stale (LastUpdated within StaleAfter) - optional UT_RULETAG_s match (RulesetTag from request body, matches UniqueTag values in UnrealTournmentMCPGameRulesets.json) - optional MAPNAME_s match (PreferredMap) - optional BuildUniqueID match - at least one open public slot - fewest open slots wins (concentrate onto fullest with capacity) To participate in the Quick-Play pool, a dedicated server advertises UT_RULETAG_s in its game-server attributes when it registers via POST /ut/api/matchmaking/session. The recommended deployment is 3 always-on bot-filled servers per ruleset (Duel / iDM / CTF), per the original Epic Quick-Play design. Files: - UT4MasterServer.Models/DTO/Request/QuickPlayRequest.cs (new) - UT4MasterServer.Services/Scoped/MatchmakingService.cs: FindQuickPlayServerAsync() - UT4MasterServer/Controllers/UT/MatchmakingController.cs: POST /ut/api/matchmaking/quickplay Verified: dotnet build UT4MasterServer.csproj clean (0 errors).
UT4 client deserializes /ut/api/game/v2/wait_times/estimate as an
object; returning a bare array caused JSON parse errors and brought
down the matchmaking search. Wrap the wait-times list in
{ waitTimes: [...] }.
Earlier commit 20abe1e wrapped the response in { waitTimes: [...] } on the hypothesis that UT4 wanted an object. That was wrong. Per Epic's released source (UTMcpUtils.cpp `GetEstimatedWaitTimes`), the client iterates the response with JsonValue->AsArray() — the canonical wire format is a bare top-level JSON array of FWaitTimeInfo objects, each shaped { ratingType, numSamples, averageWaitTimeSecs }. An empty array is acceptable (the loop simply yields zero entries). Reverts to the original `service.GetWaitTimes()` return and adds a comment citing the source so future maintainers don't repeat the envelope guesswork.
Iterating against the UT4 pre-alpha XAN-3525360 client revealed several spots where the matchmaker's HTTP path mis-matched what the OnlineSubsystemMcp parser actually accepts. Each of these in isolation is a small bug; together they let the master server reach the point where UT4's matchmaker submits a search, receives candidates, but stops at the client-side validity check (reservation step is still blocked separately). * MatchmakingService.ListAsync: normalize criterion keys to UT_ prefix so unprefixed client criteria (PLAYLISTID_i, REGION_s, NEEDS_i) line up with how game servers actually advertise attributes. Also skip Quick-Play sort hints + criteria that don't map onto our model (NEEDS_i, NEEDSSORT_i, REGION_s, UT_SERVERTRUSTLEVEL_i) so a curated QuickPlay server isn't excluded just because we don't enforce Epic's geo/trust dimensions. * MatchmakingController.CreateGameServer: when the heartbeat source IP is in the docker bridge range (172.16-31.x.x), collapse to 127.0.0.1. Local-dev master runs in a container and sees game-server heartbeats arrive via the bridge gateway IP; UT4 clients can't reach that, so it must be rewritten before being stored. Also instrument matchMakingRequest entry with an info-level log so future matchmaker debugging is one grep away. * WaitTimesController.QuickplayWaitEstimate: emit the bare "application/json" Content-Type (no charset suffix). UTMcpUtils.cpp compares Content-Type via exact string match and surfaces "Error: 1" on a 200 response if the suffix doesn't match — looks like success on the wire but the parser silently drops the result. * WaitTimeEstimateResponse: revert PascalCase guess back to camelCase. UE4's FJsonObject::GetStringField reads the field names verbatim and UT4 source shows the keys are camelCase (ratingType / averageWaitTimeSecs / numSamples). * GameServer.ToJson: when responding to a client, backfill UT_NEEDS_i (from free public-slot count), UT_TEAMELO_i, and UT_TEAMELO2_i defaults. UT4's client-side matchmaking gather pulls these directly and drops the candidate if absent, so the master has to provide them even when the game server itself doesn't track ELO.
* GameServer.ToJson: parse BuildUniqueID string into int32 when emitting to clients. UE4's FOnlineSessionMcp::ReadSessionSettings calls TryGetNumberField(buildUniqueId) which accepts numeric strings via FJsonValueString::TryGetNumber, but emitting a real JSON number matches what Epic's own FOnlineSessionMcp::WriteSessionSettings produces and survives any future Engine tightening. * MatchmakingController: keep a structured log line on each /matchmaking/session/matchMakingRequest with the requesting account, build id, and criteria count. Reasoning: previously we only had the anonymous-access warning, so the common authenticated request path was invisible in logs and matchmaker debugging required code edits. The new log entry is one line per request at Information level, which is cheap and pays off the next time the QuickPlay tile path needs work.
…roadmap GameServer.cs: auto-inject UT_NEEDS_i, UT_TEAMELO_i, UT_TEAMELO2_i, plus the QuickPlay-required attrs (UT_RULETAG_s, UT_PLAYLISTID_i, PLAYLISTID_i, UT_SERVERTRUSTLEVEL_i, UT_GAMEINSTANCE_i, UT_MATCHSTATE_s, UT_SERVERNAME_s) whenever UT_RANKED_i=1, so dedicated Blitz servers stay matchmaker-visible across mongo PUTs. MatchmakingService.cs: auto-promote — when a criterion is UT_PLAYLISTID_i, also match any UT_RANKED_i=1 server via a BsonDocument $or so the in-game Quick Play tile lands on a ranked Blitz instance even if the playlist id hasn't been written by the server yet. Announcement JSON / news HTML: replace expired Epic placeholders with a local Dallas Pick roadmap (shipped + coming-soon items). MinHeight=0 hides the dead-code WebBrowserPanel body box (SUTWebBrowserPanel::Construct in UT4 CL 3525360 never invokes ConstructPanel for inline panels, so the inner SWebBrowser is never instantiated and the body just spins).
STextBlock doesn't honor faux-centering via padding spaces, so revert to plain left-aligned roadmap text.
AUTGameSessionRanked drops back to UTEmptyServerGameMode after each match. The reservation-bypass binary patch in ut4-install lets the empty-mode server still accept QuickPlay joins, but the client lands on the void ut-entry map instead of a real FlagRun match. The watchdog tails the server log and relaunches the process whenever it detects the empty-mode transition. Configurable via env vars (INSTALL_DIR, PORT, QUERY_PORT, MAP, GAMEMODE, BOT_FILL, MIN_PLAYERS, etc.).
Covers the smoke docker stack (api/web/xmpp/mongo/mailpit), the routes UT4 clients hit, the password-grant login flow (no auth tokens needed), the auto-inject + auto-promote matchmaking logic, the blitz-watchdog companion, and notes on the dead-code announcement panel body box. Cross-linked to ut4-install/docs/DEPLOYMENT.md which covers the client side.
…entPlayers, clear-all
- FriendRequest: add Created (DateTime, defaults to UtcNow on insert) so
GetFriends returns a real timestamp instead of fabricating one per call.
- FriendsController:
- rename 'favourite' -> 'favorite' (UE4 expects US spelling)
- emit friend.Created.ToStringISO() in responses
- new DELETE /friends/api/public/friends/{id} (clear all relationships)
- new GET /friends/api/public/list/{namespace}/{id}/recentPlayers
(stub returning {recentplayers:[]})
- Announcement roadmap: note in-game friends UI is compile-time-stripped
on the 2017 Linux client (UTLocalPlayer.cpp:6254 — early return on
PLATFORM_LINUX). Backend is ready; UI needs client rebuild or a menu
binary-patch. Server-side endpoints are verified working end-to-end
via the nginx hijack at friends-public-service-prod06.ol.epicgames.com.
UE4 FOnlineFriendsMcp::QueryBlockedPlayers expects an object envelope with a blockedUsers array. Returning a bare [] makes the client log: MCP: QueryBlockedPlayers request failed. Invalid response payload=[]
SUTPlayerInfoDialog::OnReadUserFileComplete (in the shipping client
binary, differs from the open-source mirror) reads
/ut/api/cloudstorage/user/{id}/oldplayercard as an async MCP fetch.
On bWasSuccessful=false the gate at vaddr 0x15a3684 skips
UpdatePlayerCustomization, which is what clears the loading text +
populates the dialog tabs. Returning 404 left the dialog hung on
"Requesting Player Information..." forever.
Mirror the existing stats.json stub pattern: when oldplayercard is
missing, return {} so the success path runs. UpdatePlayerCustomization
null-checks all fetched data so empty is safe.
Also adds:
- docs/2026-06-16-overnight-progress.md: progress summary for the user
- scripts/smoke-test-endpoints.sh: idempotent end-to-end backend check
Smoke tests confirm: - Mono works on NixOS via nix-shell - GitDependencies.exe runs - uproject + Engine source bundled in the JimmieKJ mirror - cdn.unrealengine.com/dependencies returns 403 Forbidden - Linux toolchain URL returns 403 - Akamai mirror returns 404 Without the engine binary deps (~5-10GB of ThirdParty .so/.dll and prebuilt UBT helpers), the build can't proceed. The CDN being dead is the hard blocker. Recommended path: accept the binary-patch debt. AUTPartyBeaconHost patch (already in ut4-server-base.nix) and the new SUTPlayerInfoDialog NOP patch we just applied to the live client cover the main gaps. Friends list would need LD_PRELOAD + vtable rewrite (sketched in agent report), or wait for someone to mirror the 4.15 deps.
Owner
|
There's so much wrong with this. That I don't know if i should even properly review this... Random notes as I skim across the PR:
If this really does what it's supposed to then great, but it introduces so much noise that I am not willing to include it in a current state. Every change should be a separate PR to make changes human readable. |
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.
No description provided.