Mare 0.9 (#65)

* add jwt expiry

* start of 0.9 api impl

* some stuff idk

* some more impl

* some cleanup

* remove grouppair, add configuration, rework some pair drawing stuff

* do some stuff

* rework some ui

* I don't even know anymore

* add cancellationtoken

* token bla

* ui fixes etc

* probably individual adding/removing now working fully as expected

* add working report popup

* I guess it's more syncshell shit or so

* popup shit idk

* work out most of the syncshell bullshit I guess

* delete some old crap

* are we actually getting closer to the end

* update pair info stuff

* more fixes/adjustments, idk

* refactor some things

* some rework

* some more cleanup

* cleanup

* make menu buttons w i d e

* better icon text buttons

* add all syncshell folder and ordering fixes

---------

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
rootdarkarchon
2023-10-17 21:36:44 +02:00
committed by GitHub
parent f15b8f6bbd
commit 14575a4a6b
111 changed files with 3456 additions and 3174 deletions

View File

@@ -1,4 +1,5 @@
using MareSynchronos.API.Data;
using MareSynchronos.API.Dto;
using MareSynchronos.API.Dto.User;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
@@ -6,6 +7,7 @@ using System.Text;
namespace MareSynchronos.WebAPI;
#pragma warning disable MA0040
public partial class ApiController
{
public async Task PushCharacterData(CharacterData data, List<UserData> visibleCharacters)
@@ -14,7 +16,7 @@ public partial class ApiController
try
{
await PushCharacterDataInternal(data, visibleCharacters.ToList()).ConfigureAwait(false);
await PushCharacterDataInternal(data, [.. visibleCharacters]).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -44,14 +46,14 @@ public partial class ApiController
return await _mareHub!.InvokeAsync<List<OnlineUserIdentDto>>(nameof(UserGetOnlinePairs)).ConfigureAwait(false);
}
public async Task<List<UserPairDto>> UserGetPairedClients()
public async Task<List<UserFullPairDto>> UserGetPairedClients()
{
return await _mareHub!.InvokeAsync<List<UserPairDto>>(nameof(UserGetPairedClients)).ConfigureAwait(false);
return await _mareHub!.InvokeAsync<List<UserFullPairDto>>(nameof(UserGetPairedClients)).ConfigureAwait(false);
}
public async Task<UserProfileDto> UserGetProfile(UserDto dto)
{
if (!IsConnected) return new UserProfileDto(dto.User, false, null, null, null);
if (!IsConnected) return new UserProfileDto(dto.User, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, Description: null);
return await _mareHub!.InvokeAsync<UserProfileDto>(nameof(UserGetProfile), dto).ConfigureAwait(false);
}
@@ -90,6 +92,12 @@ public partial class ApiController
await _mareHub!.InvokeAsync(nameof(UserSetProfile), userDescription).ConfigureAwait(false);
}
public async Task UserUpdateDefaultPermissions(DefaultPermissionsDto defaultPermissionsDto)
{
CheckConnection();
await _mareHub!.InvokeAsync(nameof(UserUpdateDefaultPermissions), defaultPermissionsDto).ConfigureAwait(false);
}
private async Task PushCharacterDataInternal(CharacterData character, List<UserData> visibleCharacters)
{
Logger.LogInformation("Pushing character data for {hash} to {charas}", character.DataHash.Value, string.Join(", ", visibleCharacters.Select(c => c.AliasOrUID)));
@@ -105,4 +113,5 @@ public partial class ApiController
Logger.LogDebug("Chara data contained: {nl} {data}", Environment.NewLine, sb.ToString());
await UserPushData(new(visibleCharacters, character)).ConfigureAwait(false);
}
}
}
#pragma warning restore MA0040

View File

