fix(battle-node): header-based WS detection in auth; split unknown-bid vs mismatch logs

Previous fix used Context.WebSockets.IsWebSocketRequest, but that
requires UseWebSockets() to have already run — and UseBattleNode
(which calls UseWebSockets) is registered AFTER UseAuthentication
in Program.cs, so the WS feature isn't installed when auth runs.
Switch to reading the raw Upgrade header, which works regardless
of middleware order.

Also split the WS handler's "Unknown battle/viewer pair" warning
into two distinct cases so we can tell unknown-BattleId from
viewer-id-mismatch (which lets us see whether the bridge stored
the right viewer or the client is encrypting a different id).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-01 01:17:42 -04:00
parent 1252f7bd35
commit ccc9b41473
2 changed files with 19 additions and 4 deletions

View File

@@ -54,9 +54,20 @@ public sealed class BattleNodeWebSocketHandler
}
var pending = _store.TryGetPending(battleId);
if (pending is null || pending.ViewerId != viewerId)
if (pending is null)
{
_log.LogWarning("Unknown battle/viewer pair: {Bid}/{Vid}", battleId, viewerId);
_log.LogWarning(
"WS upgrade for unknown BattleId={Bid} (decrypted viewerId={Vid}). " +
"Bridge may not have minted this battle, or it was already consumed/expired.",
battleId, viewerId);
ctx.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
if (pending.ViewerId != viewerId)
{
_log.LogWarning(
"WS upgrade viewer-id mismatch on BattleId={Bid}: bridge expected={Expected}, decrypted={Got}.",
battleId, pending.ViewerId, viewerId);
ctx.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}

View File

@@ -37,10 +37,14 @@ public class SteamSessionAuthenticationHandler : AuthenticationHandler<SteamAuth
string path = Request.Path;
// WebSocket upgrades carry no body — Request.Body.Seek throws NotSupportedException
// on Kestrel's HttpRequestStream. The battle node has its own per-connection auth
// (encrypted viewerId query/header validated against the matched battle id), so the
// (encrypted viewerId header validated against the matched battle id), so the
// Steam handler has nothing to do here. Returning NoResult lets the request proceed
// unauthenticated to the WS endpoint.
if (Context.WebSockets.IsWebSocketRequest)
// Header-based detection: Context.WebSockets.IsWebSocketRequest needs UseWebSockets()
// to have already run, but UseBattleNode (which calls UseWebSockets) is registered
// AFTER UseAuthentication in Program.cs. Reading the raw Upgrade header works
// regardless of middleware order.
if (string.Equals(Request.Headers["Upgrade"].ToString(), "websocket", StringComparison.OrdinalIgnoreCase))
{
return AuthenticateResult.NoResult();
}