Skip to content

Commit 373dd26

Browse files
committed
XIVAuth moved to main server
1 parent 2539af2 commit 373dd26

8 files changed

Lines changed: 100 additions & 70 deletions

Snowcloak.Core/Snowcloak.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="ElezenTools.Core" Version="15.2.1" />
1111
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
12-
<PackageReference Include="Snowcloak.API" Version="3.0.0.12" />
12+
<PackageReference Include="Snowcloak.API" Version="3.0.0.13" />
1313
</ItemGroup>
1414

1515

Snowcloak/Snowcloak.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
33
<PropertyGroup>
44
<AssemblyName>Snowcloak</AssemblyName>
5-
<Version>3.0.0.1</Version>
5+
<Version>3.0.0.63</Version>
66
<PackageProjectUrl>https://github.com/Eauldane/SnowcloakClient/</PackageProjectUrl>
77
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
88
<Configurations>Release;Debug</Configurations>
@@ -43,7 +43,7 @@
4343
<PackageReference Include="NAudio" Version="2.3.0" />
4444
<PackageReference Include="Penumbra.Api" Version="5.15.1" />
4545
<PackageReference Include="Scrutor" Version="7.0.0" />
46-
<PackageReference Include="Snowcloak.API" Version="3.0.0.12" />
46+
<PackageReference Include="Snowcloak.API" Version="3.0.0.13" />
4747
<PackageReference Include="Snowcloak.CacheFile" Version="3.2.1.2" />
4848
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
4949
<PackageReference Include="Vortice.DXGI" Version="3.8.3" />