@@ -25,6 +25,13 @@ public partial class ApiController
return Task.CompletedTask;
}
public Task Client_GroupChangeUserPairPermissions(GroupPairUserPermissionDto dto)
{
Logger.LogDebug("Client_GroupChangeUserPairPermissions: {dto}", dto);
ExecuteSafely(() => _pairManager.UpdateGroupPairPermissions(dto));
return Task.CompletedTask;
}
public Task Client_GroupDelete(GroupDto groupDto)
{
Logger.LogTrace("Client_GroupDelete: {dto}", groupDto);
@@ -32,17 +39,6 @@ public partial class ApiController
return Task.CompletedTask;
}
public Task Client_GroupPairChangePermissions(GroupPairUserPermissionDto permissionDto)
{
Logger.LogTrace("Client_GroupPairChangePermissions: {perm}", permissionDto);
ExecuteSafely(() =>
{
if (string.Equals(permissionDto.UID, UID, StringComparison.Ordinal)) _pairManager.SetGroupUserPermissions(permissionDto);
else _pairManager.SetGroupPairUserPermissions(permissionDto);
});
return Task.CompletedTask;
}
public Task Client_GroupPairChangeUserInfo(GroupPairUserInfoDto userInfo)
{
Logger.LogTrace("Client_GroupPairChangeUserInfo: {dto}", userInfo);
@@ -113,10 +109,17 @@ public partial class ApiController
return Task.CompletedTask;
}
public Task Client_UpdateUserIndividualPairStatusDto(UserIndividualPairStatusDto dto)
{
Logger.LogDebug("Client_UpdateUserIndividualPairStatusDto: {dto}", dto);
ExecuteSafely(() => _pairManager.UpdateIndividualPairStatus(dto));
return Task.CompletedTask;
}
public Task Client_UserAddClientPair(UserPairDto dto)
{
Logger.LogDebug("Client_UserAddClientPair: {dto}", dto);
ExecuteSafely(() => _pairManager.AddUserPair(dto));
ExecuteSafely(() => _pairManager.AddUserPair(dto, addToLastAddedUser: true));
return Task.CompletedTask;
}
@@ -155,6 +158,13 @@ public partial class ApiController
return Task.CompletedTask;
}
public Task Client_UserUpdateDefaultPermissions(DefaultPermissionsDto dto)
{
Logger.LogDebug("Client_UserUpdateDefaultPermissions: {dto}", dto);
_connectionDto!.DefaultPreferredPermissions = dto;
return Task.CompletedTask;
}
public Task Client_UserUpdateOtherPairPermissions(UserPermissionsDto dto)
{
Logger.LogDebug("Client_UserUpdateOtherPairPermissions: {dto}", dto);
@@ -188,18 +198,18 @@ public partial class ApiController
_mareHub!.On(nameof(Client_GroupChangePermissions), act);
}
public void OnGroupChangeUserPairPermissions(Action<GroupPairUserPermissionDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupChangeUserPairPermissions), act);
}
public void OnGroupDelete(Action<GroupDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupDelete), act);
}
public void OnGroupPairChangePermissions(Action<GroupPairUserPermissionDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairChangePermissions), act);
}
public void OnGroupPairChangeUserInfo(Action<GroupPairUserInfoDto> act)
{
if (_initialized) return;
@@ -242,12 +252,24 @@ public partial class ApiController
_mareHub!.On(nameof(Client_UpdateSystemInfo), act);
}
public void OnUpdateUserIndividualPairStatusDto(Action<UserIndividualPairStatusDto> action)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UpdateUserIndividualPairStatusDto), action);
}
public void OnUserAddClientPair(Action<UserPairDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserAddClientPair), act);
}
public void OnUserDefaultPermissionUpdate(Action<DefaultPermissionsDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserUpdateDefaultPermissions), act);
}
public void OnUserReceiveCharacterData(Action<OnlineUserCharaDataDto> act)
{
if (_initialized) return;

View File

@@ -42,10 +42,10 @@ public partial class ApiController
await _mareHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false);
}
public async Task<GroupPasswordDto> GroupCreate()
public async Task<GroupJoinDto> GroupCreate()
{
CheckConnection();
return await _mareHub!.InvokeAsync<GroupPasswordDto>(nameof(GroupCreate)).ConfigureAwait(false);
return await _mareHub!.InvokeAsync<GroupJoinDto>(nameof(GroupCreate)).ConfigureAwait(false);
}
public async Task<List<string>> GroupCreateTempInvite(GroupDto group, int amount)
@@ -66,10 +66,16 @@ public partial class ApiController
return await _mareHub!.InvokeAsync<List<BannedGroupUserDto>>(nameof(GroupGetBannedUsers), group).ConfigureAwait(false);
}
public async Task<bool> GroupJoin(GroupPasswordDto passwordedGroup)
public async Task<GroupJoinInfoDto> GroupJoin(GroupPasswordDto passwordedGroup)
{
CheckConnection();
return await _mareHub!.InvokeAsync<bool>(nameof(GroupJoin), passwordedGroup).ConfigureAwait(false);
return await _mareHub!.InvokeAsync<GroupJoinInfoDto>(nameof(GroupJoin), passwordedGroup).ConfigureAwait(false);
}
public async Task<bool> GroupJoinFinalize(GroupJoinDto passwordedGroup)
{
CheckConnection();
return await _mareHub!.InvokeAsync<bool>(nameof(GroupJoinFinalize), passwordedGroup).ConfigureAwait(false);
}
public async Task GroupLeave(GroupDto group)
@@ -96,12 +102,6 @@ public partial class ApiController
return await _mareHub!.InvokeAsync<List<GroupFullInfoDto>>(nameof(GroupsGetAll)).ConfigureAwait(false);
}
public async Task<List<GroupPairFullInfoDto>> GroupsGetUsersInGroup(GroupDto group)
{
CheckConnection();
return await _mareHub!.InvokeAsync<List<GroupPairFullInfoDto>>(nameof(GroupsGetUsersInGroup), group).ConfigureAwait(false);
}
public async Task GroupUnbanUser(GroupPairDto groupPair)
{
CheckConnection();

View File

@@ -1,23 +1,21 @@
using MareSynchronos.API.Routes;
using MareSynchronos.Utils;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using Dalamud.Utility;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto;
using MareSynchronos.API.SignalR;
using Dalamud.Utility;
using System.Reflection;
using MareSynchronos.WebAPI.SignalR.Utils;
using MareSynchronos.WebAPI.SignalR;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Services;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Data;
using System.Net.Http.Headers;
using MareSynchronos.WebAPI.SignalR;
using MareSynchronos.WebAPI.SignalR.Utils;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using System.Reflection;
namespace MareSynchronos.WebAPI;
#pragma warning disable MA0040
public sealed partial class ApiController : DisposableMediatorSubscriberBase, IMareHubClient
{
public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)";
@@ -27,21 +25,25 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
private readonly HubFactory _hubFactory;
private readonly PairManager _pairManager;
private readonly ServerConfigurationManager _serverManager;
private readonly TokenProvider _tokenProvider;
private CancellationTokenSource _connectionCancellationTokenSource;
private ConnectionDto? _connectionDto;
private bool _doNotNotifyOnNextInfo = false;
private CancellationTokenSource? _healthCheckTokenSource = new();
private bool _initialized;
private string? _lastUsedToken;
private HubConnection? _mareHub;
private ServerState _serverState;
public ApiController(ILogger<ApiController> logger, HubFactory hubFactory, DalamudUtilService dalamudUtil,
PairManager pairManager, ServerConfigurationManager serverManager, MareMediator mediator) : base(logger, mediator)
PairManager pairManager, ServerConfigurationManager serverManager, MareMediator mediator,
TokenProvider tokenProvider) : base(logger, mediator)
{
_hubFactory = hubFactory;
_dalamudUtil = dalamudUtil;
_pairManager = pairManager;
_serverManager = serverManager;
_tokenProvider = tokenProvider;
_connectionCancellationTokenSource = new CancellationTokenSource();
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
@@ -49,7 +51,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
Mediator.Subscribe<HubClosedMessage>(this, (msg) => MareHubOnClosed(msg.Exception));
Mediator.Subscribe<HubReconnectedMessage>(this, (msg) => _ = Task.Run(MareHubOnReconnected));
Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => MareHubOnReconnecting(msg.Exception));
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => CyclePause(msg.UserData));
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePause(msg.UserData));
ServerState = ServerState.Offline;
@@ -63,6 +65,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
public Version CurrentClientVersion => _connectionDto?.CurrentClientVersion ?? new Version(0, 0, 0);
public DefaultPermissionsDto? DefaultPermissions => _connectionDto?.DefaultPreferredPermissions ?? null;
public string DisplayName => _connectionDto?.User.AliasOrUID ?? string.Empty;
public bool IsConnected => ServerState == ServerState.Connected;
@@ -94,7 +97,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
return await _mareHub!.InvokeAsync<bool>(nameof(CheckClientHealth)).ConfigureAwait(false);
}
public async Task CreateConnections(bool forceGetToken = false)
public async Task CreateConnections()
{
Logger.LogDebug("CreateConnections called");
@@ -135,25 +138,14 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
{
Logger.LogDebug("Building connection");
if (_serverManager.GetToken() == null || forceGetToken)
try
{
Logger.LogDebug("Requesting new JWT");
using HttpClient httpClient = new();
var ver = Assembly.GetExecutingAssembly().GetName().Version;
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MareSynchronos", ver!.Major + "." + ver!.Minor + "." + ver!.Build));
var postUri = MareAuth.AuthFullPath(new Uri(_serverManager.CurrentApiUrl
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
var auth = secretKey.GetHash256();
var result = await httpClient.PostAsync(postUri, new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("auth", auth),
new KeyValuePair<string, string>("charaIdent", await _dalamudUtil.GetPlayerNameHashedAsync().ConfigureAwait(false)),
}), token).ConfigureAwait(false);
AuthFailureMessage = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
result.EnsureSuccessStatusCode();
_serverManager.SaveToken(await result.Content.ReadAsStringAsync().ConfigureAwait(false));
Logger.LogDebug("JWT Success");
_lastUsedToken = await _tokenProvider.GetOrUpdateToken(token).ConfigureAwait(false);
}
catch (MareAuthFailureException ex)
{
AuthFailureMessage = ex.Reason;
throw new HttpRequestException("Error during authentication", ex, System.Net.HttpStatusCode.Unauthorized);
}
while (!await _dalamudUtil.GetIsPlayerPresentAsync().ConfigureAwait(false) && !token.IsCancellationRequested)
@@ -164,13 +156,11 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
if (token.IsCancellationRequested) break;
_mareHub = _hubFactory.GetOrCreate();
_mareHub = _hubFactory.GetOrCreate(token);
InitializeApiHooks();
await _mareHub.StartAsync(token).ConfigureAwait(false);
InitializeApiHooks();
await LoadIninitialPairs().ConfigureAwait(false);
_connectionDto = await GetConnectionDto().ConfigureAwait(false);
ServerState = ServerState.Connected;
@@ -200,6 +190,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
Dalamud.Interface.Internal.Notifications.NotificationType.Error));
}
await LoadIninitialPairs().ConfigureAwait(false);
await LoadOnlinePairs().ConfigureAwait(false);
}
catch (OperationCanceledException)
@@ -239,7 +230,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
{
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
var perm = pair.UserPair!.OwnPermissions;
perm.SetPaused(true);
perm.SetPaused(paused: true);
await UserSetPairPermissions(new API.Dto.User.UserPermissionsDto(userData, perm)).ConfigureAwait(false);
// wait until it's changed
while (pair.UserPair!.OwnPermissions != perm)
@@ -247,7 +238,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
await Task.Delay(250, cts.Token).ConfigureAwait(false);
Logger.LogTrace("Waiting for permissions change for {data}", userData);
}
perm.SetPaused(false);
perm.SetPaused(paused: false);
await UserSetPairPermissions(new API.Dto.User.UserPermissionsDto(userData, perm)).ConfigureAwait(false);
}, cts.Token).ContinueWith((t) => cts.Dispose());
@@ -266,7 +257,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
base.Dispose(disposing);
_healthCheckTokenSource?.Cancel();
Task.Run(async () => await StopConnection(ServerState.Disconnected).ConfigureAwait(false));
_ = Task.Run(async () => await StopConnection(ServerState.Disconnected).ConfigureAwait(false));
_connectionCancellationTokenSource?.Cancel();
}
@@ -275,19 +266,24 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
while (!ct.IsCancellationRequested && _mareHub != null)
{
await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false);
Logger.LogDebug("Checking Client Health State");
bool requireReconnect = await RefreshToken(ct).ConfigureAwait(false);
if (requireReconnect) continue;
_ = await CheckClientHealth().ConfigureAwait(false);
Logger.LogDebug("Checked Client Health State");
}
}
private void DalamudUtilOnLogIn()
{
Task.Run(() => CreateConnections(forceGetToken: true));
_ = Task.Run(() => CreateConnections());
}
private void DalamudUtilOnLogOut()
{
Task.Run(async () => await StopConnection(ServerState.Disconnected).ConfigureAwait(false));
_ = Task.Run(async () => await StopConnection(ServerState.Disconnected).ConfigureAwait(false));
ServerState = ServerState.Offline;
}
@@ -296,28 +292,30 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
if (_mareHub == null) return;
Logger.LogDebug("Initializing data");
OnDownloadReady((guid) => Client_DownloadReady(guid));
OnReceiveServerMessage((sev, msg) => Client_ReceiveServerMessage(sev, msg));
OnUpdateSystemInfo((dto) => Client_UpdateSystemInfo(dto));
OnDownloadReady((guid) => _ = Client_DownloadReady(guid));
OnReceiveServerMessage((sev, msg) => _ = Client_ReceiveServerMessage(sev, msg));
OnUpdateSystemInfo((dto) => _ = Client_UpdateSystemInfo(dto));
OnUserSendOffline((dto) => Client_UserSendOffline(dto));
OnUserAddClientPair((dto) => Client_UserAddClientPair(dto));
OnUserReceiveCharacterData((dto) => Client_UserReceiveCharacterData(dto));
OnUserRemoveClientPair(dto => Client_UserRemoveClientPair(dto));
OnUserSendOnline(dto => Client_UserSendOnline(dto));
OnUserUpdateOtherPairPermissions(dto => Client_UserUpdateOtherPairPermissions(dto));
OnUserUpdateSelfPairPermissions(dto => Client_UserUpdateSelfPairPermissions(dto));
OnUserReceiveUploadStatus(dto => Client_UserReceiveUploadStatus(dto));
OnUserUpdateProfile(dto => Client_UserUpdateProfile(dto));
OnUserSendOffline((dto) => _ = Client_UserSendOffline(dto));
OnUserAddClientPair((dto) => _ = Client_UserAddClientPair(dto));
OnUserReceiveCharacterData((dto) => _ = Client_UserReceiveCharacterData(dto));
OnUserRemoveClientPair(dto => _ = Client_UserRemoveClientPair(dto));
OnUserSendOnline(dto => _ = Client_UserSendOnline(dto));
OnUserUpdateOtherPairPermissions(dto => _ = Client_UserUpdateOtherPairPermissions(dto));
OnUserUpdateSelfPairPermissions(dto => _ = Client_UserUpdateSelfPairPermissions(dto));
OnUserReceiveUploadStatus(dto => _ = Client_UserReceiveUploadStatus(dto));
OnUserUpdateProfile(dto => _ = Client_UserUpdateProfile(dto));
OnUserDefaultPermissionUpdate(dto => _ = Client_UserUpdateDefaultPermissions(dto));
OnUpdateUserIndividualPairStatusDto(dto => _ = Client_UpdateUserIndividualPairStatusDto(dto));
OnGroupChangePermissions((dto) => Client_GroupChangePermissions(dto));
OnGroupDelete((dto) => Client_GroupDelete(dto));
OnGroupPairChangePermissions((dto) => Client_GroupPairChangePermissions(dto));
OnGroupPairChangeUserInfo((dto) => Client_GroupPairChangeUserInfo(dto));
OnGroupPairJoined((dto) => Client_GroupPairJoined(dto));
OnGroupPairLeft((dto) => Client_GroupPairLeft(dto));
OnGroupSendFullInfo((dto) => Client_GroupSendFullInfo(dto));
OnGroupSendInfo((dto) => Client_GroupSendInfo(dto));
OnGroupChangePermissions((dto) => _ = Client_GroupChangePermissions(dto));
OnGroupDelete((dto) => _ = Client_GroupDelete(dto));
OnGroupPairChangeUserInfo((dto) => _ = Client_GroupPairChangeUserInfo(dto));
OnGroupPairJoined((dto) => _ = Client_GroupPairJoined(dto));
OnGroupPairLeft((dto) => _ = Client_GroupPairLeft(dto));
OnGroupSendFullInfo((dto) => _ = Client_GroupSendFullInfo(dto));
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
_healthCheckTokenSource?.Cancel();
_healthCheckTokenSource?.Dispose();
@@ -329,24 +327,16 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
private async Task LoadIninitialPairs()
{
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
{
Logger.LogDebug("Individual Pair: {userPair}", userPair);
_pairManager.AddUserPair(userPair, addToLastAddedUser: false);
}
foreach (var entry in await GroupsGetAll().ConfigureAwait(false))
{
Logger.LogDebug("Group: {entry}", entry);
_pairManager.AddGroup(entry);
}
foreach (var group in _pairManager.GroupPairs.Keys)
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
{
var users = await GroupsGetUsersInGroup(group).ConfigureAwait(false);
foreach (var user in users)
{
Logger.LogDebug("Group Pair: {user}", user);
_pairManager.AddGroupPair(user);
}
Logger.LogDebug("Individual Pair: {userPair}", userPair);
_pairManager.AddUserPair(userPair);
}
}
@@ -405,6 +395,33 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
Logger.LogWarning(arg, "Connection closed... Reconnecting");
}
private async Task<bool> RefreshToken(CancellationToken ct)
{
Logger.LogDebug("Checking token");
bool requireReconnect = false;
try
{
var token = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
if (!string.Equals(token, _lastUsedToken, StringComparison.Ordinal))
{
Logger.LogDebug("Reconnecting due to updated token");
_doNotNotifyOnNextInfo = true;
await CreateConnections().ConfigureAwait(false);
requireReconnect = true;
}
}
catch (MareAuthFailureException ex)
{
AuthFailureMessage = ex.Reason;
await StopConnection(ServerState.Unauthorized).ConfigureAwait(false);
requireReconnect = true;
}
return requireReconnect;
}
private async Task StopConnection(ServerState state)
{
ServerState = ServerState.Disconnecting;
@@ -421,7 +438,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
_connectionDto = null;
}
ServerState = state;
}
}
}
#pragma warning restore MA0040

