Zugaenge: die Dienste-Token aus App und Panel bedienbar (2026.9.3.8)

Die Token liegen seit jeher in entry.options, geschrieben vom Options-Flow
unter Einstellungen -> Geraete & Dienste -> Konfigurieren. Der Weg bleibt und
ist der Rueckfall, wenn die App den Server nicht erreicht - genau der Fall, in
dem ein abgelaufener Token auffaellt. Erreichbar ist er aber nur ueber HAs
eigene Weboberflaeche; die iOS-App zeigt keine Konfigurationsdialoge. Die neue
Kachel ist die Bedienflaeche dafuer, kein zweiter Speicherort.

Der Wert kommt nie zurueck: veroeffentlicht werden nur "gesetzt/nicht gesetzt"
und die letzten vier Zeichen (unter zwoelf Zeichen Laenge gar nichts). Auch das
Protokoll bekommt ihn nicht, und das Panel leert das Eingabefeld sofort nach
dem Absenden.

Bewusst NICHT im Fahrzeugprofil: sicherung.py sichert fahrzeugprofil.json, und
"Backup exportieren" laedt es als Datei auf das Geraet - ein Token dort wanderte
in jede Sicherung und jede weitergegebene Datei.

Ein Dienst statt zwei: zugang_setzen(dienst, token), leerer Text loescht. In der
App nicht ueber die Warteschlange - ein Stunden spaeter nachgereichter Token
ueberschriebe womoeglich einen inzwischen eingetragenen neuen.

CONF_FLESPI_TOKEN ist neu und noch ohne Leser; der Flespi-Weg zum Auslesen der
Geraetekonfiguration ist damit vorbereitet, aber nicht gebaut.