Snowcloak/UI/CommunitySyncshellWindow.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,13 @@ public CommunitySyncshellWindow(ILogger<CommunitySyncshellWindow> logger, SnowMe
4242

4343
public override void OnOpen()
4444
{
45-
// Default the location filter to the player's own world so they see nearby shells first.
4645
ApplyDefaultLocationFilter();
47-
// Refresh on each open so the listing reflects the latest server state.
4846
RefreshDirectoryResults();
4947
}
5048

5149
private void ApplyDefaultLocationFilter()
5250
{
53-
var worldId = (uint)_dalamudUtilService.GetHomeWorldId();
51+
var worldId = _dalamudUtilService.GetHomeWorldId();
5452
var region = _dalamudUtilService.GetWorldRegion(worldId);
5553
if (worldId == 0 || string.IsNullOrEmpty(region))
5654
{
@@ -98,7 +96,6 @@ private void RefreshDirectoryResults()
9896
{
9997
try
10098
{
101-
// A specific world implies its region; the server gates on Region, then narrows by WorldId.
10299
var region = _regionFilter;
103100
if (_worldFilter != 0 && string.IsNullOrEmpty(region))
104101
{
@@ -209,7 +206,6 @@ private void SetRegionFilter(string region)
209206
{
210207
_regionFilter = region;
211208

212-
// Drop a single-world selection that no longer belongs to the chosen region.
213209
if (_worldFilter != 0
214210
&& (string.IsNullOrEmpty(region)
215211
|| !string.Equals(_dalamudUtilService.GetWorldRegion(_worldFilter), region, StringComparison.Ordinal)))

Snowcloak/UI/SettingsUi.ServerConfiguration.cs

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -337,34 +337,62 @@ private void DrawSecretKeyTab(ServerStorage selectedServer, string playerName, u
337337
}
338338

339339
ImGui.Separator();
340-
if (ElezenImgui.ShowIconButton(FontAwesomeIcon.Plus, "Add new Secret Key"))
340+
var isCurrentServer = selectedServer == _serverConfigurationManager.CurrentServer;
341+
if (isCurrentServer)
341342
{
342-
selectedServer.SecretKeys.Add(selectedServer.SecretKeys.Any() ? selectedServer.SecretKeys.Max(p => p.Key) + 1 : 0, new SecretKey()
343+
if (selectedServer.AccountLinked)
344+
{
345+
using (ImRaii.Disabled(_addKeyAccountUidFlow.IsRunning))
346+
{
347+
if (ElezenImgui.ShowIconButton(FontAwesomeIcon.Plus, "Create new Secret Key"))
348+
{
349+
_addKeyAccountUidFlow.Begin();
350+
}
351+
}
352+
ElezenImgui.AttachTooltip("Creates a new UID on the server, attaches it to your Snowcloak account, and downloads its key.");
353+
}
354+
else
355+
{
356+
using (ImRaii.Disabled(_standaloneKeyFlow.IsRunning))
357+
{
358+
if (ElezenImgui.ShowIconButton(FontAwesomeIcon.Plus, "Create new Secret Key"))
359+
{
360+
_standaloneKeyFlow.Begin(_registerService.RegisterAccount,
361+
"New secret key created.\nPlease keep a copy of your secret key in case you need to reset your plugins, or to use it on another PC.",
362+
reply =>
363+
{
364+
selectedServer.SecretKeys.Add(selectedServer.SecretKeys.Count != 0 ? selectedServer.SecretKeys.Max(p => p.Key) + 1 : 0, new SecretKey()
365+
{
366+
FriendlyName = string.Format(CultureInfo.InvariantCulture, "{0} {1}", reply.UID, string.Format(CultureInfo.InvariantCulture, "(registered {0:yyyy-MM-dd})", DateTime.Now)),
367+
Key = reply.SecretKey ?? ""
368+
});
369+
_serverConfigurationManager.Save();
370+
},
371+
"Registration failed");
372+
}
373+
}
374+
ElezenImgui.AttachTooltip("Registers a new secret key with the server and adds it to this service.");
375+
}
376+
377+
ImGui.SameLine();
378+
}
379+
380+
if (ElezenImgui.ShowIconButton(FontAwesomeIcon.Plus, "Add empty key"))
381+
{
382+
selectedServer.SecretKeys.Add(selectedServer.SecretKeys.Count != 0 ? selectedServer.SecretKeys.Max(p => p.Key) + 1 : 0, new SecretKey()
343383
{
344384
FriendlyName = "New Secret Key",
345385
});
346386
_serverConfigurationManager.Save();
347387
}
388+
ElezenImgui.AttachTooltip("Adds an empty entry so you can paste a secret key you already have.");
348389

349-
if (!selectedServer.AccountLinked)
390+
if (isCurrentServer)
350391
{
351-
ImGui.SameLine();
352-
if (ElezenImgui.ShowIconButton(FontAwesomeIcon.Plus, "Create standalone key"))
353-
{
354-
_standaloneKeyFlow.Begin(_registerService.RegisterAccount,
355-
"New standalone key created.\nPlease keep a copy of your secret key in case you need to reset your plugins, or to use it on another PC.",
356-
reply =>
357-
{
358-
selectedServer.SecretKeys.Add(selectedServer.SecretKeys.Any() ? selectedServer.SecretKeys.Max(p => p.Key) + 1 : 0, new SecretKey()
359-
{
360-
FriendlyName = string.Format(CultureInfo.InvariantCulture, "{0} {1}", reply.UID, string.Format(CultureInfo.InvariantCulture, "(registered {0:yyyy-MM-dd})", DateTime.Now)),
361-
Key = reply.SecretKey ?? ""
362-
});
363-
_serverConfigurationManager.Save();
364-
},
365-
"Registration failed");
366-
}
367-
_standaloneKeyFlow.DrawStatus("Sending request...");
392+
if (selectedServer.AccountLinked)
393+
_addKeyAccountUidFlow.DrawStatus();
394+
else
395+
_standaloneKeyFlow.DrawStatus("Sending request...");
368396
}
369397
}
370398

Snowcloak/UI/SettingsUi.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ private enum SettingsTab
6464
private readonly SecretKeyBackupFlow _secretKeyBackupFlow;
6565
private readonly CharacterKeyAssignmentFlow _characterKeyAssignmentFlow;
6666
private readonly AccountUidGenerationFlow _accountUidGenerationFlow;
67+
private readonly AccountUidGenerationFlow _addKeyAccountUidFlow;
6768
private readonly StandaloneKeyRegistrationFlow _standaloneKeyFlow;
6869

6970
public SettingsUi(ILogger<SettingsUi> logger,
@@ -101,6 +102,7 @@ public SettingsUi(ILogger<SettingsUi> logger,
101102
_secretKeyBackupFlow = new SecretKeyBackupFlow(logger, secretKeyBackupService, fileDialogManager);
102103
_characterKeyAssignmentFlow = new CharacterKeyAssignmentFlow(logger, serverConfigurationManager, apiController);
103104
_accountUidGenerationFlow = new AccountUidGenerationFlow(logger, registerService, apiController);
105+
_addKeyAccountUidFlow = new AccountUidGenerationFlow(logger, registerService, apiController);
104106
_standaloneKeyFlow = new StandaloneKeyRegistrationFlow(logger);
105107
AllowClickthrough = false;
106108
AllowPinning = false;

Snowcloak/WebAPI/AccountRegistrationService.Registration.cs

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
using Snowcloak.API.Dto.Account;
33
using Snowcloak.API.Routes;
44
using Snowcloak.Utils;
5-
using System.Net;
65
using System.Net.Http.Json;
7-
using System.Security.Cryptography;
8-
using System.Text.Json;
96

107
namespace Snowcloak.WebAPI;
118

@@ -15,55 +12,60 @@ public async Task<RegisterReplyDto> XIVAuth(CancellationToken token)
1512
{
1613
var secretKey = GenerateSecretKey();
1714
var hashedSecretKey = secretKey.GetHash256();
18-
var playerName = _dalamudUtilService.GetPlayerNameAsync().GetAwaiter().GetResult();
19-
var worldId = (ushort)_dalamudUtilService.GetHomeWorldIdAsync().GetAwaiter().GetResult();
15+
var playerName = await _dalamudUtilService.GetPlayerNameAsync().ConfigureAwait(false);
16+
var worldId = (ushort)await _dalamudUtilService.GetHomeWorldIdAsync().ConfigureAwait(false);
2017
var worldName = _dalamudUtilService.WorldData[worldId];
2118

22-
var sessionID = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)).Replace('+', '-').Replace('/', '_').TrimEnd('=');
23-
var handshakeUri = new Uri("https://account.snowcloak-sync.com/register");
24-
var handshakePayload = new { session_id = sessionID, hashed_secret = hashedSecretKey, character_name = playerName, home_world = worldName };
25-
var handshakeResponse = await _httpClient.PostAsJsonAsync(handshakeUri, handshakePayload, token).ConfigureAwait(false);
26-
handshakeResponse.EnsureSuccessStatusCode();
27-
using var registerStream = await handshakeResponse.Content.ReadAsStreamAsync(token).ConfigureAwait(false);
28-
using var register = await JsonDocument.ParseAsync(registerStream, cancellationToken: token).ConfigureAwait(false);
29-
var linkUrl = register.RootElement.TryGetProperty("link_url", out var linkUrlProperty) ? linkUrlProperty.GetString() : null;
30-
var pollUrl = register.RootElement.TryGetProperty("poll_url", out var pollUrlProperty) ? pollUrlProperty.GetString() : null;
31-
if (string.IsNullOrWhiteSpace(linkUrl) || string.IsNullOrWhiteSpace(pollUrl))
19+
var startUri = new Uri(GetApiBaseUri(), XivAuthRegisterStartRoute);
20+
var startPayload = new XivAuthRegisterStartRequestDto
21+
{
22+
HashedSecretKey = hashedSecretKey,
23+
CharacterName = playerName,
24+
HomeWorld = worldName
25+
};
26+
27+
using var startResponse = await _httpClient.PostAsJsonAsync(startUri, startPayload, token).ConfigureAwait(false);
28+
if (!startResponse.IsSuccessStatusCode)
29+
{
30+
return new RegisterReplyDto { Success = false, ErrorMessage = await ReadErrorAsync(startResponse, token).ConfigureAwait(false) };
31+
}
32+
33+
var start = await startResponse.Content.ReadFromJsonAsync<XivAuthRegisterStartReplyDto>(token).ConfigureAwait(false);
34+
if (start == null || string.IsNullOrWhiteSpace(start.SessionId) || string.IsNullOrWhiteSpace(start.AuthorizationUrl))
3235
{
3336
return new RegisterReplyDto { Success = false, ErrorMessage = "Malformed registration response." };
3437
}
3538

36-
Util.OpenLink(linkUrl);
37-
const int maxAttempts = 600 / 5;
38-
var pollUri = new Uri(pollUrl);
39-
for (var i = 0; i < maxAttempts; i++)
39+
Util.OpenLink(start.AuthorizationUrl);
40+
var expiry = start.ExpiresAtUtc > DateTimeOffset.UtcNow ? start.ExpiresAtUtc : DateTimeOffset.UtcNow.AddMinutes(10);
41+
var pollUri = new Uri(GetApiBaseUri(), XivAuthRegisterPollRoutePrefix + start.SessionId);
42+
43+
while (DateTimeOffset.UtcNow < expiry)
4044
{
4145
token.ThrowIfCancellationRequested();
42-
using var resp = await _httpClient.GetAsync(pollUri, token).ConfigureAwait(false);
43-
if (resp.StatusCode == HttpStatusCode.Gone)
46+
using var pollResponse = await _httpClient.GetAsync(pollUri, token).ConfigureAwait(false);
47+
if (!pollResponse.IsSuccessStatusCode)
4448
{
45-
return new RegisterReplyDto
46-
{
47-
Success = false, ErrorMessage = "Registration session expired. Please try again."
48-
};
49+
return new RegisterReplyDto { Success = false, ErrorMessage = $"Registration polling failed ({(int)pollResponse.StatusCode})." };
4950
}
5051

51-
if (resp.StatusCode == HttpStatusCode.OK)
52+
var poll = await pollResponse.Content.ReadFromJsonAsync<XivAuthRegisterPollReplyDto>(token).ConfigureAwait(false) ?? new XivAuthRegisterPollReplyDto();
53+
if (poll.Status.Equals("pending", StringComparison.OrdinalIgnoreCase))
5254
{
53-
using var pollStream = await resp.Content.ReadAsStreamAsync(token).ConfigureAwait(false);
54-
using var pollPayload = await JsonDocument.ParseAsync(pollStream, cancellationToken: token).ConfigureAwait(false);
55-
var status = pollPayload.RootElement.TryGetProperty("status", out var statusProperty) ? statusProperty.GetString() : null;
56-
if (status?.Equals("bound", StringComparison.OrdinalIgnoreCase) == true)
57-
{
58-
var uid = pollPayload.RootElement.TryGetProperty("uid", out var uidProperty) ? uidProperty.GetString() : null;
59-
return new RegisterReplyDto
60-
{
61-
Success = true, ErrorMessage = string.Empty, UID = uid ?? string.Empty, SecretKey = secretKey
62-
};
63-
}
55+
await Task.Delay(TimeSpan.FromSeconds(5), token).ConfigureAwait(false);
56+
continue;
6457
}
6558

66-
await Task.Delay(TimeSpan.FromSeconds(5), token).ConfigureAwait(false);
59+
if (poll.Status.Equals("completed", StringComparison.OrdinalIgnoreCase))
60+
{
61+
return new RegisterReplyDto { Success = true, ErrorMessage = string.Empty, UID = poll.Uid, SecretKey = secretKey };
62+
}
63+
64+
return new RegisterReplyDto
65+
{
66+
Success = false,
67+
ErrorMessage = string.IsNullOrWhiteSpace(poll.ErrorMessage) ? "XIVAuth registration failed. Please try again." : poll.ErrorMessage
68+
};
6769
}
6870

6971
return new RegisterReplyDto

Snowcloak/WebAPI/AccountRegistrationService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public sealed partial class AccountRegistrationService : IDisposable
1818
private const string AccountKeysRoute = "/auth/account/keys";
1919
private const string AccountKeyLinkRoute = "/auth/account/keys/link";
2020
private const string AccountUidRoute = "/auth/account/uid";
21+
private const string XivAuthRegisterStartRoute = "/auth/xivauth/register/start";
22+
private const string XivAuthRegisterPollRoutePrefix = "/auth/xivauth/register/poll/";
2123

2224
private readonly HttpClient _httpClient;
2325
private readonly ILogger<AccountRegistrationService> _logger;

Snowcloak/packages.lock.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,9 @@
207207
},
208208
"Snowcloak.API": {
209209
"type": "Direct",
210-
"requested": "[3.0.0.12, )",
211-
"resolved": "3.0.0.12",
212-
"contentHash": "CI1RisYkYON00X0+D0mOaSnUgoeSzgrzGs3ivjWrqjyn/RZYOvIHBRpQ5pRXCNZDvtcJcapvt+GdDTy/tTKM1g==",
210+
"requested": "[3.0.0.13, )",
211+
"resolved": "3.0.0.13",
212+
"contentHash": "b6XHtIuQroyDAzLRXLnFjmlANo+GgtcbgJdRR1P2ZCITvIRd3iUek1JRmg0Hf8fIrjEKvJiRbzkZApMSHhJYDQ==",
213213
"dependencies": {
214214
"Blake3": "2.2.1",
215215
"MessagePack.Annotations": "3.1.7"
@@ -755,7 +755,7 @@
755755
"dependencies": {
756756
"ElezenTools.Core": "[15.2.1, )",
757757
"Microsoft.Extensions.Logging.Abstractions": "[10.0.9, )",
758-
"Snowcloak.API": "[3.0.0.12, )"
758+
"Snowcloak.API": "[3.0.0.13, )"
759759
}
760760
},
761761
"snowcloak.game": {

0 commit comments

Comments
 (0)