View File

@@ -16,28 +16,58 @@ namespace MareSynchronos.WebAPI.SignalR;
public class HubFactory : MediatorSubscriberBase
{
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly MareConfigService _configService;
private readonly IPluginLog _pluginLog;
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly TokenProvider _tokenProvider;
private HubConnection? _instance;
private bool _isDisposed = false;
public HubFactory(ILogger<HubFactory> logger, MareMediator mediator, ServerConfigurationManager serverConfigurationManager, MareConfigService configService,
IPluginLog pluginLog) : base(logger, mediator)
public HubFactory(ILogger<HubFactory> logger, MareMediator mediator,
ServerConfigurationManager serverConfigurationManager, MareConfigService configService,
TokenProvider tokenProvider, IPluginLog pluginLog) : base(logger, mediator)
{
_serverConfigurationManager = serverConfigurationManager;
_configService = configService;
_tokenProvider = tokenProvider;
_pluginLog = pluginLog;
}
private HubConnection BuildHubConnection()
public async Task DisposeHubAsync()
{
if (_instance == null || _isDisposed) return;
Logger.LogDebug("Disposing current HubConnection");
_isDisposed = true;
_instance.Closed -= HubOnClosed;
_instance.Reconnecting -= HubOnReconnecting;
_instance.Reconnected -= HubOnReconnected;
await _instance.StopAsync().ConfigureAwait(false);
await _instance.DisposeAsync().ConfigureAwait(false);
_instance = null;
Logger.LogDebug("Current HubConnection disposed");
}
public HubConnection GetOrCreate(CancellationToken ct)
{
if (!_isDisposed && _instance != null) return _instance;
return BuildHubConnection(ct);
}
private HubConnection BuildHubConnection(CancellationToken ct)
{
Logger.LogDebug("Building new HubConnection");
_instance = new HubConnectionBuilder()
.WithUrl(_serverConfigurationManager.CurrentApiUrl + IMareHub.Path, options =>
{
options.Headers.Add("Authorization", "Bearer " + _serverConfigurationManager.GetToken());
options.AccessTokenProvider = () => _tokenProvider.GetOrUpdateToken(ct);
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
})
.AddMessagePackProtocol(opt =>
@@ -76,6 +106,12 @@ public class HubFactory : MediatorSubscriberBase
return _instance;
}
private Task HubOnClosed(Exception? arg)
{
Mediator.Publish(new HubClosedMessage(arg));
return Task.CompletedTask;
}
private Task HubOnReconnected(string? arg)
{
Mediator.Publish(new HubReconnectedMessage(arg));
@@ -87,37 +123,4 @@ public class HubFactory : MediatorSubscriberBase
Mediator.Publish(new HubReconnectingMessage(arg));
return Task.CompletedTask;
}
private Task HubOnClosed(Exception? arg)
{
Mediator.Publish(new HubClosedMessage(arg));
return Task.CompletedTask;
}
public HubConnection GetOrCreate()
{
if (!_isDisposed && _instance != null) return _instance;
return BuildHubConnection();
}
public async Task DisposeHubAsync()
{
if (_instance == null || _isDisposed) return;
Logger.LogDebug("Disposing current HubConnection");
_isDisposed = true;
_instance.Closed -= HubOnClosed;
_instance.Reconnecting -= HubOnReconnecting;
_instance.Reconnected -= HubOnReconnected;
await _instance.StopAsync().ConfigureAwait(false);
await _instance.DisposeAsync().ConfigureAwait(false);
_instance = null;
Logger.LogDebug("Current HubConnection disposed");
}
}
}

