From c771a9a4ff0c3c0a9215b5ffbec19e18438dc7b5 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 14 Dec 2024 14:30:41 +0100 Subject: [PATCH] add renew and refresh oauth buttons --- MareSynchronos/Plugin.cs | 3 +- MareSynchronos/UI/UISharedService.cs | 93 +++++++++++++++---- .../WebAPI/SignalR/ApiController.cs | 2 +- .../WebAPI/SignalR/TokenProvider.cs | 20 ++-- 4 files changed, 88 insertions(+), 30 deletions(-) diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 763da07..f5b7c8c 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -202,7 +202,8 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService(), s.GetRequiredService())); collection.AddScoped((s) => new UiSharedService(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), - pluginInterface, textureProvider, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); + pluginInterface, textureProvider, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), + s.GetRequiredService())); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs index b202489..560d1bc 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -21,7 +21,9 @@ using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Utils; using MareSynchronos.WebAPI; +using MareSynchronos.WebAPI.SignalR; using Microsoft.Extensions.Logging; +using System.IdentityModel.Tokens.Jwt; using System.Numerics; using System.Runtime.InteropServices; using System.Text; @@ -56,6 +58,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase private readonly ITextureProvider _textureProvider; private readonly Dictionary _selectedComboItems = new(StringComparer.Ordinal); private readonly ServerConfigurationManager _serverConfigurationManager; + private readonly TokenProvider _tokenProvider; private bool _cacheDirectoryHasOtherFilesThanCache = false; private bool _cacheDirectoryIsValidPath = true; @@ -79,13 +82,14 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase private bool _petNamesExists = false; private int _serverSelectionIndex = -1; + private Dictionary _oauthTokenExpiry = new(); public UiSharedService(ILogger logger, IpcManager ipcManager, ApiController apiController, CacheMonitor cacheMonitor, FileDialogManager fileDialogManager, MareConfigService configService, DalamudUtilService dalamudUtil, IDalamudPluginInterface pluginInterface, ITextureProvider textureProvider, Dalamud.Localization localization, - ServerConfigurationManager serverManager, MareMediator mediator) : base(logger, mediator) + ServerConfigurationManager serverManager, TokenProvider tokenProvider, MareMediator mediator) : base(logger, mediator) { _ipcManager = ipcManager; _apiController = apiController; @@ -97,6 +101,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase _textureProvider = textureProvider; _localization = localization; _serverConfigurationManager = serverManager; + _tokenProvider = tokenProvider; _localization.SetupWithLangCode("en"); _isDirectoryWritable = IsDirectoryWritable(_configService.Current.CacheFolder); @@ -912,7 +917,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase public void DrawOAuth(ServerStorage selectedServer) { var oauthToken = selectedServer.OAuthToken; - using var _ = ImRaii.PushIndent(10f); + _ = ImRaii.PushIndent(10f); if (oauthToken == null) { if (_discordOAuthCheck == null) @@ -977,34 +982,84 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase if (oauthToken != null) { - ColorTextWrapped($"OAuth2 is enabled, linked to: Discord User {_serverConfigurationManager.GetDiscordUserFromToken(selectedServer)}", ImGuiColors.HealerGreen); - if ((_discordOAuthUIDs == null || _discordOAuthUIDs.IsCompleted) - && IconTextButton(FontAwesomeIcon.Question, "Check Discord Connection")) + if (!_oauthTokenExpiry.TryGetValue(oauthToken, out DateTime tokenExpiry)) { - _discordOAuthUIDs = _serverConfigurationManager.GetUIDsWithDiscordToken(selectedServer.ServerUri, oauthToken); - } - else if (_discordOAuthUIDs != null) - { - if (!_discordOAuthUIDs.IsCompleted) + try { - ColorTextWrapped("Checking UIDs on Server", ImGuiColors.DalamudYellow); + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(oauthToken); + tokenExpiry = _oauthTokenExpiry[oauthToken] = jwt.ValidTo; } - else + catch (Exception ex) { - var foundUids = _discordOAuthUIDs.Result?.Count ?? 0; - var primaryUid = _discordOAuthUIDs.Result?.FirstOrDefault() ?? new KeyValuePair(string.Empty, string.Empty); - var vanity = string.IsNullOrEmpty(primaryUid.Value) ? "-" : primaryUid.Value; - if (foundUids > 0) + Logger.LogWarning(ex, "Could not parse OAuth token, deleting"); + selectedServer.OAuthToken = null; + _serverConfigurationManager.Save(); + } + } + + if (tokenExpiry > DateTime.UtcNow) + { + ColorTextWrapped($"OAuth2 is enabled, linked to: Discord User {_serverConfigurationManager.GetDiscordUserFromToken(selectedServer)}", ImGuiColors.HealerGreen); + TextWrapped($"The OAuth2 token will expire on {tokenExpiry:yyyy-MM-dd} and automatically renew itself during login on or after {(tokenExpiry - TimeSpan.FromDays(7)):yyyy-MM-dd}."); + using (ImRaii.Disabled(!CtrlPressed())) + { + if (IconTextButton(FontAwesomeIcon.Exclamation, "Renew OAuth2 token manually") && CtrlPressed()) { - ColorTextWrapped($"Found {foundUids} associated UIDs on the server, Primary UID: {primaryUid.Key} (Vanity UID: {vanity})", - ImGuiColors.HealerGreen); + _ = _tokenProvider.TryUpdateOAuth2LoginTokenAsync(selectedServer, forced: true) + .ContinueWith((_) => _apiController.CreateConnectionsAsync()); + } + } + DrawHelpText("Hold CTRL to manually refresh your OAuth2 token. Normally you do not need to do this."); + ImGuiHelpers.ScaledDummy(10f); + + if ((_discordOAuthUIDs == null || _discordOAuthUIDs.IsCompleted) + && IconTextButton(FontAwesomeIcon.Question, "Check Discord Connection")) + { + _discordOAuthUIDs = _serverConfigurationManager.GetUIDsWithDiscordToken(selectedServer.ServerUri, oauthToken); + } + else if (_discordOAuthUIDs != null) + { + if (!_discordOAuthUIDs.IsCompleted) + { + ColorTextWrapped("Checking UIDs on Server", ImGuiColors.DalamudYellow); } else { - ColorTextWrapped($"Found no UIDs associated to this linked OAuth2 account", ImGuiColors.DalamudRed); + var foundUids = _discordOAuthUIDs.Result?.Count ?? 0; + var primaryUid = _discordOAuthUIDs.Result?.FirstOrDefault() ?? new KeyValuePair(string.Empty, string.Empty); + var vanity = string.IsNullOrEmpty(primaryUid.Value) ? "-" : primaryUid.Value; + if (foundUids > 0) + { + ColorTextWrapped($"Found {foundUids} associated UIDs on the server, Primary UID: {primaryUid.Key} (Vanity UID: {vanity})", + ImGuiColors.HealerGreen); + } + else + { + ColorTextWrapped($"Found no UIDs associated to this linked OAuth2 account", ImGuiColors.DalamudRed); + } } } } + else + { + ColorTextWrapped("The OAuth2 token is stale and expired. Please renew the OAuth2 connection.", ImGuiColors.DalamudRed); + if (IconTextButton(FontAwesomeIcon.Exclamation, "Renew OAuth2 connection")) + { + selectedServer.OAuthToken = null; + _serverConfigurationManager.Save(); + _ = _serverConfigurationManager.CheckDiscordOAuth(selectedServer.ServerUri) + .ContinueWith(async (urlTask) => + { + var url = await urlTask.ConfigureAwait(false); + var token = await _serverConfigurationManager.GetDiscordOAuthToken(url!, selectedServer.ServerUri, CancellationToken.None).ConfigureAwait(false); + selectedServer.OAuthToken = token; + _serverConfigurationManager.Save(); + await _apiController.CreateConnectionsAsync().ConfigureAwait(false); + }); + } + } + DrawUnlinkOAuthButton(selectedServer); } } diff --git a/MareSynchronos/WebAPI/SignalR/ApiController.cs b/MareSynchronos/WebAPI/SignalR/ApiController.cs index 7f3ac77..e7f0665 100644 --- a/MareSynchronos/WebAPI/SignalR/ApiController.cs +++ b/MareSynchronos/WebAPI/SignalR/ApiController.cs @@ -173,7 +173,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM return; } - if (!await _tokenProvider.TryUpdateOAuth2LoginTokenAsync().ConfigureAwait(false)) + if (!await _tokenProvider.TryUpdateOAuth2LoginTokenAsync(_serverManager.CurrentServer).ConfigureAwait(false)) { Logger.LogWarning("OAuth2 login token could not be updated"); _connectionDto = null; diff --git a/MareSynchronos/WebAPI/SignalR/TokenProvider.cs b/MareSynchronos/WebAPI/SignalR/TokenProvider.cs index eb9766b..56c9c60 100644 --- a/MareSynchronos/WebAPI/SignalR/TokenProvider.cs +++ b/MareSynchronos/WebAPI/SignalR/TokenProvider.cs @@ -235,21 +235,23 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber return await GetNewToken(renewal, jwtIdentifier, ct).ConfigureAwait(false); } - public async Task TryUpdateOAuth2LoginTokenAsync() + public async Task TryUpdateOAuth2LoginTokenAsync(ServerStorage currentServer, bool forced = false) { var oauth2 = _serverManager.GetOAuth2(out _); if (oauth2 == null) return false; var handler = new JwtSecurityTokenHandler(); var jwt = handler.ReadJwtToken(oauth2.Value.OAuthToken); - if (jwt.ValidTo == DateTime.MinValue || jwt.ValidTo.Subtract(TimeSpan.FromDays(7)) > DateTime.Now) - return true; + if (!forced) + { + if (jwt.ValidTo == DateTime.MinValue || jwt.ValidTo.Subtract(TimeSpan.FromDays(7)) > DateTime.Now) + return true; - if (jwt.ValidTo < DateTime.UtcNow) - return false; + if (jwt.ValidTo < DateTime.UtcNow) + return false; + } - - var tokenUri = MareAuth.RenewOAuthTokenFullPath(new Uri(_serverManager.CurrentApiUrl + var tokenUri = MareAuth.RenewOAuthTokenFullPath(new Uri(currentServer.ServerUri .Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase) .Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase))); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, tokenUri.ToString()); @@ -260,13 +262,13 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber if (!result.IsSuccessStatusCode) { _logger.LogWarning("Could not renew OAuth2 Login token, error code {error}", result.StatusCode); - _serverManager.CurrentServer.OAuthToken = null; + currentServer.OAuthToken = null; _serverManager.Save(); return false; } var newToken = await result.Content.ReadAsStringAsync().ConfigureAwait(false); - _serverManager.CurrentServer.OAuthToken = newToken; + currentServer.OAuthToken = newToken; _serverManager.Save(); return true;