fix(check): emit rewrite_viewer_id when UDID and Steam viewers disagree

Wipe-and-resignup left the client stuck with the blank V_new's id in
Certification.ViewerId. /tool/signup is anonymous, so it can't see the
Steam ticket and creates a fresh anonymous viewer keyed on the new UDID;
the Steam handler on the next request resolves to V_old and serves its
data, but no normal-response hook overwrites Certification.ViewerId.
GameStart now compares the UDID-keyed viewer to the auth-resolved one
and emits rewrite_viewer_id when they differ, which Cute/GameStartCheckTask
writes back into Certification.ViewerId.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-01 01:49:56 -04:00
parent cc32223d7d
commit e7dac31d52
2 changed files with 75 additions and 0 deletions

View File

@@ -38,12 +38,32 @@ public class CheckController : SVSimController
?? throw new InvalidOperationException("Auth handler must set viewer in context.");
Viewer fullViewer = await _viewerRepository.GetViewerWithSocials(viewer.Id) ?? viewer;
// Wipe-and-resignup reconciliation: /tool/signup is anonymous on the wire and can't see
// the Steam ticket, so a freshly-wiped client lands a blank V_new keyed on its new UDID
// while the Steam handler on this very request resolves to the original V_old. The client
// has already written V_new.Id into Certification.ViewerId from the signup response; left
// alone, it stays wrong forever (NormalTask.Parse never reads data_headers.viewer_id —
// only SignUpTask / GameStartCheckTask.rewrite_viewer_id / the social-chain tasks do).
// Detect the mismatch by re-looking-up the UDID-keyed viewer and emit rewrite_viewer_id
// when it disagrees with the auth-resolved one.
long? rewriteViewerId = null;
Guid? udid = HttpContext.GetUdid();
if (udid is Guid u && u != Guid.Empty)
{
Viewer? udidViewer = await _viewerRepository.GetViewerByUdid(u);
if (udidViewer is not null && udidViewer.Id != fullViewer.Id)
{
rewriteViewerId = fullViewer.Id;
}
}
return new GameStartResponse
{
NowViewerId = fullViewer.Id,
NowName = fullViewer.DisplayName,
NowTutorialStep = fullViewer.MissionData.TutorialState.ToString(),
IsSetTransitionPassword = true,
RewriteViewerId = rewriteViewerId,
// Stub rank map until per-format ranks are persisted (prod observed: "1"/"2"/"4"
// keys mapping to RankName_010 / RankName_017). Empty dict here may be safe but
// we don't yet know which client paths read this — match prod stub.