View File

@@ -0,0 +1,3 @@
namespace MareSynchronos.WebAPI.SignalR;
public record JwtIdentifier(string ApiUrl, string SecretKey);

View File

@@ -0,0 +1,11 @@
namespace MareSynchronos.WebAPI.SignalR;
public class MareAuthFailureException : Exception
{
public MareAuthFailureException(string reason)
{
Reason = reason;
}
public string Reason { get; }
}

View File

@@ -0,0 +1,118 @@
using MareSynchronos.API.Routes;
using MareSynchronos.Services;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Utils;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Reflection;
namespace MareSynchronos.WebAPI.SignalR;
public sealed class TokenProvider : IDisposable
{
private readonly DalamudUtilService _dalamudUtil;
private readonly HttpClient _httpClient;
private readonly ILogger<TokenProvider> _logger;
private readonly ServerConfigurationManager _serverManager;
private readonly ConcurrentDictionary<JwtIdentifier, string> _tokenCache = new();
public TokenProvider(ILogger<TokenProvider> logger, ServerConfigurationManager serverManager, DalamudUtilService dalamudUtil)
{
_logger = logger;
_serverManager = serverManager;
_dalamudUtil = dalamudUtil;
_httpClient = new();
var ver = Assembly.GetExecutingAssembly().GetName().Version;
_httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MareSynchronos", ver!.Major + "." + ver!.Minor + "." + ver!.Build));
}
private JwtIdentifier CurrentIdentifier => new(_serverManager.CurrentApiUrl, _serverManager.GetSecretKey()!);
public void Dispose()
{
_httpClient.Dispose();
}
public async Task<string> GetNewToken(bool isRenewal, CancellationToken token)
{
Uri tokenUri;
string response = string.Empty;
HttpResponseMessage result;
try
{
if (!isRenewal)
{
_logger.LogDebug("GetNewToken: Requesting");
tokenUri = MareAuth.AuthFullPath(new Uri(_serverManager.CurrentApiUrl
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
var secretKey = _serverManager.GetSecretKey()!;
var auth = secretKey.GetHash256();
result = await _httpClient.PostAsync(tokenUri, new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("auth", auth),
new KeyValuePair<string, string>("charaIdent", await _dalamudUtil.GetPlayerNameHashedAsync().ConfigureAwait(false)),
}), token).ConfigureAwait(false);
}
else
{
_logger.LogDebug("GetNewToken: Renewal");
tokenUri = MareAuth.RenewTokenFullPath(new Uri(_serverManager.CurrentApiUrl
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
HttpRequestMessage request = new(HttpMethod.Get, tokenUri.ToString());
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenCache[CurrentIdentifier]);
result = await _httpClient.SendAsync(request, token).ConfigureAwait(false);
}
response = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
result.EnsureSuccessStatusCode();
_tokenCache[CurrentIdentifier] = response;
}
catch (HttpRequestException ex)
{
_tokenCache.TryRemove(CurrentIdentifier, out _);
_logger.LogError(ex, "GetNewToken: Failure to get token");
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
throw new MareAuthFailureException(response);
}
throw;
}
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(response);
_logger.LogTrace("GetNewToken: JWT {token}", response);
_logger.LogDebug("GetNewToken: Valid until {date}, ValidClaim until {date}", jwtToken.ValidTo,
new DateTime(long.Parse(jwtToken.Claims.Single(c => string.Equals(c.Type, "expiration_date", StringComparison.Ordinal)).Value), DateTimeKind.Utc));
return response;
}
public async Task<string?> GetOrUpdateToken(CancellationToken ct, bool forceRenew = false)
{
bool renewal = forceRenew;
if (!forceRenew && _tokenCache.TryGetValue(CurrentIdentifier, out var token))
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
if (jwtToken.ValidTo == DateTime.MinValue || jwtToken.ValidTo.Subtract(TimeSpan.FromMinutes(5)) > DateTime.UtcNow)
{
_logger.LogTrace("GetOrUpdate: Returning token from cache");
return token;
}
renewal = true;
}
_logger.LogTrace("GetOrUpdate: Getting new token");
return await GetNewToken(renewal, ct).ConfigureAwait(false);
}
}