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
+8 -45
View File
@@ -585,54 +585,17 @@ async function loadSettings() {
if (!res.ok) return;
const opt = await res.json();
document.getElementById('cfgEnabled').checked = opt.enabled;
document.getElementById('cfgPort').value = opt.port;
document.getElementById('cfgLocalOnly').checked = opt.localOnly;
document.getElementById('cfgToken').value = opt.token || '';
document.getElementById('cfgEnabled').textContent = opt.enabled ? 'aktiv' : 'deaktiviert';
document.getElementById('cfgPort').textContent = opt.port;
document.getElementById('cfgBind').textContent = opt.localOnly
? 'nur lokal (127.0.0.1)'
: 'im Netzwerk – alle Schnittstellen, unverschlüsseltes HTTP';
document.getElementById('cfgTokenStatus').textContent = opt.hasToken
? 'gesetzt'
: 'nicht gesetzt – das WebUI ist gesperrt';
} catch (e) {}
}
async function saveSettings(e) {
e.preventDefault();
const enabled = document.getElementById('cfgEnabled').checked;
const port = parseInt(document.getElementById('cfgPort').value, 10);
const localOnly = document.getElementById('cfgLocalOnly').checked;
const token = document.getElementById('cfgToken').value.trim();
try {
const res = await fetch('/api/settings/webui', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled, port, localOnly, authToken: token })
});
if (res.ok) {
const msg = document.getElementById('settingsStatusMsg');
msg.style.display = 'inline';
setTimeout(() => { msg.style.display = 'none'; }, 3000);
}
} catch (e) {}
}
async function generateNewToken() {
if (!confirm('Neues Zugangs-Token generieren? Alle bestehenden Sessions müssen sich danach neu anmelden.')) return;
try {
const res = await fetch('/api/settings/webui/generate-token', { method: 'POST' });
if (res.ok) {
const data = await res.json();
document.getElementById('cfgToken').value = data.token;
alert('Neues Token generiert und in appsettings.Local.json gespeichert:\n\n' + data.token);
}
} catch (e) {}
}
function copyToken() {
const input = document.getElementById('cfgToken');
if (!input.value) return;
navigator.clipboard.writeText(input.value).then(() => {
alert('Token in Zwischenablage kopiert!');
});
}
// Auth
async function logout() {
try {