Verifiziert: py_compile auf sechs Backend-Dateien, Panel als Modul geparst,
companion-app tsc sauber und 179/179, audi_ha_test auf 2026.9.3.8 sauber
gestartet. Live am laufenden Panel der ganze Kreis, Container hinterher wie
vorgefunden: Wegwerf-Token gesetzt -> "gesetzt . ...0000", der Wert selbst
taucht in den veroeffentlichten Attributen nirgends auf (darauf geprueft),
ueber die Sicherheitsabfrage geloescht -> wieder "nicht gesetzt", der
Gitea-Token unberuehrt.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-03 15:08:55 +02:00
co-authored by Claude Opus 5
parent 5ccbfec92d
commit 8c96d9b78e
16 changed files with 400 additions and 8 deletions
@@ -3076,6 +3076,28 @@ function vEinst() {
` : ""}
<button class="aktion ${einrichtenOffen ? "primaer" : ""}" style="margin-top:16px" data-einrichten-auf>${einrichtenOffen ? "Fertig" : "Einrichten"}</button>
</div>
${ZUGAENGE.length ? `<div class="tile"><span class="label">Zugänge</span>
${ZUGAENGE.map((z, i) => `
<button class="acc-h" aria-expanded="false" data-acc="zug${i}"><span class="mark"></span>
<span class="k">${esc(z.titel)}</span>
<span class="v" style="color:${z.gesetzt ? "var(--ok)" : "var(--fg3)"}">${
z.gesetzt ? (z.rest ? "gesetzt · …" + esc(z.rest) : "gesetzt") : "nicht gesetzt"
}</span></button>
<div class="acc-b" id="zug${i}">
<span class="label" style="margin-top:8px;color:var(--fg2);letter-spacing:0;
text-transform:none;font-size:12.5px;line-height:1.6">${esc(z.zweck)}</span>
<div class="feld" style="border-bottom:0"><label for="tok${i}">Neuer Token</label>
<input id="tok${i}" type="password" autocomplete="off" data-tokenfeld="${esc(z.dienst)}"
placeholder="${z.gesetzt ? "ersetzt den hinterlegten" : "noch keiner hinterlegt"}"></div>
<button class="aktion primaer" data-tokenspeichern="${esc(z.dienst)}">Speichern</button>
${z.gesetzt ? `<button class="aktion loeschen" data-tokenloeschen="${esc(z.dienst)}">Löschen</button>` : ""}
</div>`).join("")}
<span class="label" style="margin-top:14px;color:var(--fg2);letter-spacing:0;
text-transform:none;font-size:12.5px;line-height:1.6">
Ein gespeicherter Token wird nie wieder angezeigt nur ob einer hinterlegt ist.
Dieselben Felder gibt es unter Einstellungen Geräte &amp; Dienste Audi Dashboard
Konfigurieren.</span>
</div>` : ""}
<div class="tile"><span class="label">Fahrt beenden</span>
<div class="schieber" style="margin-top:14px">
<input type="range" min="0" max="60" step="5" value="${CONFIG.pausenzeit}"
@@ -4133,6 +4155,9 @@ let APP_VERSION = null;
// geprüft wurde. Ersetzt install.ps1 als Update-Weg, das Windows Smart App
// Control zuverlässig blockiert; siehe vVersion()/Einstellungen-Ansicht.
let INTEGRATION_UPDATE = null;
/* Nur "gesetzt/nicht gesetzt" je Dienst - der Token selbst kommt nie ueber
die Leitung, siehe zugaenge.py im Backend. */
let ZUGAENGE = [];
// "pruefen" | "installieren" | "neustart" | null - während einer dieser drei
// Aktionen läuft, siehe vEinst()'s Integration-Update-Kachel. Eigens statt
// über serviceRufen() (das feuert-und-vergisst), weil diese drei Aktionen
@@ -5976,6 +6001,31 @@ function ereignisseVerdrahten() {
// div ohne data-go, aber die Reihenfolge haelt das auch dann, wenn die
// Kachel spaeter einmal anders gebaut wird.
if (e.target.closest("[data-fahrtbeenden]")) { serviceRufen("fahrt_jetzt_beenden", {}); return; }
const tokSpeichern = e.target.closest("[data-tokenspeichern]");
if (tokSpeichern) {
const dienst = tokSpeichern.dataset.tokenspeichern;
const feld = ROOT.querySelector(`[data-tokenfeld="${dienst}"]`);
const wert = feld ? feld.value.trim() : "";
if (!wert) { hinweis("Kein Token eingegeben", "Bitte erst einen Token eintragen."); return; }
// Das Feld sofort leeren: der Wert ist ab hier nicht mehr unser Geschäft,
// und ein stehengebliebener Token im DOM wäre genau das, was zugaenge.py
// für die Rückgabe ausschließt.
if (feld) feld.value = "";
serviceRufen("zugang_setzen", { dienst, token: wert });
return;
}
const tokLoeschen = e.target.closest("[data-tokenloeschen]");
if (tokLoeschen) {
const dienst = tokLoeschen.dataset.tokenloeschen;
const titel = (ZUGAENGE.find((z) => z.dienst === dienst) || {}).titel || dienst;
bestaetigen(
"Token löschen?",
`Der hinterlegte Token für ${titel} wird entfernt. Die zugehörige Funktion ist danach abgeschaltet, bis ein neuer eingetragen wird.`,
"Löschen",
() => serviceRufen("zugang_setzen", { dienst, token: "" }),
);
return;
}
const g = e.target.closest("[data-go]");
if (g) { const [n, i] = g.dataset.go.split(":"); go(n, i); }
});
@@ -6338,6 +6388,9 @@ async function datenLaden(erzwingen) {
INTEGRATION_UPDATE = versionState.attributes.daten
? versionState.attributes.daten.integration_update ?? null
: null;
ZUGAENGE = versionState.attributes.daten
? versionState.attributes.daten.zugaenge ?? []
: [];
}
if (entitaetenState && entitaetenState.attributes) ENTITAETEN = entitaetenState.attributes.daten;