After /tool/signup the client switches to SID-only headers (no UDID), so the next request's body can't be decrypted unless the server already knows the SID's UDID. ShadowverseSessionService now mirrors the client's Cute/Cryptographer.MakeMd5(viewerId + udid) formula (salt "r!I@ws8e5i="), and ToolController.Signup prestores the mapping at the end. Verified against a live signup capture: viewerId=1 + udid=62747917-93bc-454c-abb4-ef423b3c9317 produces the captured SID dc4aac79d35fe15dfb6262e0071bb03c. Note: this only fixes the fresh-signup path. Clients restarting with a cached viewer_id (which skip /tool/signup entirely) still hit the same issue — separate follow-up. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
65 lines
2.8 KiB
C#
65 lines
2.8 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using SVSim.Database.Repositories.Viewer;
|
|
using SVSim.EmulatedEntrypoint.Extensions;
|
|
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
|
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
|
|
using SVSim.EmulatedEntrypoint.Services;
|
|
|
|
namespace SVSim.EmulatedEntrypoint.Controllers;
|
|
|
|
public class ToolController : SVSimController
|
|
{
|
|
private readonly ILogger<ToolController> _logger;
|
|
private readonly IViewerRepository _viewerRepository;
|
|
private readonly ShadowverseSessionService _sessionService;
|
|
|
|
public ToolController(
|
|
ILogger<ToolController> logger,
|
|
IViewerRepository viewerRepository,
|
|
ShadowverseSessionService sessionService)
|
|
{
|
|
_logger = logger;
|
|
_viewerRepository = viewerRepository;
|
|
_sessionService = sessionService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>POST /tool/signup</c> — the client's first request on a fresh boot. Creates (or returns
|
|
/// the existing) Viewer keyed on the request's UDID. The interesting outputs (viewer_id,
|
|
/// short_udid, udid) all flow back via <c>data_headers</c>, populated by the translation
|
|
/// middleware after this action returns — we just need to stash the viewer on HttpContext so
|
|
/// the middleware picks it up the same way the auth handler does for logged-in endpoints.
|
|
///
|
|
/// Spec: <c>docs/api-spec/endpoints/pre-login/tool-signup.md</c>.
|
|
/// </summary>
|
|
[AllowAnonymous]
|
|
[HttpPost("signup")]
|
|
public async Task<SignupResponse> Signup([FromBody] SignupRequest request)
|
|
{
|
|
Guid? maybeUdid = HttpContext.GetUdid();
|
|
if (maybeUdid is not Guid udid || udid == Guid.Empty)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot register viewer: request has no resolvable UDID (missing UDID/SID headers, or " +
|
|
"SessionidMappingMiddleware couldn't decode the UDID header).");
|
|
}
|
|
|
|
var viewer = await _viewerRepository.GetViewerByUdid(udid)
|
|
?? await _viewerRepository.RegisterAnonymousViewer(udid);
|
|
|
|
HttpContext.SetViewer(viewer);
|
|
|
|
// Pre-store the SID the client will compute and use for its very next request. After
|
|
// signup the client switches to SID-only headers (no UDID), so without this mapping the
|
|
// translation middleware can't decrypt the next body. Formula mirrors the decompiled
|
|
// Cute/Certification.SessionId getter — see ShadowverseSessionService.ComputeClientSessionId.
|
|
_sessionService.StoreSessionForViewer(viewer.Id, udid);
|
|
|
|
_logger.LogInformation("Signup resolved for udid={Udid} → viewer_id={ViewerId}, short_udid={ShortUdid}.",
|
|
udid, viewer.Id, viewer.ShortUdid);
|
|
|
|
return new SignupResponse();
|
|
}
|
|
}
|