This commit is contained in:
2025-10-07 09:28:45 +01:00
parent 3d2176693e
commit 830a401c90
3 changed files with 210 additions and 10 deletions

View File

@@ -9,6 +9,8 @@ using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Reflection;
using System.Security.Cryptography;
using Dalamud.Utility;
using System.Net;
namespace MareSynchronos.WebAPI;
@@ -17,16 +19,18 @@ public sealed class AccountRegistrationService : IDisposable
private readonly HttpClient _httpClient;
private readonly ILogger<AccountRegistrationService> _logger;
private readonly ServerConfigurationManager _serverManager;
private readonly DalamudUtilService _dalamudUtilService;
private string GenerateSecretKey()
{
return Convert.ToHexString(SHA256.HashData(RandomNumberGenerator.GetBytes(64)));
}
public AccountRegistrationService(ILogger<AccountRegistrationService> logger, ServerConfigurationManager serverManager)
public AccountRegistrationService(ILogger<AccountRegistrationService> logger, DalamudUtilService dalamudUtilService, ServerConfigurationManager serverManager)
{
_logger = logger;
_serverManager = serverManager;
_dalamudUtilService = dalamudUtilService;
_httpClient = new(
new HttpClientHandler
{
@@ -43,6 +47,72 @@ public sealed class AccountRegistrationService : IDisposable
_httpClient.Dispose();
}
public async Task<RegisterReplyDto> XIVAuth(CancellationToken token)
{
var secretKey = GenerateSecretKey();
var hashedSecretKey = secretKey.GetHash256();
var playerName = _dalamudUtilService.GetPlayerNameAsync().GetAwaiter().GetResult();
var worldId = (ushort)_dalamudUtilService.GetHomeWorldIdAsync().GetAwaiter().GetResult();
var worldName = _dalamudUtilService.WorldData.Value[(worldId)];
var sessionID = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)).Replace('+', '-').Replace('/', '_').TrimEnd('=');
Uri handshakeUri = new Uri("https://account.snowcloak-sync.com/register");
var handshakePayload = new { session_id = sessionID, hashed_secret = hashedSecretKey, character_name = playerName, home_world = worldName };
var handshakeResponse = await _httpClient.PostAsJsonAsync(handshakeUri, handshakePayload, token).ConfigureAwait(false);
handshakeResponse.EnsureSuccessStatusCode();
var register = await handshakeResponse.Content.ReadFromJsonAsync<RegisterResponse>(cancellationToken: token)
.ConfigureAwait(false);
if (register is null || string.IsNullOrWhiteSpace(register.link_url) ||
string.IsNullOrWhiteSpace(register.poll_url))
{
return new RegisterReplyDto() { Success = false, ErrorMessage = "Malformed registration response." };
}
Util.OpenLink(register.link_url);
const int maxAttempts = 600 / 15; // Try once every 15 seconds for 10 minutes
var pollUri = new Uri(register.poll_url);
PollResponse? lastPoll = null;
for (int i = 0; i < maxAttempts; i++)
{
token.ThrowIfCancellationRequested();
using var resp = await _httpClient.GetAsync(pollUri, token).ConfigureAwait(false);
if (resp.StatusCode == HttpStatusCode.Gone)
{
// Server marked this as having been consumed already OR it got TLL'd out
return new RegisterReplyDto()
{
Success = false, ErrorMessage = "Registration session expired. Please try again."
};
}
if (resp.StatusCode == HttpStatusCode.OK)
{
lastPoll = await resp.Content.ReadFromJsonAsync<PollResponse>(cancellationToken: token)
.ConfigureAwait(false);
if (lastPoll?.status?.Equals("bound", StringComparison.OrdinalIgnoreCase) == true)
{
// yay
return new RegisterReplyDto()
{
Success = true, ErrorMessage = null, UID = lastPoll?.uid, SecretKey = secretKey
};
}
// Pending, keep polling
}
await Task.Delay(TimeSpan.FromSeconds(15), token).ConfigureAwait(false);
}
// Timed out
return new RegisterReplyDto()
{
Success = false,
ErrorMessage =
"Timed out waiting for authorisation. Please try again, and complete the process within 10 minutes."
};
}
public async Task<RegisterReplyDto> RegisterAccount(CancellationToken token)
{
var secretKey = GenerateSecretKey();
@@ -67,4 +137,19 @@ public sealed class AccountRegistrationService : IDisposable
SecretKey = secretKey
};
}
}
private sealed class RegisterResponse
{
public string link_url { get; set; } = "";
public string poll_url { get; set; } = "";
}
private sealed class PollResponse
{
public string status { get; set; } = "";
public string? uid { get; set; }
}
}