diff --git a/README.md b/README.md index 748b525..114db64 100644 --- a/README.md +++ b/README.md @@ -163,9 +163,12 @@ Bei HTTP-Fehlern wie `401 Unauthorized` protokolliert der Agent: - ob ein Authorization-Header gesetzt wurde - Authorization-Scheme, z. B. `Bearer` - Token-Länge, aber niemals den Token selbst +- kurzer SHA-256-Fingerprint des Tokens zum Abgleich mit dem Backend - `WWW-Authenticate`-Header des Backends - gekürzten Response-Body des Backends +Beim Speichern wird der API-Token normalisiert. Falls versehentlich `Bearer ...` oder `Authorization: Bearer ...` eingefügt wurde, speichert der Agent nur den eigentlichen Tokenwert. + ## Nicht mehr im Agent - keine Layout-JSON-Verwaltung diff --git a/src/LabelPrintAgent/App/SettingsForm.cs b/src/LabelPrintAgent/App/SettingsForm.cs index 8833f35..ff29dcc 100644 --- a/src/LabelPrintAgent/App/SettingsForm.cs +++ b/src/LabelPrintAgent/App/SettingsForm.cs @@ -168,7 +168,9 @@ internal sealed class SettingsForm : Form var settings = _settingsStore.Load(); settings.Backend.BaseUrl = _backendBaseUrl.Text.Trim(); settings.Backend.AgentId = _agentId.Text.Trim(); - settings.Backend.EncryptedApiToken = _settingsStore.EncryptPassword(_apiToken.Text); + var normalizedToken = SettingsStore.NormalizeBackendApiToken(_apiToken.Text); + settings.Backend.EncryptedApiToken = _settingsStore.EncryptPassword(normalizedToken); + _apiToken.Text = normalizedToken; settings.Backend.NextJobPath = _nextJobPath.Text.Trim(); settings.Backend.ImagePath = _imagePath.Text.Trim(); settings.Backend.ReportSuccessPath = _reportSuccessPath.Text.Trim(); diff --git a/src/LabelPrintAgent/Backend/BackendClient.cs b/src/LabelPrintAgent/Backend/BackendClient.cs index dbe4afe..707fa8f 100644 --- a/src/LabelPrintAgent/Backend/BackendClient.cs +++ b/src/LabelPrintAgent/Backend/BackendClient.cs @@ -1,6 +1,8 @@ using System.Drawing; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Security.Cryptography; +using System.Text; using System.Text.Json; using LabelPrintAgent.Configuration; using Serilog; @@ -117,7 +119,7 @@ public sealed class BackendClient private HttpRequestMessage CreateRequest(HttpMethod method, string url, AppSettings settings) { var request = new HttpRequestMessage(method, url); - var token = _settingsStore.DecryptBackendApiToken(settings); + var token = SettingsStore.NormalizeBackendApiToken(_settingsStore.DecryptBackendApiToken(settings)); if (!string.IsNullOrWhiteSpace(token)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); @@ -154,7 +156,7 @@ public sealed class BackendClient Log.Warning( "Backend request failed: {Method} {Url} -> {StatusCode} {ReasonPhrase}. " + "AuthorizationHeaderPresent={AuthorizationHeaderPresent}, AuthorizationScheme={AuthorizationScheme}, " + - "TokenLength={TokenLength}, WwwAuthenticate={WwwAuthenticate}, ResponseBody={ResponseBody}", + "TokenLength={TokenLength}, TokenSha256Prefix={TokenSha256Prefix}, WwwAuthenticate={WwwAuthenticate}, ResponseBody={ResponseBody}", request.Method.Method, request.RequestUri, (int)response.StatusCode, @@ -162,6 +164,7 @@ public sealed class BackendClient authorization is not null, authorization?.Scheme ?? string.Empty, authorization?.Parameter?.Length ?? 0, + GetTokenSha256Prefix(authorization?.Parameter), wwwAuthenticate, Truncate(responseBody, 2000)); @@ -197,6 +200,17 @@ public sealed class BackendClient return value[..maxLength] + "..."; } + private static string GetTokenSha256Prefix(string? token) + { + if (string.IsNullOrEmpty(token)) + { + return string.Empty; + } + + var hash = SHA256.HashData(Encoding.UTF8.GetBytes(token)); + return Convert.ToHexString(hash)[..12]; + } + private static Bitmap CreateBitmap(byte[] bytes) { using var stream = new MemoryStream(bytes); diff --git a/src/LabelPrintAgent/Configuration/SettingsStore.cs b/src/LabelPrintAgent/Configuration/SettingsStore.cs index 1725120..4ae2c04 100644 --- a/src/LabelPrintAgent/Configuration/SettingsStore.cs +++ b/src/LabelPrintAgent/Configuration/SettingsStore.cs @@ -57,4 +57,25 @@ public sealed class SettingsStore public string EncryptPassword(string password) => _protectedStringService.Protect(password); public string DecryptBackendApiToken(AppSettings settings) => _protectedStringService.Unprotect(settings.Backend.EncryptedApiToken); + + public static string NormalizeBackendApiToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) + { + return string.Empty; + } + + var normalized = token.Trim().Trim('"', '\'').Trim(); + if (normalized.StartsWith("Authorization:", StringComparison.OrdinalIgnoreCase)) + { + normalized = normalized["Authorization:".Length..].Trim(); + } + + if (normalized.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { + normalized = normalized["Bearer ".Length..].Trim(); + } + + return normalized.Trim().Trim('"', '\'').Trim(); + } }