WebUI gibt keine Geheimnisse mehr heraus und aendert keine Zugangseinstellungen

Behebt #562.

Entscheidung Richard, 13.09.2026: Geheimnisse verwalten wir ausschliesslich im
Desktop.

Bis dahin lieferte GET /api/settings/webui das Zugangs-Token im Klartext. Ueber
das WebUI liessen sich ausserdem ein Token setzen, ein neues erzeugen und
LocalOnly abschalten. Wer das Token hatte, konnte das WebUI damit ueber
unverschluesseltes HTTP ins Netz oeffnen, sich den Zugang verlegen oder sich
selbst aussperren.

Jetzt:
- GET /api/settings/webui liefert Enabled, Port, LocalOnly und vom Token nur
  hasToken.
- POST /api/settings/webui und /webui/generate-token antworten mit 403 und
  protokollieren den Versuch. Sie bleiben als ausdrueckliche Ablehnung stehen:
  Ohne eigene Route faengt die Fallback-Route den POST ab und antwortet mit 200,
  ein altes Skript hielte das fuer einen Erfolg.
- Die WebUI-Einstellungsansicht zeigt den Zugang nur noch an. Gepflegt wird er
  im Desktop unter Einstellungen -> WebUI.

WebUiSecretsTests zaehlt alle GET-Routen selbst auf und prueft jede Antwort
gegen Kanarienwerte. Geprueft werden ApiKey, ApiSecret, ApiPassphrase und
PrivateKey eines Kontos, Mullvad-Account, Alchemy-Key, Watchdog-Token,
Lizenzschluessel und das WebUI-Token. Eine neue Route, die ein Konto oder die
Server-Einstellungen serialisiert, faellt damit auf, ohne dass jemand an diesen
Test denken muss. Gegen die alte Route schlug er fehl ("GET /api/settings/webui
liefert ein Geheimnis aus"), jetzt ist er gruen.

Ausserdem festgehalten: /api/accounts kennt nur GET. Polymarket-Konten bleiben
im WebUI lesbar und nicht aenderbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-13 13:45:54 +02:00
co-authored by Claude Opus 5
parent 2278d1ccf3
commit 99172a6be2
5 changed files with 244 additions and 125 deletions
@@ -7,9 +7,22 @@ using PolyTraderSharp.Services;
namespace PolyTrader.Web.Endpoints
{
/// <summary>
/// Zugangseinstellungen des WebUI - nur lesend (#562).
///
/// <para>Entscheidung Richard, 13.09.2026: Geheimnisse verwalten wir ausschliesslich im Desktop. Bis
/// dahin lieferte GET /api/settings/webui das Zugangs-Token im Klartext aus, und ueber das WebUI
/// liessen sich Token setzen, ein neues erzeugen und LocalOnly abschalten. Wer das Token hatte,
/// konnte damit das WebUI ins Netz oeffnen (unverschluesseltes HTTP), sich den Zugang verlegen oder
/// sich selbst aussperren.</para>
///
/// <para>Port, LocalOnly, Enabled und das Token werden jetzt nur noch im Desktop unter
/// Einstellungen -> WebUI gepflegt. Das WebUI zeigt die Werte an, vom Token nur, ob es gesetzt ist.</para>
/// </summary>
public static class SettingsEndpoints
{
public record UpdateWebUiSettingsRequest(bool Enabled, int Port, bool LocalOnly, string? AuthToken);
internal const string DesktopOnlyMessage =
"Zugang, Port und Token des WebUI werden ausschließlich im Desktop verwaltet (Einstellungen → WebUI).";
public static IEndpointRouteBuilder MapSettingsEndpoints(this IEndpointRouteBuilder routes)
{
@@ -24,51 +37,23 @@ namespace PolyTrader.Web.Endpoints
port = opt.Port,
localOnly = opt.LocalOnly,
hasToken = !string.IsNullOrWhiteSpace(opt.AuthToken),
token = opt.AuthToken
managedIn = "desktop"
});
});
group.MapPost("/webui", (UpdateWebUiSettingsRequest req, IOptions<WebUiOptions> optionsProvider, TerminalLogger logger) =>
{
var opt = optionsProvider.Value;
opt.Enabled = req.Enabled;
opt.Port = req.Port > 0 && req.Port <= 65535 ? req.Port : 6001;
opt.LocalOnly = req.LocalOnly;
if (req.AuthToken != null)
{
opt.AuthToken = req.AuthToken.Trim();
}
WebUiConfigManager.SaveToLocalSettings(opt);
logger.Info($"🌐 WebUI: Einstellungen gespeichert (Port {opt.Port}, LocalOnly: {opt.LocalOnly}).");
return Results.Ok(new
{
success = true,
enabled = opt.Enabled,
port = opt.Port,
localOnly = opt.LocalOnly,
hasToken = !string.IsNullOrWhiteSpace(opt.AuthToken)
});
});
group.MapPost("/webui/generate-token", (IOptions<WebUiOptions> optionsProvider, TerminalLogger logger) =>
{
var opt = optionsProvider.Value;
string newToken = WebUiConfigManager.GenerateSecureToken();
opt.AuthToken = newToken;
WebUiConfigManager.SaveToLocalSettings(opt);
logger.Info("🔐 WebUI: Neues Zugangs-Token generiert und in appsettings.Local.json gespeichert.");
return Results.Ok(new
{
success = true,
token = newToken
});
});
// Die frueheren Schreibwege bleiben als ausdrueckliche Ablehnung stehen, statt zu verschwinden:
// Ohne eigene Route faengt die Fallback-Route (index.html) den POST ab und antwortet mit 200 -
// ein altes Skript hielte das fuer einen Erfolg.
group.MapPost("/webui", RejectDesktopOnly);
group.MapPost("/webui/generate-token", RejectDesktopOnly);
return routes;
}
private static IResult RejectDesktopOnly(HttpContext context, TerminalLogger logger)
{
logger.Warning($"🌐 WebUI: Änderung der Zugangseinstellungen über {context.Request.Path} abgelehnt – nur im Desktop möglich.");
return Results.Json(new { error = DesktopOnlyMessage }, statusCode: StatusCodes.Status403Forbidden);
}
}
}