From e2931c73ed34a0196d40e8f731c43e95d93f4cdc Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Mon, 2 Jan 2023 17:08:56 +0100 Subject: [PATCH] Switch to JWT authentication (#32) * switch to jwt authentication * update api to main/jwt * bump version Co-authored-by: rootdarkarchon --- MareAPI | 2 +- MareSynchronos/MareSynchronos.csproj | 2 +- MareSynchronos/UI/SettingsUi.cs | 2 +- MareSynchronos/UI/UIShared.cs | 4 +-- .../WebAPI/ApIController.Functions.Files.cs | 12 ++++---- MareSynchronos/WebAPI/ApiController.cs | 30 ++++++++++++++++--- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/MareAPI b/MareAPI index c01c499..6645eaf 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit c01c4990d48f0d8eb5cfc197b63d86957df340f2 +Subproject commit 6645eaf63fe7c44669f0d62ab95003bcf1d3d04d diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index bf6d32f..acf4754 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -3,7 +3,7 @@ - 0.5.18 + 0.5.19 https://github.com/Penumbra-Sync/client diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 69f8f06..dec4381 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -512,7 +512,7 @@ public class SettingsUi : Window, IDisposable { _configuration.FullPause = marePaused; _configuration.Save(); - Task.Run(_apiController.CreateConnections); + Task.Run(() => _apiController.CreateConnections(false)); } UiShared.DrawHelpText("Completely pauses the sync and clears your current data (not uploaded files) on the service."); diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index f86c75c..a817e3c 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -395,7 +395,7 @@ public class UiShared : IDisposable { _pluginConfiguration.FullPause = false; _pluginConfiguration.Save(); - Task.Run(_apiController.CreateConnections); + Task.Run(() => _apiController.CreateConnections(true)); } } @@ -432,7 +432,7 @@ public class UiShared : IDisposable _pluginConfiguration.ClientSecret[_pluginConfiguration.ApiUri] = _secretKey; _pluginConfiguration.Save(); _secretKey = string.Empty; - Task.Run(_apiController.CreateConnections); + Task.Run(() => _apiController.CreateConnections(true)); _enterSecretKey = false; callBackOnExit?.Invoke(); } diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index 738c5ec..2e8bc7d 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -46,24 +46,26 @@ public partial class ApiController private async Task DownloadFileHttpClient(Uri url, IProgress progress, CancellationToken ct) { using var client = new HttpClient(); - client.DefaultRequestHeaders.Add("Authorization", SecretKey); + client.DefaultRequestHeaders.Add(AuthorizationJwtHeader.Key, AuthorizationJwtHeader.Value); int attempts = 0; bool failed = true; const int maxAttempts = 10; HttpResponseMessage response = null!; HttpStatusCode? lastError = HttpStatusCode.OK; + var bypassUrl = new Uri(url, "?nocache=" + DateTime.UtcNow.Ticks); + while (failed && attempts < maxAttempts && !ct.IsCancellationRequested) { try { - response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false); + response = await client.GetAsync(bypassUrl, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false); response.EnsureSuccessStatusCode(); failed = false; } catch (HttpRequestException ex) { - Logger.Warn($"Attempt {attempts}: Error during download of {url}, HttpStatusCode: {ex.StatusCode}"); + Logger.Warn($"Attempt {attempts}: Error during download of {bypassUrl}, HttpStatusCode: {ex.StatusCode}"); lastError = ex.StatusCode; if (ex.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Unauthorized) { @@ -98,13 +100,13 @@ public partial class ApiController progress.Report(bytesRead); } - Logger.Debug($"{url} downloaded to {fileName}"); + Logger.Debug($"{bypassUrl} downloaded to {fileName}"); return fileName; } } catch (Exception ex) { - Logger.Warn($"Error during file download of {url}", ex); + Logger.Warn($"Error during file download of {bypassUrl}", ex); try { File.Delete(fileName); diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs index 83fee94..63c8035 100644 --- a/MareSynchronos/WebAPI/ApiController.cs +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -3,6 +3,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Security.Cryptography; +using System.Text; using System.Threading; using System.Threading.Tasks; using MareSynchronos.API; @@ -29,6 +31,8 @@ public partial class ApiController : IDisposable, IMareHubClient private readonly DalamudUtil _dalamudUtil; private readonly FileCacheManager _fileDbManager; private CancellationTokenSource _connectionCancellationTokenSource; + private string _jwtToken = string.Empty; + private KeyValuePair AuthorizationJwtHeader => new("Authorization", "Bearer " + _jwtToken); private HubConnection? _mareHub; @@ -70,7 +74,7 @@ public partial class ApiController : IDisposable, IMareHubClient private void DalamudUtilOnLogIn() { - Task.Run(CreateConnections); + Task.Run(() => CreateConnections(true)); } @@ -134,7 +138,7 @@ public partial class ApiController : IDisposable, IMareHubClient } } - public async Task CreateConnections() + public async Task CreateConnections(bool forceGetToken = false) { Logger.Debug("CreateConnections called"); @@ -169,6 +173,24 @@ public partial class ApiController : IDisposable, IMareHubClient { Logger.Debug("Building connection"); + if (string.IsNullOrEmpty(_jwtToken) || forceGetToken) + { + Logger.Debug("Requesting new JWT token"); + using HttpClient httpClient = new(); + var postUri = new Uri(new Uri(ApiUri + .Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase) + .Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)), MareAuth.AuthFullPath); + using var sha256 = SHA256.Create(); + var auth = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(SecretKey))).Replace("-", "", StringComparison.OrdinalIgnoreCase); + var result = await httpClient.PostAsync(postUri, new FormUrlEncodedContent(new[] + { + new KeyValuePair("auth", auth) + })).ConfigureAwait(false); + result.EnsureSuccessStatusCode(); + _jwtToken = await result.Content.ReadAsStringAsync().ConfigureAwait(false); + Logger.Debug("JWT Token Success"); + } + while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested) { Logger.Debug("Player not loaded in yet, waiting"); @@ -253,7 +275,7 @@ public partial class ApiController : IDisposable, IMareHubClient private Task MareHubOnReconnected(string? arg) { - _ = Task.Run(CreateConnections); + _ = Task.Run(() => CreateConnections(false)); return Task.CompletedTask; } @@ -332,7 +354,7 @@ public partial class ApiController : IDisposable, IMareHubClient return new HubConnectionBuilder() .WithUrl(ApiUri + hubName, options => { - options.Headers.Add("Authorization", SecretKey); + options.Headers.Add(AuthorizationJwtHeader); options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling; }) .WithAutomaticReconnect(new ForeverRetryPolicy())